initial commit
|
@ -0,0 +1,130 @@
|
|||
|
||||
# Created by https://www.gitignore.io/api/intellij,go,linux,osx,windows
|
||||
|
||||
### Intellij ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
## File-based project format:
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
|
||||
### Go ###
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
*.swp
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
|
||||
### OSX ###
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
|
||||
### Windows ###
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
build/
|
||||
bind/
|
||||
vendor/
|
||||
mecab/
|
||||
*.log
|
||||
.vscode/
|
||||
changer
|
||||
|
||||
settings_test.yml
|
|
@ -0,0 +1,61 @@
|
|||
|
||||
|
||||
# This how we want to name the binary output
|
||||
BINARY=changer
|
||||
|
||||
# These are the values we want to pass for VERSION and BUILD
|
||||
# git tag 1.0.1
|
||||
# git commit -am "One more change after the tags"
|
||||
VERSION=`git describe --always`
|
||||
BUILD=`date +%FT%T%z`
|
||||
|
||||
# Setup the -ldflags option for go build here, interpolate the variable values
|
||||
LDFLAGS=-ldflags "-w -s -X amuz.es/gogs/infra/changer/util.version=${VERSION} -X amuz.es/gogs/infra/changer/util.buildDate=${BUILD}"
|
||||
CGO_ENABLED=0
|
||||
|
||||
|
||||
# Builds the project
|
||||
build:
|
||||
go build ${LDFLAGS} -o ${BINARY}
|
||||
strip -x ${BINARY}
|
||||
|
||||
# Builds the project
|
||||
setup:
|
||||
go get -u github.com/kardianos/govendor
|
||||
go get -u github.com/jteeuwen/go-bindata/...
|
||||
# go get -u github.com/gogo/protobuf/proto
|
||||
# go get -u github.com/gogo/protobuf/protoc-gen-gogo
|
||||
# go get -u github.com/gogo/protobuf/gogoproto
|
||||
# go get -u github.com/gogo/protobuf/protoc-gen-gofast
|
||||
# go get -u github.com/gogo/protobuf/protoc-gen-gogofaster
|
||||
# go get -u github.com/golang/protobuf/proto
|
||||
${GOPATH}/bin/govendor fetch -v +missing
|
||||
#libjpeg-turbo
|
||||
generate:
|
||||
go get -u github.com/gogo/protobuf/proto
|
||||
go get -u github.com/gogo/protobuf/protoc-gen-gogo
|
||||
go get -u github.com/gogo/protobuf/gogoproto
|
||||
go get -u github.com/gogo/protobuf/protoc-gen-gofast
|
||||
go get -u github.com/gogo/protobuf/protoc-gen-gogofaster
|
||||
go get -u github.com/golang/protobuf/proto
|
||||
go get -u github.com/mailru/easyjson
|
||||
protoc --gogofaster_out=. --proto_path=../../:. subsys/redis/app_token_data.proto
|
||||
go-bindata -nomemcopy -ignore .DS_Store -prefix asset/static -o bind/static/data.go -pkg static asset/static/...
|
||||
go-bindata -nomemcopy -ignore .DS_Store -prefix asset/template -o bind/template/data.go -pkg template asset/template/...
|
||||
#${GOPATH}/bin/ffjson subsys/http/iface/rest.go
|
||||
${GOPATH}/bin/easyjson subsys/http/iface/rest.go
|
||||
# go-bindata -debug -nomemcopy -ignore .DS_Store -prefix asset/static -o bind/static/data.go -pkg static asset/static/...
|
||||
# go-bindata -debug -nomemcopy -ignore .DS_Store -prefix asset/template -o bind/template/data.go -pkg template asset/template/...
|
||||
|
||||
|
||||
strip:
|
||||
upx rooibos
|
||||
# Installs our project: copies binaries
|
||||
install:
|
||||
go install ${LDFLAGS}
|
||||
|
||||
# Cleans our project: deletes binaries
|
||||
clean:
|
||||
if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi
|
||||
|
||||
.PHONY: clean install
|
|
@ -0,0 +1,98 @@
|
|||
![alt tag](https://upload.wikimedia.org/wikipedia/commons/2/23/Golang.png)
|
||||
|
||||
[![Build Status](https://travis-ci.org/Massad/gin-boilerplate.svg?branch=master)](https://travis-ci.org/Massad/gin-boilerplate)
|
||||
[![Join the chat at https://gitter.im/Massad/gin-boilerplate](https://badges.gitter.im/Massad/gin-boilerplate.svg)](https://gitter.im/Massad/gin-boilerplate?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Welcome to **Golang Gin boilerplate**!
|
||||
|
||||
The fastest way to deploy a restful api's with [Gin Framework](https://gin-gonic.github.io/gin/) with a structured project that defaults to **PostgreSQL** database and **Redis** as the session storage.
|
||||
|
||||
## Configured with
|
||||
|
||||
* [go-gorp](github.com/go-gorp/gorp): Go Relational Persistence
|
||||
* [RedisStore](https://github.com/gin-gonic/contrib/tree/master/sessions): Gin middleware for session management with multi-backend support (currently cookie, Redis).
|
||||
* Built-in **CORS Middleware**
|
||||
* Feature **PostgreSQL 9.4** JSON queries
|
||||
* Unit test
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
$ go get github.com/Massad/gin-boilerplate
|
||||
```
|
||||
|
||||
```
|
||||
$ cd $GOPATH/src/github.com/Massad/gin-boilerplate
|
||||
```
|
||||
|
||||
```
|
||||
$ go get -t -v ./...
|
||||
```
|
||||
|
||||
> Sometimes you need to get this package manually
|
||||
```
|
||||
$ go get github.com/bmizerany/assert
|
||||
```
|
||||
|
||||
You will find the **database.sql** in `db/database.sql`
|
||||
|
||||
And you can import the postgres database using this command:
|
||||
```
|
||||
$ psql -U postgres -h localhost < ./db/database.sql
|
||||
```
|
||||
|
||||
## Running Your Application
|
||||
|
||||
```
|
||||
$ go run *.go
|
||||
```
|
||||
|
||||
## Building Your Application
|
||||
|
||||
```
|
||||
$ go build -v
|
||||
```
|
||||
|
||||
```
|
||||
$ ./gin-boilerplate
|
||||
```
|
||||
|
||||
## Testing Your Application
|
||||
|
||||
```
|
||||
$ go test -v ./tests/*
|
||||
```
|
||||
|
||||
|
||||
## Import Postman Collection (API's)
|
||||
You can import from this [link](https://www.getpostman.com/collections/ac0680f90961bafd5de7). If you don't have **Postman**, check this link [https://www.getpostman.com](https://www.getpostman.com/)
|
||||
|
||||
## Contribution
|
||||
|
||||
You are welcome to contribute to keep it up to date and always improving!
|
||||
|
||||
If you have any question or need help, drop a message at [https://gitter.im/Massad/gin-boilerplate](https://gitter.im/Massad/gin-boilerplate)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
(The MIT License)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
After Width: | Height: | Size: 3.1 KiB |
|
@ -0,0 +1,12 @@
|
|||
<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
width="40px" height="40px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<path fill="#eb3c6a" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z">
|
||||
<animateTransform attributeType="xml"
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 25 25"
|
||||
to="360 25 25"
|
||||
dur="0.6s"
|
||||
repeatCount="indefinite"/>
|
||||
</path>
|
||||
</svg>
|
After Width: | Height: | Size: 603 B |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 34 KiB |
|
@ -0,0 +1,33 @@
|
|||
define([
|
||||
'require',
|
||||
'jquery',
|
||||
'svg!../../images/ajax-loader.svg',
|
||||
'jquery.blockui'
|
||||
], function (require,
|
||||
$,
|
||||
loadimg) {
|
||||
"use strict";
|
||||
return {
|
||||
blockUI: function (item) {
|
||||
$(item).block({
|
||||
message: loadimg,
|
||||
css: {
|
||||
border: 'none',
|
||||
padding: '0px',
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
backgroundColor: 'transparent'
|
||||
},
|
||||
overlayCSS: {
|
||||
backgroundColor: '#fff',
|
||||
opacity: 0.9,
|
||||
cursor: 'wait'
|
||||
}
|
||||
});
|
||||
}
|
||||
,
|
||||
unblockUI: function (item) {
|
||||
$(item).unblock();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
'use strict';
|
||||
|
||||
function dep(baseUrl) {
|
||||
// Require.js allows us to configure shortcut alias
|
||||
return {
|
||||
baseUrl: baseUrl,
|
||||
|
||||
bundles: {
|
||||
// "javascripts/common/export.min": [
|
||||
// 'javascripts/common/enums',
|
||||
// 'javascripts/common/routes'
|
||||
// ],
|
||||
"javascripts/common/mixed": [
|
||||
'javascripts/common/tether.mixed',
|
||||
'javascripts/common/toastr.mixed'
|
||||
]
|
||||
},
|
||||
|
||||
map: {
|
||||
'*': {
|
||||
tpl: 'https://cdnjs.cloudflare.com/ajax/libs/requirejs-tpl/0.0.2/tpl.min.js',
|
||||
toastr: 'javascripts/common/toastr.mixed',
|
||||
tether: 'javascripts/common/tether.mixed',
|
||||
postcode: 'javascripts/common/postcode.mixed!',
|
||||
},
|
||||
'javascripts/common/toastr.mixed': {
|
||||
'toastr': 'toastr'
|
||||
},
|
||||
'javascripts/common/tether.mixed': {
|
||||
'tether': 'tether'
|
||||
},
|
||||
},
|
||||
paths: {
|
||||
text: 'https://cdnjs.cloudflare.com/ajax/libs/require-text/2.0.12/text.min',
|
||||
svg: 'libs/requirejs-svg-0.0.1/svg.min',
|
||||
css: 'https://cdnjs.cloudflare.com/ajax/libs/require-css/0.1.8/css.min',
|
||||
json: 'https://cdnjs.cloudflare.com/ajax/libs/requirejs-plugins/1.0.3/json.min',
|
||||
noext: 'https://cdnjs.cloudflare.com/ajax/libs/requirejs-plugins/1.0.3/noext.min',
|
||||
slimscroll: 'https://cdnjs.cloudflare.com/ajax/libs/jQuery-slimScroll/1.3.8/jquery.slimscroll.min',
|
||||
jquery: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min',
|
||||
'jquery.form': 'https://cdnjs.cloudflare.com/ajax/libs/jquery.form/3.51/jquery.form.min',
|
||||
'jquery.blockui': 'https://cdnjs.cloudflare.com/ajax/libs/jquery.blockUI/2.70/jquery.blockUI.min',
|
||||
underscore: 'https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min',
|
||||
'underscore.string': 'https://cdnjs.cloudflare.com/ajax/libs/underscore.string/3.3.4/underscore.string.min',
|
||||
toastr: 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.min',
|
||||
bootstrap: 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/js/bootstrap',
|
||||
domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min',
|
||||
backbone: 'https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min',
|
||||
'backbone.paginator': 'https://cdnjs.cloudflare.com/ajax/libs/backbone.paginator/2.0.5/backbone.paginator.min',
|
||||
'backbone.bootstrapModal': 'libs/backbone-bootstrap-modal/backbone-bootstrap-modal',
|
||||
'backbone.computedfields': 'libs/backbone-computedfields-0.0.11/backbone.computedfields.min',
|
||||
moment: 'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.16.0/moment.min',
|
||||
tether: 'https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether',
|
||||
'autoNumeric': 'https://cdnjs.cloudflare.com/ajax/libs/autonumeric/1.9.46/autoNumeric.min',
|
||||
'browser-update': 'https://browser-update.org/update.min',
|
||||
},
|
||||
// scripts that do not call define() to register a module
|
||||
shim: {
|
||||
bootstrap: {
|
||||
deps: ['jquery', 'tether'],
|
||||
// exports: "$.fn.tooltip"
|
||||
},
|
||||
slimscroll: {
|
||||
deps: ['jquery'],
|
||||
exports: "$.fn.slimScroll"
|
||||
},
|
||||
underscore: {
|
||||
exports: '_'
|
||||
},
|
||||
backbone: {
|
||||
deps: [
|
||||
'underscore',
|
||||
'jquery'
|
||||
],
|
||||
exports: 'Backbone'
|
||||
},
|
||||
'jquery.form': {
|
||||
deps: ['jquery'],
|
||||
exports: '$.fn.ajaxSubmit'
|
||||
},
|
||||
toastr: {
|
||||
deps: [
|
||||
'css!https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.min'
|
||||
]
|
||||
},
|
||||
'backbone.computedfields': {
|
||||
deps: [
|
||||
'backbone',
|
||||
],
|
||||
exports: 'Backbone.ComputedFields',
|
||||
},
|
||||
'ion.rangeSlider': {
|
||||
deps: [
|
||||
'jquery',
|
||||
'css!https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/css/ion.rangeSlider.min',
|
||||
'css!https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/css/ion.rangeSlider.skinFlat.min'
|
||||
],
|
||||
exports: '$.fn.ionRangeSlider'
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
define(['require', 'domReady', 'jquery', 'bootstrap', 'slimscroll'], function (require, domReady, $, bootstrap) {
|
||||
"use strict";
|
||||
|
||||
// Slimscroll
|
||||
$('.slimscroll').slimscroll({
|
||||
height: "100%",
|
||||
color: "#ccc",
|
||||
distance: "0",
|
||||
opacity: 0.3,
|
||||
allowPageScroll: true,
|
||||
borderRadius: "0",
|
||||
railBorderRadius: "0"
|
||||
});
|
||||
|
||||
// Makes .page-inner height same as .page-sidebar height
|
||||
var sidebarAndContentHeight = function () {
|
||||
var content = $('.page-inner'),
|
||||
sidebar = $('.page-sidebar'),
|
||||
body = $('body'),
|
||||
height = $(window).height();
|
||||
|
||||
if (height >= content.height()) {
|
||||
content.attr('style', 'min-height:' + height + 'px !important');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
domReady(function () {
|
||||
sidebarAndContentHeight();
|
||||
$(window).on('resize', sidebarAndContentHeight);
|
||||
window.$buoop = {vs: {i: 9, f: -2, o: -2, s: 6, c: -2}, unsecure: true, api: 4};
|
||||
require(['browser-update']);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
define('javascripts/common/tether.mixed',['tether'], function (tether) {
|
||||
"use strict";
|
||||
window.Tether=tether;
|
||||
return tether;
|
||||
});
|
||||
define('javascripts/common/toastr.mixed',['toastr'], function (toastr) {
|
||||
"use strict";
|
||||
toastr.options = {
|
||||
closeButton: true,
|
||||
debug: false,
|
||||
newestOnTop: true,
|
||||
progressBar: false,
|
||||
positionClass: 'toast-top-right',
|
||||
preventDuplicates: false,
|
||||
onclick: null,
|
||||
showDuration: '300',
|
||||
hideDuration: '1000',
|
||||
timeOut: '5000',
|
||||
extendedTimeOut: '1000',
|
||||
showEasing: 'swing',
|
||||
hideEasing: 'linear',
|
||||
showMethod: 'fadeIn',
|
||||
hideMethod: 'fadeOut'
|
||||
};
|
||||
return toastr;
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
define(['jquery', 'underscore', 'backbone.bootstrapModal', 'tpl!views/widgets/modal'], function ($, _, Modal, template) {
|
||||
"use strict";
|
||||
return Modal.extend({
|
||||
initialize: function (options) {
|
||||
this.options = _.extend({
|
||||
title: null,
|
||||
okText: 'OK',
|
||||
focusOk: true,
|
||||
okCloses: false,
|
||||
cancelText: 'Cancel',
|
||||
showFooter: true,
|
||||
allowCancel: true,
|
||||
escape: true,
|
||||
animate: true,
|
||||
template: template,
|
||||
enterTriggersOk: false
|
||||
}, options);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,284 @@
|
|||
/**
|
||||
* Bootstrap Modal wrapper for use with Backbone.
|
||||
*
|
||||
* Takes care of instantiation, manages multiple modals,
|
||||
* adds several options and removes the element from the DOM when closed
|
||||
*
|
||||
* @author Charles Davison <charlie@powmedia.co.uk>
|
||||
*
|
||||
* Events:
|
||||
* shown: Fired when the modal has finished animating in
|
||||
* hidden: Fired when the modal has finished animating out
|
||||
* cancel: The user dismissed the modal
|
||||
* ok: The user clicked OK
|
||||
*/
|
||||
define(['jquery', 'underscore', 'backbone'], function ($, _, Backbone) {
|
||||
|
||||
//Set custom template settings
|
||||
var _interpolateBackup = _.templateSettings;
|
||||
_.templateSettings = {
|
||||
interpolate: /\{\{(.+?)\}\}/g,
|
||||
evaluate: /<%([\s\S]+?)%>/g
|
||||
};
|
||||
|
||||
var template = _.template('\
|
||||
<div class="modal-dialog"><div class="modal-content">\
|
||||
<% if (title) { %>\
|
||||
<div class="modal-header">\
|
||||
<% if (allowCancel) { %>\
|
||||
<a class="close">×</a>\
|
||||
<% } %>\
|
||||
<h4>{{title}}</h4>\
|
||||
</div>\
|
||||
<% } %>\
|
||||
<div class="modal-body">{{content}}</div>\
|
||||
<% if (showFooter) { %>\
|
||||
<div class="modal-footer">\
|
||||
<% if (allowCancel) { %>\
|
||||
<% if (cancelText) { %>\
|
||||
<a href="#" class="btn cancel">{{cancelText}}</a>\
|
||||
<% } %>\
|
||||
<% } %>\
|
||||
<a href="#" class="btn ok btn-primary">{{okText}}</a>\
|
||||
</div>\
|
||||
<% } %>\
|
||||
</div></div>\
|
||||
');
|
||||
|
||||
//Reset to users' template settings
|
||||
_.templateSettings = _interpolateBackup;
|
||||
|
||||
|
||||
var Modal = Backbone.View.extend({
|
||||
|
||||
className: 'modal',
|
||||
|
||||
events: {
|
||||
'click .close': function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.trigger('cancel');
|
||||
|
||||
if (this.options.content && this.options.content.trigger) {
|
||||
this.options.content.trigger('cancel', this);
|
||||
}
|
||||
},
|
||||
'click .cancel': function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.trigger('cancel');
|
||||
|
||||
if (this.options.content && this.options.content.trigger) {
|
||||
this.options.content.trigger('cancel', this);
|
||||
}
|
||||
},
|
||||
'click .ok': function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.trigger('ok');
|
||||
|
||||
if (this.options.content && this.options.content.trigger) {
|
||||
this.options.content.trigger('ok', this);
|
||||
}
|
||||
|
||||
if (this.options.okCloses) {
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
'keypress': function(event) {
|
||||
if (this.options.enterTriggersOk && event.which == 13) {
|
||||
event.preventDefault();
|
||||
|
||||
this.trigger('ok');
|
||||
|
||||
if (this.options.content && this.options.content.trigger) {
|
||||
this.options.content.trigger('ok', this);
|
||||
}
|
||||
|
||||
if (this.options.okCloses) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates an instance of a Bootstrap Modal
|
||||
*
|
||||
* @see http://twitter.github.com/bootstrap/javascript.html#modals
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {String|View} [options.content] Modal content. Default: none
|
||||
* @param {String} [options.title] Title. Default: none
|
||||
* @param {String} [options.okText] Text for the OK button. Default: 'OK'
|
||||
* @param {String} [options.cancelText] Text for the cancel button. Default: 'Cancel'. If passed a falsey value, the button will be removed
|
||||
* @param {Boolean} [options.allowCancel Whether the modal can be closed, other than by pressing OK. Default: true
|
||||
* @param {Boolean} [options.escape] Whether the 'esc' key can dismiss the modal. Default: true, but false if options.cancellable is true
|
||||
* @param {Boolean} [options.animate] Whether to animate in/out. Default: false
|
||||
* @param {Function} [options.template] Compiled underscore template to override the default one
|
||||
* @param {Boolean} [options.enterTriggersOk] Whether the 'enter' key will trigger OK. Default: false
|
||||
*/
|
||||
initialize: function(options) {
|
||||
this.options = _.extend({
|
||||
title: null,
|
||||
okText: 'OK',
|
||||
focusOk: true,
|
||||
okCloses: true,
|
||||
cancelText: 'Cancel',
|
||||
showFooter: true,
|
||||
allowCancel: true,
|
||||
escape: true,
|
||||
animate: false,
|
||||
template: template,
|
||||
enterTriggersOk: false
|
||||
}, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the DOM element
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
render: function() {
|
||||
var $el = this.$el,
|
||||
options = this.options,
|
||||
content = options.content;
|
||||
|
||||
//Create the modal container
|
||||
$el.html(options.template(options));
|
||||
|
||||
var $content = this.$content = $el.find('.modal-body')
|
||||
|
||||
//Insert the main content if it's a view
|
||||
if (content && content.$el) {
|
||||
content.render();
|
||||
$el.find('.modal-body').html(content.$el);
|
||||
}
|
||||
|
||||
if (options.animate) $el.addClass('fade');
|
||||
|
||||
this.isRendered = true;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders and shows the modal
|
||||
*
|
||||
* @param {Function} [cb] Optional callback that runs only when OK is pressed.
|
||||
*/
|
||||
open: function(cb) {
|
||||
if (!this.isRendered) this.render();
|
||||
|
||||
var self = this,
|
||||
$el = this.$el;
|
||||
|
||||
//Create it
|
||||
$el.modal(_.extend({
|
||||
keyboard: this.options.allowCancel,
|
||||
backdrop: this.options.allowCancel ? true : 'static'
|
||||
}, this.options.modalOptions));
|
||||
|
||||
//Focus OK button
|
||||
$el.one('shown.bs.modal', function() {
|
||||
if (self.options.focusOk) {
|
||||
$el.find('.btn.ok').focus();
|
||||
}
|
||||
|
||||
if (self.options.content && self.options.content.trigger) {
|
||||
self.options.content.trigger('shown', self);
|
||||
}
|
||||
|
||||
self.trigger('shown');
|
||||
});
|
||||
|
||||
//Adjust the modal and backdrop z-index; for dealing with multiple modals
|
||||
var numModals = Modal.count,
|
||||
$backdrop = $('.modal-backdrop:eq('+numModals+')'),
|
||||
backdropIndex = parseInt($backdrop.css('z-index'),10),
|
||||
elIndex = parseInt($backdrop.css('z-index'), 10);
|
||||
|
||||
$backdrop.css('z-index', backdropIndex + numModals);
|
||||
this.$el.css('z-index', elIndex + numModals);
|
||||
|
||||
if (this.options.allowCancel) {
|
||||
$backdrop.one('click', function() {
|
||||
if (self.options.content && self.options.content.trigger) {
|
||||
self.options.content.trigger('cancel', self);
|
||||
}
|
||||
|
||||
self.trigger('cancel');
|
||||
});
|
||||
|
||||
$(document).one('keyup.dismiss.modal', function (e) {
|
||||
e.which == 27 && self.trigger('cancel');
|
||||
|
||||
if (self.options.content && self.options.content.trigger) {
|
||||
e.which == 27 && self.options.content.trigger('shown', self);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.on('cancel', function() {
|
||||
self.close();
|
||||
});
|
||||
|
||||
Modal.count++;
|
||||
|
||||
//Run callback on OK if provided
|
||||
if (cb) {
|
||||
self.on('ok', cb);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the modal
|
||||
*/
|
||||
close: function() {
|
||||
var self = this,
|
||||
$el = this.$el;
|
||||
|
||||
//Check if the modal should stay open
|
||||
if (this._preventClose) {
|
||||
this._preventClose = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$el.one('hidden.bs.modal', function onHidden(e) {
|
||||
// Ignore events propagated from interior objects, like bootstrap tooltips
|
||||
if(e.target !== e.currentTarget){
|
||||
return $el.one('hidden', onHidden);
|
||||
}
|
||||
self.remove();
|
||||
|
||||
if (self.options.content && self.options.content.trigger) {
|
||||
self.options.content.trigger('hidden', self);
|
||||
}
|
||||
|
||||
self.trigger('hidden');
|
||||
});
|
||||
|
||||
$el.modal('hide');
|
||||
|
||||
Modal.count--;
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop the modal from closing.
|
||||
* Can be called from within a 'close' or 'ok' event listener.
|
||||
*/
|
||||
preventClose: function() {
|
||||
this._preventClose = true;
|
||||
}
|
||||
}, {
|
||||
//STATICS
|
||||
|
||||
//The number of modals on display
|
||||
count: 0
|
||||
});
|
||||
|
||||
return Modal;
|
||||
|
||||
});
|
|
@ -0,0 +1,136 @@
|
|||
// Backbone.ComputedFields, v0.0.10
|
||||
// Copyright (c)2014 alexander.beletsky@gmail.com
|
||||
// Distributed under MIT license
|
||||
// https://github.com/alexanderbeletsky/backbone-computedfields
|
||||
|
||||
Backbone.ComputedFields = (function(Backbone, _){
|
||||
|
||||
var ComputedFields = function (model) {
|
||||
this.model = model;
|
||||
this._computedFields = [];
|
||||
|
||||
this.initialize();
|
||||
};
|
||||
|
||||
_.extend(ComputedFields.prototype, {
|
||||
initialize: function () {
|
||||
_.bindAll(
|
||||
this,
|
||||
'_bindModelEvents',
|
||||
'_computeFieldValue',
|
||||
'_dependentFields',
|
||||
'_isModelInitialized',
|
||||
'_lookUpComputedFields',
|
||||
'_thenComputedChanges',
|
||||
'_thenDependentChanges',
|
||||
'_toJSON',
|
||||
'_wrapJSON',
|
||||
'initialize'
|
||||
);
|
||||
|
||||
this._lookUpComputedFields();
|
||||
this._bindModelEvents();
|
||||
this._wrapJSON();
|
||||
},
|
||||
|
||||
_lookUpComputedFields: function () {
|
||||
for (var obj in this.model.computed) {
|
||||
var field = this.model.computed[obj];
|
||||
|
||||
if (field && (field.set || field.get)) {
|
||||
this._computedFields.push({name: obj, field: field});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_bindModelEvents: function () {
|
||||
_.each(this._computedFields, function (computedField) {
|
||||
var fieldName = computedField.name;
|
||||
var field = computedField.field;
|
||||
|
||||
var updateComputed = _.bind(function () {
|
||||
var value = this._computeFieldValue(field);
|
||||
this.model.set(fieldName, value, { skipChangeEvent: true });
|
||||
}, this);
|
||||
|
||||
var updateDependent = _.bind(function (model, value, options) {
|
||||
if (options && options.skipChangeEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.set) {
|
||||
var fields = this._dependentFields(field.depends);
|
||||
value = value || this.model.get(fieldName);
|
||||
|
||||
field.set.call(this.model, value, fields);
|
||||
this.model.set(fields, options);
|
||||
}
|
||||
}, this);
|
||||
|
||||
this._thenDependentChanges(field.depends, updateComputed);
|
||||
this._thenComputedChanges(fieldName, updateDependent);
|
||||
|
||||
if (this._isModelInitialized()) {
|
||||
updateComputed();
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
_isModelInitialized: function () {
|
||||
return !_.isEmpty(this.model.attributes);
|
||||
},
|
||||
|
||||
_thenDependentChanges: function (depends, callback) {
|
||||
_.each(depends, function (name) {
|
||||
if (typeof (name) === 'string') {
|
||||
this.model.on('change:' + name, callback);
|
||||
}
|
||||
|
||||
if (typeof (name) === 'function') {
|
||||
name.call(this.model, callback);
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
_thenComputedChanges: function (fieldName, callback) {
|
||||
this.model.on('change:' + fieldName, callback);
|
||||
},
|
||||
|
||||
_wrapJSON: function () {
|
||||
this.model.toJSON = _.wrap(this.model.toJSON, this._toJSON);
|
||||
},
|
||||
|
||||
_toJSON: function (toJSON) {
|
||||
var args = Array.prototype.slice.call(arguments, 1),
|
||||
attributes = toJSON.apply(this.model, args),
|
||||
strip = !!(args[0] || {}).computedFields;
|
||||
|
||||
var stripped = strip ? {} : _.reduce(this._computedFields, function (memo, computed) {
|
||||
if (computed.field.toJSON === false) {
|
||||
memo.push(computed.name);
|
||||
}
|
||||
return memo;
|
||||
},[]);
|
||||
|
||||
return _.omit(attributes, stripped);
|
||||
},
|
||||
|
||||
_computeFieldValue: function (computedField) {
|
||||
if (computedField && computedField.get) {
|
||||
var fields = this._dependentFields(computedField.depends);
|
||||
return computedField.get.call(this.model, fields);
|
||||
}
|
||||
},
|
||||
|
||||
_dependentFields: function (depends) {
|
||||
return _.reduce(depends, function (memo, field) {
|
||||
memo[field] = this.model.get(field);
|
||||
return memo;
|
||||
}, {}, this);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return ComputedFields;
|
||||
|
||||
})(Backbone, _);
|
1
asset/static/libs/backbone-computedfields-0.0.11/backbone.computedfields.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Backbone.ComputedFields=function(a,b){var c=function(a){this.model=a,this._computedFields=[],this.initialize()};return b.extend(c.prototype,{initialize:function(){b.bindAll(this,"_bindModelEvents","_computeFieldValue","_dependentFields","_isModelInitialized","_lookUpComputedFields","_thenComputedChanges","_thenDependentChanges","_toJSON","_wrapJSON","initialize"),this._lookUpComputedFields(),this._bindModelEvents(),this._wrapJSON()},_lookUpComputedFields:function(){for(var a in this.model.computed){var b=this.model.computed[a];b&&(b.set||b.get)&&this._computedFields.push({name:a,field:b})}},_bindModelEvents:function(){b.each(this._computedFields,function(a){var c=a.name,d=a.field,e=b.bind(function(){var a=this._computeFieldValue(d);this.model.set(c,a,{skipChangeEvent:!0})},this),f=b.bind(function(a,b,e){if((!e||!e.skipChangeEvent)&&d.set){var f=this._dependentFields(d.depends);b=b||this.model.get(c),d.set.call(this.model,b,f),this.model.set(f,e)}},this);this._thenDependentChanges(d.depends,e),this._thenComputedChanges(c,f),this._isModelInitialized()&&e()},this)},_isModelInitialized:function(){return!b.isEmpty(this.model.attributes)},_thenDependentChanges:function(a,c){b.each(a,function(a){"string"==typeof a&&this.model.on("change:"+a,c),"function"==typeof a&&a.call(this.model,c)},this)},_thenComputedChanges:function(a,b){this.model.on("change:"+a,b)},_wrapJSON:function(){this.model.toJSON=b.wrap(this.model.toJSON,this._toJSON)},_toJSON:function(a){var c=Array.prototype.slice.call(arguments,1),d=a.apply(this.model,c),e=!!(c[0]||{}).computedFields,f=e?{}:b.reduce(this._computedFields,function(a,b){return b.field.toJSON===!1&&a.push(b.name),a},[]);return b.omit(d,f)},_computeFieldValue:function(a){if(a&&a.get){var b=this._dependentFields(a.depends);return a.get.call(this.model,b)}},_dependentFields:function(a){return b.reduce(a,function(a,b){return a[b]=this.model.get(b),a},{},this)}}),c}(Backbone,_);
|
|
@ -0,0 +1,75 @@
|
|||
/*jslint regexp: true */
|
||||
/*global define */
|
||||
|
||||
define(['module', 'text'], function (module, textPlugin) {
|
||||
'use strict';
|
||||
|
||||
var svg = {
|
||||
buildMap: {},
|
||||
sprite_id: 'requirejs-svg-sprite',
|
||||
sprite: null,
|
||||
|
||||
extractGraphicAsSymbol: function(document, svgText) {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = svgText;
|
||||
var element = div.querySelector('svg');
|
||||
var id = element.getAttribute('id');
|
||||
var viewBox = element.getAttribute('viewbox') || element.getAttribute('viewBox');
|
||||
return svg.createSymbol(document, id, element, viewBox);
|
||||
},
|
||||
|
||||
createSymbol: function(document, id, element, viewBox) {
|
||||
var symbol = document.createElementNS('http://www.w3.org/2000/svg', 'symbol');
|
||||
while (element.firstChild) {
|
||||
symbol.appendChild(element.firstChild);
|
||||
}
|
||||
typeof id === 'string' && symbol.setAttribute('id', id);
|
||||
typeof viewBox === 'string' && symbol.setAttribute('viewBox', viewBox);
|
||||
return symbol;
|
||||
},
|
||||
|
||||
createSprite: function(document) {
|
||||
svg.sprite = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.sprite.setAttribute('style', 'display: none');
|
||||
svg.sprite.setAttribute('id', this.sprite_id);
|
||||
return svg.sprite;
|
||||
},
|
||||
|
||||
appendSprite: function(content) {
|
||||
if (! svg.sprite) {
|
||||
svg.createSprite(document);
|
||||
document.body.appendChild(svg.sprite);
|
||||
}
|
||||
|
||||
svg.sprite.appendChild( svg.extractGraphicAsSymbol(document, content) );
|
||||
},
|
||||
|
||||
finishLoad: function(name, onLoad, config) {
|
||||
return function(content) {
|
||||
if (config && !config.isBuild) {
|
||||
svg.appendSprite(content);
|
||||
}
|
||||
else {
|
||||
svg.buildMap[name] = content;
|
||||
}
|
||||
|
||||
return onLoad(content);
|
||||
};
|
||||
},
|
||||
|
||||
load: function(name, req, onLoad, config) {
|
||||
textPlugin.load(name + '!strip', req, svg.finishLoad(name, onLoad, config), config);
|
||||
},
|
||||
|
||||
write: function (pluginName, moduleName, write/*, config*/) {
|
||||
if (svg.buildMap.hasOwnProperty(moduleName)) {
|
||||
var content = textPlugin.jsEscape(svg.buildMap[moduleName]);
|
||||
write.asModule(pluginName + "!" + moduleName,
|
||||
"define(['" + pluginName + "'], function (svg) { svg.appendSprite('" + content + "');});\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return svg;
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
define(["module","text"],function(g,f){var b={b:{},j:"requirejs-svg-sprite",a:null,h:function(a,e){var c=a.createElement("div");c.innerHTML=e;var c=c.querySelector("svg"),d=c.getAttribute("id"),f=c.getAttribute("viewbox")||c.getAttribute("viewBox");return b.g(a,d,c,f)},g:function(a,b,c,d){for(a=a.createElementNS("http://www.w3.org/2000/svg","symbol");c.firstChild;)a.appendChild(c.firstChild);"string"===typeof b&&a.setAttribute("id",b);"string"===typeof d&&a.setAttribute("viewBox",d);return a},f:function(a){b.a=
|
||||
a.createElementNS("http://www.w3.org/2000/svg","svg");b.a.setAttribute("style","display: none");b.a.setAttribute("id",this.j);return b.a},c:function(a){b.a||(b.f(document),document.body.appendChild(b.a));b.a.appendChild(b.h(document,a))},i:function(a,e,c){return function(d){c&&!c.m?b.c(d):b.b[a]=d;return e(d)}},load:function(a,e,c,d){f.load(a+"!strip",e,b.i(a,c,d),d)},write:function(a,e,c){if(b.b.hasOwnProperty(e)){var d=f.o(b.b[e]);c.l(a+"!"+e,"define(['"+a+"'], function (svg) { svg.appendSprite('"+
|
||||
d+"');});\n")}}};return b});
|
|
@ -0,0 +1,484 @@
|
|||
.select2-container {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
vertical-align: middle; }
|
||||
.select2-container .select2-selection--single {
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 28px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; }
|
||||
.select2-container .select2-selection--single .select2-selection__rendered {
|
||||
display: block;
|
||||
padding-left: 8px;
|
||||
padding-right: 20px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap; }
|
||||
.select2-container .select2-selection--single .select2-selection__clear {
|
||||
position: relative; }
|
||||
.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
|
||||
padding-right: 8px;
|
||||
padding-left: 20px; }
|
||||
.select2-container .select2-selection--multiple {
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
min-height: 32px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; }
|
||||
.select2-container .select2-selection--multiple .select2-selection__rendered {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
padding-left: 8px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap; }
|
||||
.select2-container .select2-search--inline {
|
||||
float: left; }
|
||||
.select2-container .select2-search--inline .select2-search__field {
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
font-size: 100%;
|
||||
margin-top: 5px;
|
||||
padding: 0; }
|
||||
.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none; }
|
||||
|
||||
.select2-dropdown {
|
||||
background-color: white;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -100000px;
|
||||
width: 100%;
|
||||
z-index: 1051; }
|
||||
|
||||
.select2-results {
|
||||
display: block; }
|
||||
|
||||
.select2-results__options {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
|
||||
.select2-results__option {
|
||||
padding: 6px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; }
|
||||
.select2-results__option[aria-selected] {
|
||||
cursor: pointer; }
|
||||
|
||||
.select2-container--open .select2-dropdown {
|
||||
left: 0; }
|
||||
|
||||
.select2-container--open .select2-dropdown--above {
|
||||
border-bottom: none;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0; }
|
||||
|
||||
.select2-container--open .select2-dropdown--below {
|
||||
border-top: none;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0; }
|
||||
|
||||
.select2-search--dropdown {
|
||||
display: block;
|
||||
padding: 4px; }
|
||||
.select2-search--dropdown .select2-search__field {
|
||||
padding: 4px;
|
||||
width: 100%;
|
||||
box-sizing: border-box; }
|
||||
.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none; }
|
||||
.select2-search--dropdown.select2-search--hide {
|
||||
display: none; }
|
||||
|
||||
.select2-close-mask {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: block;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
opacity: 0;
|
||||
z-index: 99;
|
||||
background-color: #fff;
|
||||
filter: alpha(opacity=0); }
|
||||
|
||||
.select2-hidden-accessible {
|
||||
border: 0 !important;
|
||||
clip: rect(0 0 0 0) !important;
|
||||
height: 1px !important;
|
||||
margin: -1px !important;
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
position: absolute !important;
|
||||
width: 1px !important; }
|
||||
|
||||
.select2-container--default .select2-selection--single {
|
||||
background-color: #fff;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
||||
color: #444;
|
||||
line-height: 28px; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__clear {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
font-weight: bold; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__placeholder {
|
||||
color: #999; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow {
|
||||
height: 26px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
width: 20px; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow b {
|
||||
border-color: #888 transparent transparent transparent;
|
||||
border-style: solid;
|
||||
border-width: 5px 4px 0 4px;
|
||||
height: 0;
|
||||
left: 50%;
|
||||
margin-left: -4px;
|
||||
margin-top: -2px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 0; }
|
||||
|
||||
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
|
||||
float: left; }
|
||||
|
||||
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
|
||||
left: 1px;
|
||||
right: auto; }
|
||||
|
||||
.select2-container--default.select2-container--disabled .select2-selection--single {
|
||||
background-color: #eee;
|
||||
cursor: default; }
|
||||
.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
|
||||
display: none; }
|
||||
|
||||
.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
|
||||
border-color: transparent transparent #888 transparent;
|
||||
border-width: 0 4px 5px 4px; }
|
||||
|
||||
.select2-container--default .select2-selection--multiple {
|
||||
background-color: white;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
cursor: text; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
|
||||
box-sizing: border-box;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0 5px;
|
||||
width: 100%; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__rendered li {
|
||||
list-style: none; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__placeholder {
|
||||
color: #999;
|
||||
margin-top: 5px;
|
||||
float: left; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__clear {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
font-weight: bold;
|
||||
margin-top: 5px;
|
||||
margin-right: 10px; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice {
|
||||
background-color: #e4e4e4;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
cursor: default;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
margin-top: 5px;
|
||||
padding: 0 5px; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin-right: 2px; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
|
||||
color: #333; }
|
||||
|
||||
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
|
||||
float: right; }
|
||||
|
||||
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
|
||||
margin-left: 5px;
|
||||
margin-right: auto; }
|
||||
|
||||
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
|
||||
margin-left: 2px;
|
||||
margin-right: auto; }
|
||||
|
||||
.select2-container--default.select2-container--focus .select2-selection--multiple {
|
||||
border: solid black 1px;
|
||||
outline: 0; }
|
||||
|
||||
.select2-container--default.select2-container--disabled .select2-selection--multiple {
|
||||
background-color: #eee;
|
||||
cursor: default; }
|
||||
|
||||
.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
|
||||
display: none; }
|
||||
|
||||
.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0; }
|
||||
|
||||
.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0; }
|
||||
|
||||
.select2-container--default .select2-search--dropdown .select2-search__field {
|
||||
border: 1px solid #aaa; }
|
||||
|
||||
.select2-container--default .select2-search--inline .select2-search__field {
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
-webkit-appearance: textfield; }
|
||||
|
||||
.select2-container--default .select2-results > .select2-results__options {
|
||||
max-height: 200px;
|
||||
overflow-y: auto; }
|
||||
|
||||
.select2-container--default .select2-results__option[role=group] {
|
||||
padding: 0; }
|
||||
|
||||
.select2-container--default .select2-results__option[aria-disabled=true] {
|
||||
color: #999; }
|
||||
|
||||
.select2-container--default .select2-results__option[aria-selected=true] {
|
||||
background-color: #ddd; }
|
||||
|
||||
.select2-container--default .select2-results__option .select2-results__option {
|
||||
padding-left: 1em; }
|
||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__group {
|
||||
padding-left: 0; }
|
||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -1em;
|
||||
padding-left: 2em; }
|
||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -2em;
|
||||
padding-left: 3em; }
|
||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -3em;
|
||||
padding-left: 4em; }
|
||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -4em;
|
||||
padding-left: 5em; }
|
||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -5em;
|
||||
padding-left: 6em; }
|
||||
|
||||
.select2-container--default .select2-results__option--highlighted[aria-selected] {
|
||||
background-color: #5897fb;
|
||||
color: white; }
|
||||
|
||||
.select2-container--default .select2-results__group {
|
||||
cursor: default;
|
||||
display: block;
|
||||
padding: 6px; }
|
||||
|
||||
.select2-container--classic .select2-selection--single {
|
||||
background-color: #f7f7f7;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
outline: 0;
|
||||
background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%);
|
||||
background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%);
|
||||
background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
|
||||
.select2-container--classic .select2-selection--single:focus {
|
||||
border: 1px solid #5897fb; }
|
||||
.select2-container--classic .select2-selection--single .select2-selection__rendered {
|
||||
color: #444;
|
||||
line-height: 28px; }
|
||||
.select2-container--classic .select2-selection--single .select2-selection__clear {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
font-weight: bold;
|
||||
margin-right: 10px; }
|
||||
.select2-container--classic .select2-selection--single .select2-selection__placeholder {
|
||||
color: #999; }
|
||||
.select2-container--classic .select2-selection--single .select2-selection__arrow {
|
||||
background-color: #ddd;
|
||||
border: none;
|
||||
border-left: 1px solid #aaa;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
height: 26px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
width: 20px;
|
||||
background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
|
||||
background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
|
||||
background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); }
|
||||
.select2-container--classic .select2-selection--single .select2-selection__arrow b {
|
||||
border-color: #888 transparent transparent transparent;
|
||||
border-style: solid;
|
||||
border-width: 5px 4px 0 4px;
|
||||
height: 0;
|
||||
left: 50%;
|
||||
margin-left: -4px;
|
||||
margin-top: -2px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 0; }
|
||||
|
||||
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
|
||||
float: left; }
|
||||
|
||||
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
|
||||
border: none;
|
||||
border-right: 1px solid #aaa;
|
||||
border-radius: 0;
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
left: 1px;
|
||||
right: auto; }
|
||||
|
||||
.select2-container--classic.select2-container--open .select2-selection--single {
|
||||
border: 1px solid #5897fb; }
|
||||
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
|
||||
background: transparent;
|
||||
border: none; }
|
||||
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
|
||||
border-color: transparent transparent #888 transparent;
|
||||
border-width: 0 4px 5px 4px; }
|
||||
|
||||
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
|
||||
border-top: none;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%);
|
||||
background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
|
||||
background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
|
||||
|
||||
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
|
||||
border-bottom: none;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%);
|
||||
background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%);
|
||||
background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); }
|
||||
|
||||
.select2-container--classic .select2-selection--multiple {
|
||||
background-color: white;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
cursor: text;
|
||||
outline: 0; }
|
||||
.select2-container--classic .select2-selection--multiple:focus {
|
||||
border: 1px solid #5897fb; }
|
||||
.select2-container--classic .select2-selection--multiple .select2-selection__rendered {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0 5px; }
|
||||
.select2-container--classic .select2-selection--multiple .select2-selection__clear {
|
||||
display: none; }
|
||||
.select2-container--classic .select2-selection--multiple .select2-selection__choice {
|
||||
background-color: #e4e4e4;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
cursor: default;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
margin-top: 5px;
|
||||
padding: 0 5px; }
|
||||
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin-right: 2px; }
|
||||
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
|
||||
color: #555; }
|
||||
|
||||
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
|
||||
float: right; }
|
||||
|
||||
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
|
||||
margin-left: 5px;
|
||||
margin-right: auto; }
|
||||
|
||||
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
|
||||
margin-left: 2px;
|
||||
margin-right: auto; }
|
||||
|
||||
.select2-container--classic.select2-container--open .select2-selection--multiple {
|
||||
border: 1px solid #5897fb; }
|
||||
|
||||
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
|
||||
border-top: none;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0; }
|
||||
|
||||
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
|
||||
border-bottom: none;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0; }
|
||||
|
||||
.select2-container--classic .select2-search--dropdown .select2-search__field {
|
||||
border: 1px solid #aaa;
|
||||
outline: 0; }
|
||||
|
||||
.select2-container--classic .select2-search--inline .select2-search__field {
|
||||
outline: 0;
|
||||
box-shadow: none; }
|
||||
|
||||
.select2-container--classic .select2-dropdown {
|
||||
background-color: white;
|
||||
border: 1px solid transparent; }
|
||||
|
||||
.select2-container--classic .select2-dropdown--above {
|
||||
border-bottom: none; }
|
||||
|
||||
.select2-container--classic .select2-dropdown--below {
|
||||
border-top: none; }
|
||||
|
||||
.select2-container--classic .select2-results > .select2-results__options {
|
||||
max-height: 200px;
|
||||
overflow-y: auto; }
|
||||
|
||||
.select2-container--classic .select2-results__option[role=group] {
|
||||
padding: 0; }
|
||||
|
||||
.select2-container--classic .select2-results__option[aria-disabled=true] {
|
||||
color: grey; }
|
||||
|
||||
.select2-container--classic .select2-results__option--highlighted[aria-selected] {
|
||||
background-color: #3875d7;
|
||||
color: white; }
|
||||
|
||||
.select2-container--classic .select2-results__group {
|
||||
cursor: default;
|
||||
display: block;
|
||||
padding: 6px; }
|
||||
|
||||
.select2-container--classic.select2-container--open .select2-dropdown {
|
||||
border-color: #5897fb; }
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ar",[],function(){return{errorLoading:function(){return"لا يمكن تحميل النتائج"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="الرجاء حذف "+t+" عناصر";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="الرجاء إضافة "+t+" عناصر";return n},loadingMore:function(){return"جاري تحميل نتائج إضافية..."},maximumSelected:function(e){var t="تستطيع إختيار "+e.maximum+" بنود فقط";return t},noResults:function(){return"لم يتم العثور على أي نتائج"},searching:function(){return"جاري البحث…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/az",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return t+" simvol silin"},inputTooShort:function(e){var t=e.minimum-e.input.length;return t+" simvol daxil edin"},loadingMore:function(){return"Daha çox nəticə yüklənir…"},maximumSelected:function(e){return"Sadəcə "+e.maximum+" element seçə bilərsiniz"},noResults:function(){return"Nəticə tapılmadı"},searching:function(){return"Axtarılır…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/bg",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Моля въведете с "+t+" по-малко символ";return t>1&&(n+="a"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Моля въведете още "+t+" символ";return t>1&&(n+="a"),n},loadingMore:function(){return"Зареждат се още…"},maximumSelected:function(e){var t="Можете да направите до "+e.maximum+" ";return e.maximum>1?t+="избора":t+="избор",t},noResults:function(){return"Няма намерени съвпадения"},searching:function(){return"Търсене…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ca",[],function(){return{errorLoading:function(){return"La càrrega ha fallat"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Si us plau, elimina "+t+" car";return t==1?n+="àcter":n+="àcters",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Si us plau, introdueix "+t+" car";return t==1?n+="àcter":n+="àcters",n},loadingMore:function(){return"Carregant més resultats…"},maximumSelected:function(e){var t="Només es pot seleccionar "+e.maximum+" element";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No s'han trobat resultats"},searching:function(){return"Cercant…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/cs",[],function(){function e(e,t){switch(e){case 2:return t?"dva":"dvě";case 3:return"tři";case 4:return"čtyři"}return""}return{errorLoading:function(){return"Výsledky nemohly být načteny."},inputTooLong:function(t){var n=t.input.length-t.maximum;return n==1?"Prosím zadejte o jeden znak méně":n<=4?"Prosím zadejte o "+e(n,!0)+" znaky méně":"Prosím zadejte o "+n+" znaků méně"},inputTooShort:function(t){var n=t.minimum-t.input.length;return n==1?"Prosím zadejte ještě jeden znak":n<=4?"Prosím zadejte ještě další "+e(n,!0)+" znaky":"Prosím zadejte ještě dalších "+n+" znaků"},loadingMore:function(){return"Načítají se další výsledky…"},maximumSelected:function(t){var n=t.maximum;return n==1?"Můžete zvolit jen jednu položku":n<=4?"Můžete zvolit maximálně "+e(n,!1)+" položky":"Můžete zvolit maximálně "+n+" položek"},noResults:function(){return"Nenalezeny žádné položky"},searching:function(){return"Vyhledávání…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/da",[],function(){return{errorLoading:function(){return"Resultaterne kunne ikke indlæses."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Angiv venligst "+t+" tegn mindre";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Angiv venligst "+t+" tegn mere";return n},loadingMore:function(){return"Indlæser flere resultater…"},maximumSelected:function(e){var t="Du kan kun vælge "+e.maximum+" emne";return e.maximum!=1&&(t+="r"),t},noResults:function(){return"Ingen resultater fundet"},searching:function(){return"Søger…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/de",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Bitte "+t+" Zeichen weniger eingeben"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Bitte "+t+" Zeichen mehr eingeben"},loadingMore:function(){return"Lade mehr Ergebnisse…"},maximumSelected:function(e){var t="Sie können nur "+e.maximum+" Eintr";return e.maximum===1?t+="ag":t+="äge",t+=" auswählen",t},noResults:function(){return"Keine Übereinstimmungen gefunden"},searching:function(){return"Suche…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/el",[],function(){return{errorLoading:function(){return"Τα αποτελέσματα δεν μπόρεσαν να φορτώσουν."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Παρακαλώ διαγράψτε "+t+" χαρακτήρ";return t==1&&(n+="α"),t!=1&&(n+="ες"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Παρακαλώ συμπληρώστε "+t+" ή περισσότερους χαρακτήρες";return n},loadingMore:function(){return"Φόρτωση περισσότερων αποτελεσμάτων…"},maximumSelected:function(e){var t="Μπορείτε να επιλέξετε μόνο "+e.maximum+" επιλογ";return e.maximum==1&&(t+="ή"),e.maximum!=1&&(t+="ές"),t},noResults:function(){return"Δεν βρέθηκαν αποτελέσματα"},searching:function(){return"Αναζήτηση…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Please delete "+t+" character";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Please enter "+t+" or more characters";return n},loadingMore:function(){return"Loading more results…"},maximumSelected:function(e){var t="You can only select "+e.maximum+" item";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No results found"},searching:function(){return"Searching…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/es",[],function(){return{errorLoading:function(){return"La carga falló"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor, elimine "+t+" car";return t==1?n+="ácter":n+="acteres",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Por favor, introduzca "+t+" car";return t==1?n+="ácter":n+="acteres",n},loadingMore:function(){return"Cargando más resultados…"},maximumSelected:function(e){var t="Sólo puede seleccionar "+e.maximum+" elemento";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No se encontraron resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/et",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" vähem",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" rohkem",n},loadingMore:function(){return"Laen tulemusi…"},maximumSelected:function(e){var t="Saad vaid "+e.maximum+" tulemus";return e.maximum==1?t+="e":t+="t",t+=" valida",t},noResults:function(){return"Tulemused puuduvad"},searching:function(){return"Otsin…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/eu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gutxiago",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gehiago",n},loadingMore:function(){return"Emaitza gehiago kargatzen…"},maximumSelected:function(e){return e.maximum===1?"Elementu bakarra hauta dezakezu":e.maximum+" elementu hauta ditzakezu soilik"},noResults:function(){return"Ez da bat datorrenik aurkitu"},searching:function(){return"Bilatzen…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fa",[],function(){return{errorLoading:function(){return"امکان بارگذاری نتایج وجود ندارد."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="لطفاً "+t+" کاراکتر را حذف نمایید";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="لطفاً تعداد "+t+" کاراکتر یا بیشتر وارد نمایید";return n},loadingMore:function(){return"در حال بارگذاری نتایج بیشتر..."},maximumSelected:function(e){var t="شما تنها میتوانید "+e.maximum+" آیتم را انتخاب نمایید";return t},noResults:function(){return"هیچ نتیجهای یافت نشد"},searching:function(){return"در حال جستجو..."}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fi",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Ole hyvä ja anna "+t+" merkkiä vähemmän"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Ole hyvä ja anna "+t+" merkkiä lisää"},loadingMore:function(){return"Ladataan lisää tuloksia…"},maximumSelected:function(e){return"Voit valita ainoastaan "+e.maximum+" kpl"},noResults:function(){return"Ei tuloksia"},searching:function(){}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fr",[],function(){return{errorLoading:function(){return"Les résultats ne peuvent pas être chargés."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Supprimez "+t+" caractère";return t!==1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Saisissez "+t+" caractère";return t!==1&&(n+="s"),n},loadingMore:function(){return"Chargement de résultats supplémentaires…"},maximumSelected:function(e){var t="Vous pouvez seulement sélectionner "+e.maximum+" élément";return e.maximum!==1&&(t+="s"),t},noResults:function(){return"Aucun résultat trouvé"},searching:function(){return"Recherche en cours…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/gl",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Elimine ";return t===1?n+="un carácter":n+=t+" caracteres",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Engada ";return t===1?n+="un carácter":n+=t+" caracteres",n},loadingMore:function(){return"Cargando máis resultados…"},maximumSelected:function(e){var t="Só pode ";return e.maximum===1?t+="un elemento":t+=e.maximum+" elementos",t},noResults:function(){return"Non se atoparon resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/he",[],function(){return{errorLoading:function(){return"שגיאה בטעינת התוצאות"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="נא למחוק ";return t===1?n+="תו אחד":n+=t+" תווים",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="נא להכניס ";return t===1?n+="תו אחד":n+=t+" תווים",n+=" או יותר",n},loadingMore:function(){return"טוען תוצאות נוספות…"},maximumSelected:function(e){var t="באפשרותך לבחור עד ";return e.maximum===1?t+="פריט אחד":t+=e.maximum+" פריטים",t},noResults:function(){return"לא נמצאו תוצאות"},searching:function(){return"מחפש…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hi",[],function(){return{errorLoading:function(){return"परिणामों को लोड नहीं किया जा सका।"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" अक्षर को हटा दें";return t>1&&(n=t+" अक्षरों को हटा दें "),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="कृपया "+t+" या अधिक अक्षर दर्ज करें";return n},loadingMore:function(){return"अधिक परिणाम लोड हो रहे है..."},maximumSelected:function(e){var t="आप केवल "+e.maximum+" आइटम का चयन कर सकते हैं";return t},noResults:function(){return"कोई परिणाम नहीं मिला"},searching:function(){return"खोज रहा है..."}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hr",[],function(){function e(e){var t=" "+e+" znak";return e%10<5&&e%10>0&&(e%100<5||e%100>19)?e%10>1&&(t+="a"):t+="ova",t}return{errorLoading:function(){return"Preuzimanje nije uspjelo."},inputTooLong:function(t){var n=t.input.length-t.maximum;return"Unesite "+e(n)},inputTooShort:function(t){var n=t.minimum-t.input.length;return"Unesite još "+e(n)},loadingMore:function(){return"Učitavanje rezultata…"},maximumSelected:function(e){return"Maksimalan broj odabranih stavki je "+e.maximum},noResults:function(){return"Nema rezultata"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return"Túl hosszú. "+t+" karakterrel több, mint kellene."},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Túl rövid. Még "+t+" karakter hiányzik."},loadingMore:function(){return"Töltés…"},maximumSelected:function(e){return"Csak "+e.maximum+" elemet lehet kiválasztani."},noResults:function(){return"Nincs találat."},searching:function(){return"Keresés…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/id",[],function(){return{errorLoading:function(){return"Data tidak boleh diambil."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Hapuskan "+t+" huruf"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Masukkan "+t+" huruf lagi"},loadingMore:function(){return"Mengambil data…"},maximumSelected:function(e){return"Anda hanya dapat memilih "+e.maximum+" pilihan"},noResults:function(){return"Tidak ada data yang sesuai"},searching:function(){return"Mencari…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/is",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vinsamlegast styttið texta um "+t+" staf";return t<=1?n:n+"i"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vinsamlegast skrifið "+t+" staf";return t>1&&(n+="i"),n+=" í viðbót",n},loadingMore:function(){return"Sæki fleiri niðurstöður…"},maximumSelected:function(e){return"Þú getur aðeins valið "+e.maximum+" atriði"},noResults:function(){return"Ekkert fannst"},searching:function(){return"Leita…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/it",[],function(){return{errorLoading:function(){return"I risultati non possono essere caricati."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Per favore cancella "+t+" caratter";return t!==1?n+="i":n+="e",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Per favore inserisci "+t+" o più caratteri";return n},loadingMore:function(){return"Caricando più risultati…"},maximumSelected:function(e){var t="Puoi selezionare solo "+e.maximum+" element";return e.maximum!==1?t+="i":t+="o",t},noResults:function(){return"Nessun risultato trovato"},searching:function(){return"Sto cercando…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ja",[],function(){return{errorLoading:function(){return"結果が読み込まれませんでした"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" 文字を削除してください";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="少なくとも "+t+" 文字を入力してください";return n},loadingMore:function(){return"読み込み中…"},maximumSelected:function(e){var t=e.maximum+" 件しか選択できません";return t},noResults:function(){return"対象が見つかりません"},searching:function(){return"検索しています…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/km",[],function(){return{errorLoading:function(){return"មិនអាចទាញយកទិន្នន័យ"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="សូមលុបចេញ "+t+" អក្សរ";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="សូមបញ្ចូល"+t+" អក្សរ រឺ ច្រើនជាងនេះ";return n},loadingMore:function(){return"កំពុងទាញយកទិន្នន័យបន្ថែម..."},maximumSelected:function(e){var t="អ្នកអាចជ្រើសរើសបានតែ "+e.maximum+" ជម្រើសប៉ុណ្ណោះ";return t},noResults:function(){return"មិនមានលទ្ធផល"},searching:function(){return"កំពុងស្វែងរក..."}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ko",[],function(){return{errorLoading:function(){return"결과를 불러올 수 없습니다."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="너무 깁니다. "+t+" 글자 지워주세요.";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="너무 짧습니다. "+t+" 글자 더 입력해주세요.";return n},loadingMore:function(){return"불러오는 중…"},maximumSelected:function(e){var t="최대 "+e.maximum+"개까지만 선택 가능합니다.";return t},noResults:function(){return"결과가 없습니다."},searching:function(){return"검색 중…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lt",[],function(){function e(e,t,n,r){return e%10===1&&(e%100<11||e%100>19)?t:e%10>=2&&e%10<=9&&(e%100<11||e%100>19)?n:r}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Pašalinkite "+n+" simbol";return r+=e(n,"į","ius","ių"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Įrašykite dar "+n+" simbol";return r+=e(n,"į","ius","ių"),r},loadingMore:function(){return"Kraunama daugiau rezultatų…"},maximumSelected:function(t){var n="Jūs galite pasirinkti tik "+t.maximum+" element";return n+=e(t.maximum,"ą","us","ų"),n},noResults:function(){return"Atitikmenų nerasta"},searching:function(){return"Ieškoma…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lv",[],function(){function e(e,t,n,r){return e===11?t:e%10===1?n:r}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Lūdzu ievadiet par "+n;return r+=" simbol"+e(n,"iem","u","iem"),r+" mazāk"},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Lūdzu ievadiet vēl "+n;return r+=" simbol"+e(n,"us","u","us"),r},loadingMore:function(){return"Datu ielāde…"},maximumSelected:function(t){var n="Jūs varat izvēlēties ne vairāk kā "+t.maximum;return n+=" element"+e(t.maximum,"us","u","us"),n},noResults:function(){return"Sakritību nav"},searching:function(){return"Meklēšana…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/mk",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Ве молиме внесете "+e.maximum+" помалку карактер";return e.maximum!==1&&(n+="и"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Ве молиме внесете уште "+e.maximum+" карактер";return e.maximum!==1&&(n+="и"),n},loadingMore:function(){return"Вчитување резултати…"},maximumSelected:function(e){var t="Можете да изберете само "+e.maximum+" ставк";return e.maximum===1?t+="а":t+="и",t},noResults:function(){return"Нема пронајдено совпаѓања"},searching:function(){return"Пребарување…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ms",[],function(){return{errorLoading:function(){return"Keputusan tidak berjaya dimuatkan."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Sila hapuskan "+t+" aksara"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Sila masukkan "+t+" atau lebih aksara"},loadingMore:function(){return"Sedang memuatkan keputusan…"},maximumSelected:function(e){return"Anda hanya boleh memilih "+e.maximum+" pilihan"},noResults:function(){return"Tiada padanan yang ditemui"},searching:function(){return"Mencari…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nb",[],function(){return{errorLoading:function(){return"Kunne ikke hente resultater."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Vennligst fjern "+t+" tegn"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vennligst skriv inn ";return t>1?n+=" flere tegn":n+=" tegn til",n},loadingMore:function(){return"Laster flere resultater…"},maximumSelected:function(e){return"Du kan velge maks "+e.maximum+" elementer"},noResults:function(){return"Ingen treff"},searching:function(){return"Søker…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nl",[],function(){return{errorLoading:function(){return"De resultaten konden niet worden geladen."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Gelieve "+t+" karakters te verwijderen";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Gelieve "+t+" of meer karakters in te voeren";return n},loadingMore:function(){return"Meer resultaten laden…"},maximumSelected:function(e){var t=e.maximum==1?"kan":"kunnen",n="Er "+t+" maar "+e.maximum+" item";return e.maximum!=1&&(n+="s"),n+=" worden geselecteerd",n},noResults:function(){return"Geen resultaten gevonden…"},searching:function(){return"Zoeken…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pl",[],function(){var e=["znak","znaki","znaków"],t=["element","elementy","elementów"],n=function(t,n){if(t===1)return n[0];if(t>1&&t<=4)return n[1];if(t>=5)return n[2]};return{errorLoading:function(){return"Nie można załadować wyników."},inputTooLong:function(t){var r=t.input.length-t.maximum;return"Usuń "+r+" "+n(r,e)},inputTooShort:function(t){var r=t.minimum-t.input.length;return"Podaj przynajmniej "+r+" "+n(r,e)},loadingMore:function(){return"Trwa ładowanie…"},maximumSelected:function(e){return"Możesz zaznaczyć tylko "+e.maximum+" "+n(e.maximum,t)},noResults:function(){return"Brak wyników"},searching:function(){return"Trwa wyszukiwanie…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt-BR",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Apague "+t+" caracter";return t!=1&&(n+="es"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Digite "+t+" ou mais caracteres";return n},loadingMore:function(){return"Carregando mais resultados…"},maximumSelected:function(e){var t="Você só pode selecionar "+e.maximum+" ite";return e.maximum==1?t+="m":t+="ns",t},noResults:function(){return"Nenhum resultado encontrado"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor apague "+t+" ";return n+=t!=1?"caracteres":"carácter",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Introduza "+t+" ou mais caracteres";return n},loadingMore:function(){return"A carregar mais resultados…"},maximumSelected:function(e){var t="Apenas pode seleccionar "+e.maximum+" ";return t+=e.maximum!=1?"itens":"item",t},noResults:function(){return"Sem resultados"},searching:function(){return"A procurar…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ro",[],function(){return{errorLoading:function(){return"Rezultatele nu au putut fi incărcate."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vă rugăm să ștergeți"+t+" caracter";return t!==1&&(n+="e"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vă rugăm să introduceți "+t+"sau mai multe caractere";return n},loadingMore:function(){return"Se încarcă mai multe rezultate…"},maximumSelected:function(e){var t="Aveți voie să selectați cel mult "+e.maximum;return t+=" element",e.maximum!==1&&(t+="e"),t},noResults:function(){return"Nu au fost găsite rezultate"},searching:function(){return"Căutare…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ru",[],function(){function e(e,t,n,r){return e%10<5&&e%10>0&&e%100<5||e%100>20?e%10>1?n:t:r}return{errorLoading:function(){return"Невозможно загрузить результаты"},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Пожалуйста, введите на "+n+" символ";return r+=e(n,"","a","ов"),r+=" меньше",r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Пожалуйста, введите еще хотя бы "+n+" символ";return r+=e(n,"","a","ов"),r},loadingMore:function(){return"Загрузка данных…"},maximumSelected:function(t){var n="Вы можете выбрать не более "+t.maximum+" элемент";return n+=e(t.maximum,"","a","ов"),n},noResults:function(){return"Совпадений не найдено"},searching:function(){return"Поиск…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sk",[],function(){var e={2:function(e){return e?"dva":"dve"},3:function(){return"tri"},4:function(){return"štyri"}};return{inputTooLong:function(t){var n=t.input.length-t.maximum;return n==1?"Prosím, zadajte o jeden znak menej":n>=2&&n<=4?"Prosím, zadajte o "+e[n](!0)+" znaky menej":"Prosím, zadajte o "+n+" znakov menej"},inputTooShort:function(t){var n=t.minimum-t.input.length;return n==1?"Prosím, zadajte ešte jeden znak":n<=4?"Prosím, zadajte ešte ďalšie "+e[n](!0)+" znaky":"Prosím, zadajte ešte ďalších "+n+" znakov"},loadingMore:function(){return"Loading more results…"},maximumSelected:function(t){return t.maximum==1?"Môžete zvoliť len jednu položku":t.maximum>=2&&t.maximum<=4?"Môžete zvoliť najviac "+e[t.maximum](!1)+" položky":"Môžete zvoliť najviac "+t.maximum+" položiek"},noResults:function(){return"Nenašli sa žiadne položky"},searching:function(){return"Vyhľadávanie…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sr-Cyrl",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Преузимање није успело."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Обришите "+n+" симбол";return r+=e(n,"","а","а"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Укуцајте бар још "+n+" симбол";return r+=e(n,"","а","а"),r},loadingMore:function(){return"Преузимање још резултата…"},maximumSelected:function(t){var n="Можете изабрати само "+t.maximum+" ставк";return n+=e(t.maximum,"у","е","и"),n},noResults:function(){return"Ништа није пронађено"},searching:function(){return"Претрага…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sr",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Preuzimanje nije uspelo."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Obrišite "+n+" simbol";return r+=e(n,"","a","a"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Ukucajte bar još "+n+" simbol";return r+=e(n,"","a","a"),r},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(t){var n="Možete izabrati samo "+t.maximum+" stavk";return n+=e(t.maximum,"u","e","i"),n},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sv",[],function(){return{errorLoading:function(){return"Resultat kunde inte laddas."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vänligen sudda ut "+t+" tecken";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vänligen skriv in "+t+" eller fler tecken";return n},loadingMore:function(){return"Laddar fler resultat…"},maximumSelected:function(e){var t="Du kan max välja "+e.maximum+" element";return t},noResults:function(){return"Inga träffar"},searching:function(){return"Söker…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/th",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="โปรดลบออก "+t+" ตัวอักษร";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="โปรดพิมพ์เพิ่มอีก "+t+" ตัวอักษร";return n},loadingMore:function(){return"กำลังค้นข้อมูลเพิ่ม…"},maximumSelected:function(e){var t="คุณสามารถเลือกได้ไม่เกิน "+e.maximum+" รายการ";return t},noResults:function(){return"ไม่พบข้อมูล"},searching:function(){return"กำลังค้นข้อมูล…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/tr",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" karakter daha girmelisiniz";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="En az "+t+" karakter daha girmelisiniz";return n},loadingMore:function(){return"Daha fazla…"},maximumSelected:function(e){var t="Sadece "+e.maximum+" seçim yapabilirsiniz";return t},noResults:function(){return"Sonuç bulunamadı"},searching:function(){return"Aranıyor…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/uk",[],function(){function e(e,t,n,r){return e%100>10&&e%100<15?r:e%10===1?t:e%10>1&&e%10<5?n:r}return{errorLoading:function(){return"Неможливо завантажити результати"},inputTooLong:function(t){var n=t.input.length-t.maximum;return"Будь ласка, видаліть "+n+" "+e(t.maximum,"літеру","літери","літер")},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Будь ласка, введіть "+t+" або більше літер"},loadingMore:function(){return"Завантаження інших результатів…"},maximumSelected:function(t){return"Ви можете вибрати лише "+t.maximum+" "+e(t.maximum,"пункт","пункти","пунктів")},noResults:function(){return"Нічого не знайдено"},searching:function(){return"Пошук…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/vi",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vui lòng nhập ít hơn "+t+" ký tự";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vui lòng nhập nhiều hơn "+t+' ký tự"';return n},loadingMore:function(){return"Đang lấy thêm kết quả…"},maximumSelected:function(e){var t="Chỉ có thể chọn được "+e.maximum+" lựa chọn";return t},noResults:function(){return"Không tìm thấy kết quả"},searching:function(){return"Đang tìm…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/zh-CN",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="请删除"+t+"个字符";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="请再输入至少"+t+"个字符";return n},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(e){var t="最多只能选择"+e.maximum+"个项目";return t},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,3 @@
|
|||
/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */
|
||||
|
||||
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/zh-TW",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="請刪掉"+t+"個字元";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="請再輸入"+t+"個字元";return n},loadingMore:function(){return"載入中…"},maximumSelected:function(e){var t="你只能選擇最多"+e.maximum+"項";return t},noResults:function(){return"沒有找到相符的項目"},searching:function(){return"搜尋中…"}}}),{define:e.define,require:e.require}})();
|
|
@ -0,0 +1,217 @@
|
|||
/* Write your custom CSS here */
|
||||
|
||||
/*no pad style*/
|
||||
.no-padding {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
border: 0px;
|
||||
-webkit-box-shadow: 0;
|
||||
box-shadow: 0;
|
||||
}
|
||||
|
||||
/* status related color row */
|
||||
table .basket_status_price_ready {
|
||||
color: #eb3c6a;
|
||||
}
|
||||
|
||||
table .basket_status_priced {
|
||||
color: #2bab2b;
|
||||
}
|
||||
|
||||
table .basket_status_delivered {
|
||||
color: #2bab2b;
|
||||
}
|
||||
|
||||
table .basket_status_canceled {
|
||||
color: #ababab;
|
||||
}
|
||||
|
||||
table .basket_status_refund_after_adjust {
|
||||
color: #ababab;
|
||||
}
|
||||
|
||||
table .basket_status_refund_before_adjust {
|
||||
color: #ababab;
|
||||
}
|
||||
|
||||
table .upload_status_initial {
|
||||
color: #eb3c6a;
|
||||
}
|
||||
|
||||
table .upload_status_handled {
|
||||
color: #2bab2b;
|
||||
}
|
||||
|
||||
table .upload_status_discarded {
|
||||
color: #ababab;
|
||||
}
|
||||
|
||||
/* label related style */
|
||||
|
||||
.basket-item-grid table .net_price {
|
||||
white-space: nowrap;
|
||||
background-color: #eb3c6a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.basket-item-grid table .rest_area {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.basket-item-grid table .rest_area dl {
|
||||
display: inline-block;
|
||||
min-width: 100px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.basket-item-grid table dt {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.basket-item-grid table dd {
|
||||
text-align: right;
|
||||
font-size: 15pt;
|
||||
}
|
||||
|
||||
.basket-item-grid table .net_price a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.basket-confirm-bar-widget .btn-pricing,
|
||||
.basket-confirm-bar-widget .btn-delivering {
|
||||
background-color: #eb3c6a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.upload-confirm-bar-widget .btn-handle {
|
||||
background-color: #eb3c6a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.header-status-progress .nav-item.active .nav-link {
|
||||
color: #eb3c6a;
|
||||
background: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.company-description .important-info * {
|
||||
font-size: 27px;
|
||||
}
|
||||
|
||||
.positive * {
|
||||
color: #2bab2b;
|
||||
}
|
||||
|
||||
.negative * {
|
||||
color: #eb3c6a;
|
||||
}
|
||||
|
||||
.card-positive {
|
||||
background: #2bab2b;
|
||||
}
|
||||
|
||||
.card-positive .card-header {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.card-positive .card-block {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.card-positive .card-header .card-control a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.card-positive .card-header .card-control a:hover {
|
||||
color: #f1f1f1;
|
||||
}
|
||||
|
||||
.tx-registration-bar .btn-registration {
|
||||
background-color: #eb3c6a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.image-cell .preview {
|
||||
display: none;
|
||||
position: absolute;
|
||||
border: 1px solid #000;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
/*justified-gallery figure css definition*/
|
||||
.justified-gallery > figure {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
/* background: #888888; To have gray placeholders while the gallery is loading with waitThumbnailsLoad = false */
|
||||
filter: "alpha(opacity=10)";
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.justified-gallery > figure > img,
|
||||
.justified-gallery > figure > a > img {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
filter: "alpha(opacity=0)";
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.justified-gallery > figure > .caption {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding: 5px;
|
||||
background-color: #000000;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.justified-gallery > figure > .caption.caption-visible {
|
||||
display: initial;
|
||||
filter: "alpha(opacity=70)";
|
||||
opacity: 0.7;
|
||||
-webkit-transition: opacity 500ms ease-in;
|
||||
-moz-transition: opacity 500ms ease-in;
|
||||
-o-transition: opacity 500ms ease-in;
|
||||
transition: opacity 500ms ease-in;
|
||||
}
|
||||
|
||||
/*justified-gallery figure css definition end */
|
||||
|
||||
/* PhotoSwipe preview load glitch */
|
||||
.pswp__img {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.top-menu .navbar-nav .nav-item + .nav-item {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.top-menu.collapse.show {
|
||||
background-color: #fff;
|
||||
}
|
||||
button.navbar-toggler{
|
||||
line-height: 60px;
|
||||
padding: 0 15px 0 15px;
|
||||
vertical-align: middle;
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
right: 0rem;
|
||||
}
|
||||
.dropdown-toggle::after {
|
||||
content: none;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<% if (title) { %>
|
||||
<div class="modal-header">
|
||||
<% if (allowCancel) { %>
|
||||
<a class="close">×</a>
|
||||
<% } %>
|
||||
<h4><%= title %></h4>
|
||||
</div>
|
||||
<% } %>
|
||||
<div class="modal-body"><%= content %></div>
|
||||
<% if (showFooter) { %>
|
||||
<div class="modal-footer">
|
||||
<% if (allowCancel &&cancelText) { %>
|
||||
<a href="javascript:void(0);" class="btn cancel btn-default"><%= cancelText %></a>
|
||||
<% } %>
|
||||
<a href="javascript:void(0);" class="btn ok btn-success"><%= okText %></a>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,77 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!-- Title -->
|
||||
<title>In-house service</title>
|
||||
<meta http-equiv="Content-type" content="text/html">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport"
|
||||
content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width">
|
||||
<meta name="description" content="we have been able to make amazing things.">
|
||||
<meta name="author" content="Sangbum Kim">
|
||||
<meta name="keywords">
|
||||
<!-- IE6-10 -->
|
||||
<link rel="shortcut icon" href="./static/images/favicon.ico'?v=1">
|
||||
<!-- Everybody else -->
|
||||
<link rel="icon" sizes="16x16 24x24 32x32 48x48 64x64"
|
||||
href="./static/images/favicon.ico?v=1">
|
||||
<!-- ms tile favicon -->
|
||||
<meta name="msapplication-TileColor" content="#e74e1c">
|
||||
<meta name="msapplication-TileImage" content="./static/images/favicon.144.png?v=1">
|
||||
<!-- apple touch icon -->
|
||||
<link rel="apple-touch-icon-precomposed" href="./.static/images/favicon.152.png?v=1">
|
||||
<!-- chrome frame theme -->
|
||||
<meta name="theme-color" content="#e74e1c">
|
||||
<!-- ios web app fullscreen -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
|
||||
<!-- android web app fullscreen -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<!-- initial font loading -->
|
||||
<script>
|
||||
'use strict';
|
||||
var WebFontConfig = {
|
||||
custom: {
|
||||
families: ['Spoqa Han Sans:100,300,400,700', 'FontAwesome'],
|
||||
urls: [
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/spoqa-han-sans/2.1.0/css/SpoqaHanSans-kr.css',
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'
|
||||
]
|
||||
},
|
||||
timeout: 60000,
|
||||
|
||||
active: function () {
|
||||
sessionStorage.fonts = true;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script type="text/javascript"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/webfont/1.6.27/webfontloader.js"></script>
|
||||
{% block style_area %}
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="static/stylesheets/common/modern.css">
|
||||
<link rel="stylesheet" href="static/stylesheets/common/custom.css">
|
||||
<script type="text/javascript" src="static/javascripts/common/dep.js"></script>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
var require = dep("static");
|
||||
</script>
|
||||
<script type="text/javascript"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.2/require.min.js"></script>
|
||||
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
<body class="page-header-fixed page-horizontal-bar">
|
||||
{% block body_area %}{% endblock %}
|
||||
{% block script_area %}
|
||||
<script>
|
||||
require(['javascripts/common/initial']);
|
||||
</script>
|
||||
{% endblock %}
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,83 @@
|
|||
{% extends "/layout/base.html" %} {% block body_area %}
|
||||
|
||||
<!-- Search Form -->
|
||||
<main class="page-content content-wrap ">
|
||||
<nav class="navbar navbar-toggleable-sm bg-faded">
|
||||
<div class="logo-box">
|
||||
<a class="logo-text" href="./">
|
||||
<i class="fa fa-gamepad" aria-hidden="true"></i>
|
||||
|
||||
<span>in-house</span></a>
|
||||
</div>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse"
|
||||
data-target="#gnb" aria-controls="gnb" aria-expanded="false"
|
||||
aria-label="Toggle navigation">
|
||||
<i class="fa fa-bars" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="top-menu collapse navbar-collapse" id="gnb">
|
||||
<ul class="nav navbar-nav mr-auto navbar-left">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="./" title="홈">
|
||||
<span>
|
||||
<i class="menu-icon fa fa-home"></i>홈
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="./change_password" title="비밀번호 변경">
|
||||
<span>
|
||||
<i class="menu-icon fa fa-key"></i>비밀번호 변경
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav mr-auto navbar-right">
|
||||
<li class="nav-item">
|
||||
{% if user_role=="ROLE_ANONYMOUS"%}
|
||||
<a href="./login"
|
||||
class="log-out nav-link ">
|
||||
<span><i class="fa fa-sign-out m-r-xs"></i>Log in</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="./logout"
|
||||
class="log-in nav-link">
|
||||
<span><i class="fa fa-sign-in m-r-xs"></i>Log out</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Nav -->
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Page Sidebar -->
|
||||
<div class="page-inner">
|
||||
<div class="page-title">
|
||||
<div class="row">
|
||||
<div class="col-md-5 col-sm-4 col-xs-7">
|
||||
<h3 class="page-name">{{ title }}</h3>
|
||||
<div class="page-breadcrumb">
|
||||
{% block breadcrumb %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-7 col-sm-8 col-xs-5">
|
||||
{% block extra_head_content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="main-wrapper">
|
||||
{% block content_area %}{% endblock %}
|
||||
<!-- Row -->
|
||||
</div>
|
||||
|
||||
{% block footer_area %} {% endblock %}
|
||||
<!-- Main Wrapper -->
|
||||
<div class="page-footer">
|
||||
<p class="no-s">2017 © Sangbum Kim.</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page Inner -->
|
||||
</main>
|
||||
<!-- Page Content -->
|
||||
{% endblock %}
|
|
@ -0,0 +1,116 @@
|
|||
{% extends "base.html" %}
|
||||
{% block script_area %}
|
||||
{{ block.Super|safe }}
|
||||
|
||||
<script type="text/javascript">
|
||||
require([
|
||||
'jquery',
|
||||
'underscore',
|
||||
'underscore.string',
|
||||
'toastr',
|
||||
'javascripts/common/blockui'
|
||||
], function ($,
|
||||
_,
|
||||
_s,
|
||||
toastr,
|
||||
blockui) {
|
||||
"use strict";
|
||||
$(".submit_change_password").on("click", function (event) {
|
||||
var form = $(event.target).closest("form");
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
var data = _.chain(form)
|
||||
.result('serializeArray', [])
|
||||
.map(function (n) {
|
||||
return [n.name, n.value];
|
||||
})
|
||||
.object()
|
||||
.value();
|
||||
blockui.blockUI(form);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: location.pathname,
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify(data),
|
||||
success: function (responseText, statusText) {
|
||||
toastr['success']('변경완료.');
|
||||
},
|
||||
error: function (response) {
|
||||
var errObj, message;
|
||||
message = '변경오류';
|
||||
if (!_.has(response, 'responseJSON')) {
|
||||
|
||||
} else {
|
||||
errObj = _.property('responseJSON')(response);
|
||||
if (_.has(errObj, 'message')) {
|
||||
message = message + '<br/>' + errObj.message;
|
||||
}
|
||||
}
|
||||
toastr['error'](message);
|
||||
},
|
||||
complete: function () {
|
||||
form[0].reset();
|
||||
blockui.unblockUI(form);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
{% block content_area %}
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2 mx-auto change_password_form">
|
||||
<form onsubmit="javascript:void(0);" class="form-horizontal">
|
||||
<div class="card card-white">
|
||||
<div class="card-block">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 form-control-label">ID</label>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<p class="form-control-static">{{user_id}}</p>
|
||||
<p class="help-block"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 form-control-label">이전비밀번호</label>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<input type="password" name="old_password" class="form-control" placeholder="입력해주세요"
|
||||
required="true">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 form-control-label">비밀번호</label>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<input type="password" name="password" class="form-control" placeholder="입력해주세요"
|
||||
required="true">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 form-control-label">비밀번호확인</label>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<input type="password" name="password_confirm" class="form-control" placeholder="입력해주세요"
|
||||
required="true">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-block">
|
||||
<div class="row">
|
||||
<div class="col-md-12 confirm-bar">
|
||||
<span class="float-right">
|
||||
<button type="button" class="submit_change_password btn btn-primary btn-lg">변경</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,17 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content_area %}
|
||||
<div class="row">
|
||||
<div class="col-md-4 center">
|
||||
<h1 class="text-xxl text-primary text-center"><i class="{{ icon }}" aria-hidden="true"></i>{{ code }}
|
||||
</h1>
|
||||
<div class="details">
|
||||
<h3>{{ message }}</h3>
|
||||
<p>오류가 발생하거나 허용되지않은 동작입니다.</p>
|
||||
<p>뒤로 가시거나
|
||||
<a href="./">
|
||||
<i class="fa fa-cogs" aria-hidden="true"></i>홈</a>으로 이동해주세요.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Row -->
|
||||
{% endblock %}
|
|
@ -0,0 +1,14 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content_area %}
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-md-offset-4 mx-auto">
|
||||
<div class="card text-center">
|
||||
<div class="card-block">
|
||||
<h4 class="card-title"><i class="fa fa-wrench
|
||||
" aria-hidden="true" style="font-size:12em"></i></h4>
|
||||
<p class="card-text">홈홈</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,111 @@
|
|||
{% extends "base.html" %}
|
||||
{% block script_area %}
|
||||
{{ block.Super|safe }}
|
||||
|
||||
<script type="text/javascript">
|
||||
require([
|
||||
'jquery',
|
||||
'underscore',
|
||||
'underscore.string',
|
||||
'toastr',
|
||||
'javascripts/common/blockui'
|
||||
], function ($,
|
||||
_,
|
||||
_s,
|
||||
toastr,
|
||||
blockui) {
|
||||
"use strict";
|
||||
$('.login_form form').on("submit", function (event) {
|
||||
var form = $(event.target).closest("form");
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
var data = _.chain(form)
|
||||
.result('serializeArray', [])
|
||||
.map(function (n) {
|
||||
return [n.name, n.value];
|
||||
})
|
||||
.object()
|
||||
.value();
|
||||
blockui.blockUI(form);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: location.pathname,
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify(data),
|
||||
success: function (response, statusText) {
|
||||
var respObj, message, redirectTo = "./";
|
||||
message = '로그인 완료!';
|
||||
if (_.has(response, 'responseJSON')) {
|
||||
respObj = _.property('responseJSON')(response);
|
||||
if (_.has(respObj, 'message')) {
|
||||
message = message + '<br/>' + respObj.message;
|
||||
}
|
||||
if (_.has(respObj, 'redirectTo')) {
|
||||
redirectTo = respObj.message;
|
||||
}
|
||||
}
|
||||
toastr['success'](message);
|
||||
document.location = redirectTo;
|
||||
},
|
||||
error: function (response) {
|
||||
var errObj, message;
|
||||
message = '로그인 오류';
|
||||
if (!_.has(response, 'responseJSON')) {
|
||||
|
||||
} else {
|
||||
errObj = _.property('responseJSON')(response);
|
||||
if (_.has(errObj, 'message')) {
|
||||
message = message + '<br/>' + errObj.message;
|
||||
}
|
||||
}
|
||||
toastr['error'](message);
|
||||
},
|
||||
complete: function () {
|
||||
form[0].reset();
|
||||
blockui.unblockUI(form);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
{% block content_area %}
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2 mx-auto login_form">
|
||||
<form onsubmit="javascript:void(0);" class="form-horizontal">
|
||||
<div class="card card-white">
|
||||
<div class="card-block">
|
||||
<input type="hidden" name="redirect_to" value="{{redirectTo}}">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 form-control-label">ID</label>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<input type="text" name="id" class="form-control" placeholder="입력해주세요" required="true">
|
||||
<p class="help-block"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 form-control-label">비밀번호</label>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<input type="password" name="password" class="form-control" placeholder="입력해주세요"
|
||||
required="true">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-block">
|
||||
<div class="row">
|
||||
<div class="col-md-12 confirm-bar">
|
||||
<span class="float-right">
|
||||
<button type="submit" onclick="javascript:void(0);" class="submit_login btn btn-primary btn-lg">로그인</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
|
||||
def report(c, lines):
|
||||
rtv = []
|
||||
for str in lines:
|
||||
str=re.sub("^[ \t]+",'', str)
|
||||
if str.find("Latency") == 0 or str.find("Requests/sec") == 0:
|
||||
token = re.split("[ \t]+", str)
|
||||
value = re.sub("[^0-9\.]", '', token[1])
|
||||
if re.match("[0-9\.]+us", token[1]):
|
||||
rtv.append(float(value)/1000)
|
||||
elif re.match("[0-9\.]+s", token[1]):
|
||||
rtv.append(float(value)*1000)
|
||||
else:
|
||||
rtv.append(float(value))
|
||||
print c,"\t", rtv[0],"\t",rtv[1]
|
||||
|
||||
def run(cmd):
|
||||
stdin, stdout, stderr = os.popen3(cmd)
|
||||
return stdout.readlines()
|
||||
|
||||
offset = 5
|
||||
maxConcurrency = offset * 61
|
||||
duration = 10
|
||||
for num in range(1, maxConcurrency/offset):
|
||||
concurrency = num * offset
|
||||
thread = 16 if concurrency > 16 else concurrency
|
||||
cmd = (
|
||||
'''wrk --timeout 7 -t %d -c %d -d %d http://localhost:8080/api/v1/media/admin/2/thumb'''
|
||||
% (thread, concurrency, duration))
|
||||
result =run(cmd)
|
||||
report(concurrency, result)
|
||||
time.sleep(5)
|
|
@ -0,0 +1,28 @@
|
|||
[Unit]
|
||||
Description=Inhouse internal user mgnt server
|
||||
Documentation=https://amuz.es
|
||||
After=syslog.target network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
Restart=always
|
||||
RestartSec=15
|
||||
ExecStart=/usr/bin/changer -C /etc/changer/settings.yml -L /var/logs/changer
|
||||
WorkingDirectory=/var/logs/changer
|
||||
#ExecReload=/bin/kill -s HUP $MAINPID
|
||||
TimeoutStartSec=5
|
||||
|
||||
|
||||
# Disable timeout logic and wait until process is stopped
|
||||
TimeoutStopSec=0
|
||||
# SIGTERM signal is used to stop Minio
|
||||
KillSignal=SIGTERM
|
||||
SendSIGKILL=no
|
||||
|
||||
SuccessExitStatus=0
|
||||
# kill only the docker process, not all processes in the cgroup
|
||||
KillMode=process
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
|
@ -0,0 +1,47 @@
|
|||
package enums
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/mailru/easyjson/jlexer"
|
||||
"github.com/mailru/easyjson/jwriter"
|
||||
)
|
||||
|
||||
type UserType string
|
||||
|
||||
const (
|
||||
UserTypeAnon UserType = "ROLE_ANONYMOUS"
|
||||
UserTypeUser UserType = "ROLE_USER"
|
||||
UserTypeAdmin UserType = "ROLE_ADMIN"
|
||||
)
|
||||
|
||||
func UserTypeNameOf(value string) (UserType, error) {
|
||||
switch value {
|
||||
case "ROLE_USER":
|
||||
return UserTypeUser, nil
|
||||
case "ROLE_ADMIN":
|
||||
return UserTypeAdmin, nil
|
||||
}
|
||||
return UserTypeAnon, errors.New("unable to find UserType")
|
||||
}
|
||||
|
||||
func (status UserType) Name() string {
|
||||
return string(status)
|
||||
}
|
||||
|
||||
func (status UserType) String() string {
|
||||
switch status {
|
||||
case UserTypeUser:
|
||||
return "사용자"
|
||||
case UserTypeAdmin:
|
||||
return "관리자"
|
||||
}
|
||||
return "로그인 안함"
|
||||
}
|
||||
|
||||
func (status UserType) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
w.String(string(status))
|
||||
}
|
||||
func (status *UserType) UnmarshalEasyJSON(w *jlexer.Lexer) {
|
||||
*status, _ = UserTypeNameOf(w.String())
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package iface
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
//turns a new initialized CounterMetric object.
|
||||
func NewCounterMetric() *CounterMetric {
|
||||
ca := &CounterMetric{}
|
||||
ca.Inc = make(chan Tuple)
|
||||
ca.internalRequestsSum = 0
|
||||
ca.internalRequests = make(map[string]uint64, 0)
|
||||
ca.internalRequestCodes = make(map[string]uint64, 0)
|
||||
return ca
|
||||
}
|
||||
|
||||
// StartTimer will call a forever loop in a goroutine to calculate
|
||||
// metrics for measurements every d ticks. The parameter of this
|
||||
// function should normally be 1 * time.Minute, if not it will expose
|
||||
// unintuive JSON keys (requests_per_minute and
|
||||
// request_sum_per_minute).
|
||||
func (ca *CounterMetric) StartTimer(d time.Duration) {
|
||||
timer := time.Tick(d)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case tup := <-ca.Inc:
|
||||
ca.internalRequestsSum++
|
||||
ca.internalRequests[tup.Path]++
|
||||
ca.internalRequestCodes[strconv.FormatInt(int64(tup.Code), 10)]++
|
||||
case <-timer:
|
||||
ca.reset()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// GetStats to fulfill aspects.Aspect interface, it returns the data
|
||||
// that will be served as JSON.
|
||||
func (ca *CounterMetric) GetStats() interface{} {
|
||||
return *ca
|
||||
}
|
||||
|
||||
// Name to fulfill aspects.Aspect interface, it will return the name
|
||||
// of the JSON object that will be served.
|
||||
func (ca *CounterMetric) Name() string {
|
||||
return "Counter"
|
||||
}
|
||||
|
||||
// InRoot to fulfill aspects.Aspect interface, it will return where to
|
||||
// put the JSON object into the monitoring endpoint.
|
||||
func (ca *CounterMetric) InRoot() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (ca *CounterMetric) reset() {
|
||||
ca.RequestsSum = ca.internalRequestsSum
|
||||
ca.Requests = ca.internalRequests
|
||||
ca.RequestCodes = ca.internalRequestCodes
|
||||
ca.internalRequestsSum = 0
|
||||
ca.internalRequests = make(map[string]uint64, ca.RequestsSum)
|
||||
ca.internalRequestCodes = make(map[string]uint64, len(ca.RequestCodes))
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package iface
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RequestDurationAggr struct {
|
||||
lastMinuteRequestTimes []float64
|
||||
Min float64 `json:"min"`
|
||||
Max float64 `json:"max"`
|
||||
Mean float64 `json:"mean"`
|
||||
Stdev float64 `json:"stdev"`
|
||||
P90 float64 `json:"p90"`
|
||||
P95 float64 `json:"p95"`
|
||||
P99 float64 `json:"p99"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// NewRequestDurationAggr returns a new initialized RequestDurationAggr
|
||||
// object.
|
||||
func NewRequestDurationAggr() *RequestDurationAggr {
|
||||
rt := &RequestDurationAggr{}
|
||||
rt.lastMinuteRequestTimes = make([]float64, 0)
|
||||
rt.Timestamp = time.Now()
|
||||
return rt
|
||||
}
|
||||
|
||||
// StartTimer will call a forever loop in a goroutine to calculate
|
||||
// metrics for measurements every d ticks.
|
||||
func (rt *RequestDurationAggr) StartTimer(d time.Duration) {
|
||||
timer := time.Tick(d)
|
||||
go func() {
|
||||
for {
|
||||
<-timer
|
||||
rt.calculate()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// GetStats to fulfill Metrics.Metric interface, it returns the data
|
||||
// that will be served as JSON.
|
||||
func (rt *RequestDurationAggr) GetStats() interface{} {
|
||||
return rt
|
||||
}
|
||||
|
||||
// Name to fulfill Metrics.Metric interface, it will return the name
|
||||
// of the JSON object that will be served.
|
||||
func (rt *RequestDurationAggr) Name() string {
|
||||
return "RequestTime"
|
||||
}
|
||||
|
||||
// InRoot to fulfill Metrics.Metric interface, it will return where to
|
||||
// put the JSON object into the monitoring endpoint.
|
||||
func (rt *RequestDurationAggr) InRoot() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (rt *RequestDurationAggr) Add(n float64) {
|
||||
rt.lastMinuteRequestTimes = append(rt.lastMinuteRequestTimes, n)
|
||||
}
|
||||
|
||||
func (rt *RequestDurationAggr) calculate() {
|
||||
sortedSlice := rt.lastMinuteRequestTimes[:]
|
||||
rt.lastMinuteRequestTimes = make([]float64, 0)
|
||||
l := len(sortedSlice)
|
||||
if l <= 1 {
|
||||
return
|
||||
}
|
||||
sort.Float64s(sortedSlice)
|
||||
|
||||
rt.Timestamp = time.Now()
|
||||
rt.Min = sortedSlice[0]
|
||||
rt.Max = sortedSlice[l - 1]
|
||||
rt.Mean = mean(sortedSlice, l)
|
||||
rt.Stdev = correctedStdev(sortedSlice, rt.Mean, l)
|
||||
rt.P90 = p90(sortedSlice, l)
|
||||
rt.P95 = p95(sortedSlice, l)
|
||||
rt.P99 = p99(sortedSlice, l)
|
||||
}
|
||||
|
||||
func mean(orderedObservations []float64, l int) float64 {
|
||||
res := 0.0
|
||||
for i := 0; i < l; i++ {
|
||||
res += orderedObservations[i]
|
||||
}
|
||||
|
||||
return res / float64(l)
|
||||
}
|
||||
|
||||
func p90(orderedObservations []float64, l int) float64 {
|
||||
return percentile(orderedObservations, l, 0.9)
|
||||
}
|
||||
|
||||
func p95(orderedObservations []float64, l int) float64 {
|
||||
return percentile(orderedObservations, l, 0.95)
|
||||
}
|
||||
|
||||
func p99(orderedObservations []float64, l int) float64 {
|
||||
return percentile(orderedObservations, l, 0.99)
|
||||
}
|
||||
|
||||
// percentile with argument p \in (0,1), l is the length of given orderedObservations
|
||||
// It does a simple apporximation of an ordered list of observations.
|
||||
// Formula: sortedSlice[0.95*length(sortedSlice)]
|
||||
func percentile(orderedObservations []float64, l int, p float64) float64 {
|
||||
return orderedObservations[int(p * float64(l))]
|
||||
}
|
||||
|
||||
func correctedStdev(observations []float64, mean float64, l int) float64 {
|
||||
var omega float64
|
||||
for i := 0; i < l; i++ {
|
||||
omega += math.Pow(observations[i]-mean, 2)
|
||||
}
|
||||
stdev := math.Sqrt(1 / (float64(l) - 1) * omega)
|
||||
return stdev
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
//go:generate ${GOPATH}/bin/easyjson $GOFILE
|
||||
package iface
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
|
||||
"amuz.es/gogs/infra/changer/enums"
|
||||
)
|
||||
|
||||
//easyjson:json
|
||||
type Login struct {
|
||||
Id string `form:"id" json:"id" binding:"required"`
|
||||
Password string `form:"password" json:"password" binding:"required"`
|
||||
RedirectTo string `form:"redirect_to" json:"redirect_to,omitempty"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type ChangePassword struct {
|
||||
OldPassword string `form:"old_password" json:"old_password" binding:"required"`
|
||||
Password string `form:"password" json:"password" binding:"required"`
|
||||
PasswordConfirm string `form:"password_confirm" json:"password_confirm" binding:"required"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type Token struct {
|
||||
Token string `json:"token"`
|
||||
Expire string `json:"expire"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type LoginResponse struct {
|
||||
Message string `json:"message"`
|
||||
RedirectTo string `json:"redirectTo,omitempty"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type WrappedResponse struct {
|
||||
Message string `json:"message"`
|
||||
Content interface{} `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type NewId struct {
|
||||
Id interface{} `json:"id" binding:"required"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type NameCodePair struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type KeyValuePair struct {
|
||||
Key string `json:"key" binding:"required"`
|
||||
Value string `json:"value" binding:"required"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type Tuple struct {
|
||||
Path string
|
||||
Code int
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type CounterMetric struct {
|
||||
Inc chan Tuple `json:"-"`
|
||||
internalRequestsSum uint64
|
||||
internalRequests map[string]uint64
|
||||
internalRequestCodes map[string]uint64
|
||||
RequestsSum uint64 `json:"request_sum_per_minute"`
|
||||
Requests map[string]uint64 `json:"requests_per_minute"`
|
||||
RequestCodes map[string]uint64 `json:"request_codes_per_minute"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type DurationMetricReadable struct {
|
||||
Min string `json:"min"`
|
||||
Max string `json:"max"`
|
||||
Mean string `json:"mean"`
|
||||
Stdev string `json:"stdev"`
|
||||
P90 string `json:"p90"`
|
||||
P95 string `json:"p95"`
|
||||
P99 string `json:"p99"`
|
||||
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type DurationMetric struct {
|
||||
Min uint64 `json:"min"`
|
||||
Max uint64 `json:"max"`
|
||||
Mean uint64 `json:"mean"`
|
||||
Stdev uint64 `json:"stdev"`
|
||||
P90 uint64 `json:"p90"`
|
||||
P95 uint64 `json:"p95"`
|
||||
P99 uint64 `json:"p99"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type RequestMetricReadable struct {
|
||||
Duration DurationMetricReadable `json:"duration"`
|
||||
Count CounterMetric `json:"count"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type RequestMetric struct {
|
||||
Duration DurationMetric `json:"duration"`
|
||||
Count CounterMetric `json:"count"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type RuntimeMetric struct {
|
||||
GoVersion string `json:"go_version"`
|
||||
GoOs string `json:"go_os"`
|
||||
GoArch string `json:"go_arch"`
|
||||
CpuNum int `json:"cpu_num"`
|
||||
GoroutineNum int `json:"goroutine_num"`
|
||||
Gomaxprocs int `json:"go_maxprocs"`
|
||||
CgoCallNum int64 `json:"cgo_call_num"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type MemoryMetric struct {
|
||||
MemAllocated uint64 `json:"mem_allocated"`
|
||||
MemTotal uint64 `json:"mem_total"`
|
||||
MemSys uint64 `json:"mem_sys"`
|
||||
Lookups uint64 `json:"lookups"`
|
||||
MemMallocs uint64 `json:"mem_mallocs"`
|
||||
MemFrees uint64 `json:"mem_frees"`
|
||||
|
||||
HeapAlloc uint64 `json:"heap_alloc"`
|
||||
HeapSys uint64 `json:"heap_sys"`
|
||||
HeapIdle uint64 `json:"heap_idle"`
|
||||
HeapInuse uint64 `json:"heap_inuse"`
|
||||
HeapReleased uint64 `json:"heap_released"`
|
||||
HeapObjects uint64 `json:"heap_objects"`
|
||||
|
||||
StackInuse uint64 `json:"stack_inuse"`
|
||||
StackSys uint64 `json:"stack_sys"`
|
||||
MSpanInuse uint64 `json:"m_span_inuse"`
|
||||
MSpanSys uint64 `json:"m_span_sys"`
|
||||
MCacheInuse uint64 `json:"m_cache_inuse"`
|
||||
MCacheSys uint64 `json:"m_cache_sys"`
|
||||
BuckHashSys uint64 `json:"buck_hash_sys"`
|
||||
GCSys uint64 `json:"gc_sys"`
|
||||
OtherSys uint64 `json:"other_sys"`
|
||||
|
||||
NextGC uint64 `json:"next_gc"`
|
||||
LastGC uint64 `json:"last_gc"`
|
||||
PauseTotalNs uint64 `json:"pause_total_ns"`
|
||||
PauseNs uint64 `json:"pause_ns"`
|
||||
NumGC uint32 `json:"num_gc"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type Metric struct {
|
||||
Cmdline []string `json:"cmd_line"`
|
||||
Uptime uint64 `json:"uptime"`
|
||||
Request RequestMetric `json:"request"`
|
||||
Runtime RuntimeMetric `json:"runtime"`
|
||||
Memory MemoryMetric `json:"memory"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type PermRes struct {
|
||||
code int `json:"-" binding:"required"`
|
||||
Message string `json:"message" binding:"required"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type MemoryMetricReadable struct {
|
||||
MemAllocated string `json:"mem_allocated"`
|
||||
MemTotal string `json:"mem_total"`
|
||||
MemSys string `json:"mem_sys"`
|
||||
Lookups uint64 `json:"lookups"`
|
||||
MemMallocs uint64 `json:"mem_mallocs"`
|
||||
MemFrees uint64 `json:"mem_frees"`
|
||||
|
||||
HeapAlloc string `json:"heap_alloc"`
|
||||
HeapSys string `json:"heap_sys"`
|
||||
HeapIdle string `json:"heap_idle"`
|
||||
HeapInuse string `json:"heap_inuse"`
|
||||
HeapReleased string `json:"heap_released"`
|
||||
HeapObjects uint64 `json:"heap_objects"`
|
||||
|
||||
StackInuse string `json:"stack_inuse"`
|
||||
StackSys string `json:"stack_sys"`
|
||||
MSpanInuse string `json:"m_span_inuse"`
|
||||
MSpanSys string `json:"m_span_sys"`
|
||||
MCacheInuse string `json:"m_cache_inuse"`
|
||||
MCacheSys string `json:"m_cache_sys"`
|
||||
BuckHashSys string `json:"buck_hash_sys"`
|
||||
GCSys string `json:"gc_sys"`
|
||||
OtherSys string `json:"other_sys"`
|
||||
|
||||
NextGC string `json:"next_gc"`
|
||||
LastGC string `json:"last_gc"`
|
||||
PauseTotalNs string `json:"pause_total_ns"`
|
||||
PauseNs string `json:"pause_ns"`
|
||||
NumGC uint32 `json:"num_gc"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type MetricReadable struct {
|
||||
Cmdline []string `json:"cmd_line"`
|
||||
Uptime string `json:"uptime"`
|
||||
Request RequestMetricReadable `json:"request"`
|
||||
Runtime RuntimeMetric `json:"runtime"`
|
||||
Memory MemoryMetricReadable `json:"memory"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type UserData struct {
|
||||
Id string `json:"id" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Mail string `json:"mail" binding:"required"`
|
||||
Role enums.UserType `json:"role" binding:"required"`
|
||||
}
|
||||
|
||||
func (detail *UserData) String() string {
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString("UserData(")
|
||||
if detail != nil {
|
||||
buffer.WriteString("id:")
|
||||
buffer.WriteString(detail.Id)
|
||||
} else {
|
||||
buffer.WriteString("nil")
|
||||
}
|
||||
buffer.WriteString(")")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// -vars="mem:memory.mem_allocated,duration:request.duration.min,duration:request.duration.max,duration:request.duration.mean,duration:request.duration.stdev,duration:request.duration.p90,duration:request.duration.p95,duration:request.duration.p99,mem:memory.mem_allocated,mem:memory.heap_alloc,mem:memory.heap_inuse,mem:memory.stack_inuse,duration:memory.next_gc,duration:memory.last_gc,duration:memory.pause_total_ns,duration:memory.pause_ns"
|
|
@ -0,0 +1,116 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"amuz.es/gogs/infra/changer/http/middleware/logging"
|
||||
"amuz.es/gogs/infra/changer/http/middleware/recovery"
|
||||
"amuz.es/gogs/infra/changer/http/middleware/session"
|
||||
"amuz.es/gogs/infra/changer/http/route"
|
||||
"amuz.es/gogs/infra/changer/http/template"
|
||||
"amuz.es/gogs/infra/changer/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
errs "github.com/pkg/errors"
|
||||
graceful "gopkg.in/tylerb/graceful.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = util.NewLogger("http")
|
||||
server *graceful.Server
|
||||
)
|
||||
|
||||
func InitHttp(
|
||||
bindAddress string,
|
||||
prefix string,
|
||||
accessLogConfig *util.LogConfig,
|
||||
ldapConfig *util.LdapConfig,
|
||||
sessionConfig *util.SessionConfig,
|
||||
profile bool,
|
||||
errorSignal chan error, closeSignal chan struct{}) {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
engine := gin.New()
|
||||
|
||||
session.InitRole()
|
||||
|
||||
initMiddleware(engine, accessLogConfig,ldapConfig, sessionConfig)
|
||||
|
||||
initRoutes(engine, prefix, profile)
|
||||
server = &graceful.Server{
|
||||
Timeout: 10 * time.Second,
|
||||
Server: &http.Server{
|
||||
Addr: bindAddress,
|
||||
Handler: engine,
|
||||
},
|
||||
TCPKeepAlive: 3 * time.Second,
|
||||
NoSignalHandling: true,
|
||||
LogFunc: logger.Debugf,
|
||||
}
|
||||
go bind(server, errorSignal, closeSignal)
|
||||
logger.Info(bindAddress + " bound")
|
||||
}
|
||||
|
||||
func CloseHttp() {
|
||||
logger.Info("HTTP closing..")
|
||||
server.Stop(10 * time.Second)
|
||||
logger.Info("HTTP closed")
|
||||
}
|
||||
func bind(server *graceful.Server, errorSignal chan error, closeSignal chan struct{}) {
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
errorSignal <- errs.Wrap(err, "failed to listen or serve http")
|
||||
} else {
|
||||
close(closeSignal)
|
||||
}
|
||||
}
|
||||
func initRoutes(engine *gin.Engine, prefix string, profile bool) {
|
||||
suburl := prefix
|
||||
path := engine.Group(suburl)
|
||||
|
||||
path.StaticFS("/static", route.Static(""))
|
||||
|
||||
|
||||
if profile {
|
||||
logger.Info("enable Pprof handler ")
|
||||
path.GET("/debug/pprof/", IndexHandler())
|
||||
path.GET("/debug/pprof/heap", HeapHandler())
|
||||
path.GET("/debug/pprof/goroutine", GoroutineHandler())
|
||||
path.GET("/debug/pprof/block", BlockHandler())
|
||||
path.GET("/debug/pprof/threadcreate", ThreadCreateHandler())
|
||||
path.GET("/debug/pprof/cmdline", CmdlineHandler())
|
||||
path.GET("/debug/pprof/profile", ProfileHandler())
|
||||
path.GET("/debug/pprof/symbol", SymbolHandler())
|
||||
path.POST("/debug/pprof/symbol", SymbolHandler())
|
||||
path.GET("/debug/pprof/trace", TraceHandler())
|
||||
}
|
||||
|
||||
path.GET("/favicon.ico", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusMovedPermanently, "/static/favicon/images/favicon.ico")
|
||||
})
|
||||
|
||||
path.GET("/manifest.json", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusMovedPermanently, "/static/favicon/manifest.json")
|
||||
})
|
||||
|
||||
path.GET("/browserconfig.xml", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusMovedPermanently, "/static/favicon/browserconfig.xml")
|
||||
})
|
||||
|
||||
path.GET("/", route.Index)
|
||||
path.GET("/change_password", route.ChangePasswordView)
|
||||
path.POST("/change_password", route.ChangePassword)
|
||||
path.GET("/login", route.LoginView)
|
||||
path.POST("/login", route.Login)
|
||||
path.GET("/logout", route.Logout)
|
||||
}
|
||||
|
||||
func initMiddleware(engine *gin.Engine, accessLogConfig *util.LogConfig,ldapConfig *util.LdapConfig, sessionConfig *util.SessionConfig) {
|
||||
engine.Use(logging.AccessLog(accessLogConfig))
|
||||
engine.Use(recovery.RecoveryJSON())
|
||||
engine.Use(session.New(ldapConfig, sessionConfig))
|
||||
|
||||
engine.HTMLRender = template.DefaultRender()
|
||||
|
||||
// not found handler
|
||||
engine.NoRoute(recovery.RecoveryHttpHTMLError(http.StatusNotFound))
|
||||
engine.NoMethod(recovery.RecoveryHttpHTMLError(http.StatusMethodNotAllowed))
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
// middleware
|
||||
package logging
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"amuz.es/gogs/infra/changer/http/iface"
|
||||
"amuz.es/gogs/infra/changer/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
dateFormat = "02/Jan/2006:15:04:05 -0700"
|
||||
)
|
||||
|
||||
var DurationAspect = iface.NewRequestDurationAggr()
|
||||
var CounterAspect = iface.NewCounterMetric()
|
||||
|
||||
type logItem struct {
|
||||
host string
|
||||
method string
|
||||
uri string
|
||||
proto string
|
||||
referer string
|
||||
userAgent string
|
||||
requestedHost string
|
||||
requestedPath string
|
||||
status int
|
||||
responseSize int
|
||||
requestTime time.Time
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
func AccessLog(config *util.LogConfig) gin.HandlerFunc {
|
||||
_, logWriter := util.NewLogWriter(config)
|
||||
DurationAspect.StartTimer(10 * time.Second)
|
||||
CounterAspect.StartTimer(1 * time.Minute)
|
||||
logChan := make(chan logItem)
|
||||
|
||||
go sendLogging(logWriter, logChan)
|
||||
return func(c *gin.Context) {
|
||||
now := time.Now()
|
||||
req := c.Request
|
||||
|
||||
defer func() {
|
||||
dur := time.Now().Sub(now)
|
||||
|
||||
logItemData := logItem{
|
||||
status: c.Writer.Status(),
|
||||
responseSize: c.Writer.Size(),
|
||||
requestTime: now,
|
||||
duration: dur,
|
||||
}
|
||||
|
||||
if req != nil {
|
||||
logItemData.host = remoteHost(req)
|
||||
logItemData.method = req.Method
|
||||
logItemData.uri = req.RequestURI
|
||||
logItemData.proto = req.Proto
|
||||
logItemData.referer = req.Referer()
|
||||
logItemData.userAgent = req.UserAgent()
|
||||
logItemData.requestedHost = req.Host
|
||||
logItemData.requestedPath = req.URL.Path
|
||||
} else {
|
||||
logItemData.host = "-"
|
||||
logItemData.method = ""
|
||||
logItemData.uri = ""
|
||||
logItemData.proto = ""
|
||||
logItemData.referer = ""
|
||||
logItemData.userAgent = ""
|
||||
logItemData.requestedHost = ""
|
||||
logItemData.requestedPath = ""
|
||||
|
||||
}
|
||||
|
||||
logChan <- logItemData
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
func sendLogging(accesslog io.Writer, logChan chan logItem) {
|
||||
for {
|
||||
select {
|
||||
case logItemData := <-logChan:
|
||||
DurationAspect.Add(float64(logItemData.duration.Nanoseconds()))
|
||||
CounterAspect.Inc <- iface.Tuple{
|
||||
Path: logItemData.requestedPath,
|
||||
Code: logItemData.status,
|
||||
}
|
||||
// Logs an access event in Apache combined log format (with a minor customization with the duration).
|
||||
|
||||
buf := bytes.NewBufferString("")
|
||||
buf.WriteString(logItemData.host)
|
||||
buf.WriteString(` - - [`)
|
||||
buf.WriteString(logItemData.requestTime.Format(dateFormat))
|
||||
buf.WriteString(`] "`)
|
||||
buf.WriteString(logItemData.method)
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(logItemData.uri)
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(logItemData.proto)
|
||||
buf.WriteString(`" `)
|
||||
buf.WriteString(strconv.Itoa(logItemData.status))
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(strconv.Itoa(logItemData.responseSize))
|
||||
buf.WriteString(` "`)
|
||||
buf.WriteString(logItemData.referer)
|
||||
buf.WriteString(`" "`)
|
||||
buf.WriteString(logItemData.userAgent)
|
||||
buf.WriteString(`" `)
|
||||
buf.WriteString(strconv.FormatInt((logItemData.duration.Nanoseconds() / time.Millisecond.Nanoseconds()), 10))
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(logItemData.requestedHost)
|
||||
buf.WriteByte('\n')
|
||||
accesslog.Write(buf.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// strip port from addresses with hostname, ipv4 or ipv6
|
||||
func stripPort(address string) string {
|
||||
if h, _, err := net.SplitHostPort(address); err == nil {
|
||||
return h
|
||||
}
|
||||
|
||||
return address
|
||||
}
|
||||
|
||||
// The remote address of the client. When the 'X-Forwarded-For'
|
||||
// header is set, then it is used instead.
|
||||
func remoteAddr(r *http.Request) string {
|
||||
ff := r.Header.Get("X-Forwarded-For")
|
||||
if ff != "" {
|
||||
return ff
|
||||
}
|
||||
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
func remoteHost(r *http.Request) string {
|
||||
a := remoteAddr(r)
|
||||
h := stripPort(a)
|
||||
if h != "" {
|
||||
return h
|
||||
}
|
||||
|
||||
return "-"
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
package recovery
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"amuz.es/gogs/infra/changer/http/iface"
|
||||
"amuz.es/gogs/infra/changer/util"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/flosch/pongo2"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
dunno = []byte("???")
|
||||
centerDot = []byte("·")
|
||||
dot = []byte(".")
|
||||
slash = []byte("/")
|
||||
logger = util.NewLogger("recovery")
|
||||
color = "danger"
|
||||
preText = "루이보스 API 오류 :bomb:"
|
||||
title = "API 요청을 처리하는 중에 의도하지 않은 오류가 발행하였습니다."
|
||||
footer = "확인해 주세요! :hugging_face:"
|
||||
)
|
||||
|
||||
func recoveryInternal(stack []byte, code int, req *http.Request, err *string) {
|
||||
httprequest, _ := httputil.DumpRequest(req, false)
|
||||
detailInfo := map[string]interface{}{
|
||||
"method": req.Method,
|
||||
"statusCode": code,
|
||||
"path": req.URL.Path,
|
||||
"message": err,
|
||||
"request": string(httprequest),
|
||||
"stack": string(stack),
|
||||
}
|
||||
// message := slack.Payload{
|
||||
// Attachments: []slack.Attachment{
|
||||
// convertSlackMessage(code, err, &stack, req, &httprequest),
|
||||
// },
|
||||
// }
|
||||
// slack.Send(util.Config.Slack.Url, "", message)
|
||||
logger.WithFields(logrus.Fields(detailInfo)).Error("[Recovery] panic recovered:\n")
|
||||
}
|
||||
|
||||
func recoveryError(code int, _ *gin.Context) *iface.WrappedResponse {
|
||||
return &iface.WrappedResponse{
|
||||
Message: http.StatusText(code),
|
||||
}
|
||||
}
|
||||
|
||||
func RecoveryHttpJSONError(code int) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
info := recoveryError(code, c)
|
||||
util.DumpJSON(c, code, info)
|
||||
}
|
||||
}
|
||||
|
||||
func RecoveryHttpHTMLError(code int) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
info := recoveryError(code, c)
|
||||
c.HTML(code, "views/error.html", pongo2.Context{
|
||||
"code": code,
|
||||
"message": info.Message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func RecoveryJSON() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
code := http.StatusInternalServerError
|
||||
stack := stack(5)
|
||||
systemErrMessage := err.(error).Error()
|
||||
go recoveryInternal(stack, code, c.Copy().Request, &systemErrMessage)
|
||||
util.DumpJSON(c, code, &iface.WrappedResponse{
|
||||
Message: systemErrMessage,
|
||||
})
|
||||
c.Abort()
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func RecoveryHTML() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
code := http.StatusInternalServerError
|
||||
systemErrMessage := err.(error).Error()
|
||||
c.HTML(code, "views/error.html", pongo2.Context{
|
||||
"code": code,
|
||||
"message": systemErrMessage,
|
||||
})
|
||||
c.Abort()
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// stack returns a nicely formated stack frame, skipping skip frames
|
||||
func stack(skip int) []byte {
|
||||
buf := new(bytes.Buffer) // the returned data
|
||||
// As we loop, we open files and read them. These variables record the currently
|
||||
// loaded file.
|
||||
var lines [][]byte
|
||||
var lastFile string
|
||||
for i := skip; ; i++ {
|
||||
// Skip the expected number of frames
|
||||
pc, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if paths := strings.SplitN(file, "src/", 2); len(paths) == 1 {
|
||||
// Print this much at least. If we can't find the source, it won't show.
|
||||
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
|
||||
} else if vendors := strings.SplitN(paths[1], "vendor/", 2); len(vendors) == 1 {
|
||||
// Print this much at least. If we can't find the source, it won't show.
|
||||
fmt.Fprintf(buf, "%s:%d (0x%x)\n", paths[1], line, pc)
|
||||
} else {
|
||||
// Print this much at least. If we can't find the source, it won't show.
|
||||
fmt.Fprintf(buf, "%s:%d (0x%x)\n", vendors[1], line, pc)
|
||||
}
|
||||
if file != lastFile {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
lines = bytes.Split(data, []byte{'\n'})
|
||||
lastFile = file
|
||||
}
|
||||
fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// source returns a space-trimmed slice of the n'th line.
|
||||
func source(lines [][]byte, n int) []byte {
|
||||
n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
|
||||
if n < 0 || n >= len(lines) {
|
||||
return dunno
|
||||
}
|
||||
return bytes.TrimSpace(lines[n])
|
||||
|
||||
}
|
||||
|
||||
// function returns, if possible, the name of the function containing the PC.
|
||||
func function(pc uintptr) []byte {
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
return dunno
|
||||
}
|
||||
name := []byte(fn.Name())
|
||||
// The name includes the path name to the package, which is unnecessary
|
||||
// since the file name is already included. Plus, it has center dots.
|
||||
// That is, we see
|
||||
// runtime/debug.*T·ptrmethod
|
||||
// and want
|
||||
// *T.ptrmethod
|
||||
// Also the package path might contains dot (e.g. code.google.com/...),
|
||||
// so first eliminate the path prefix
|
||||
if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
|
||||
name = name[lastslash+1:]
|
||||
}
|
||||
if period := bytes.Index(name, dot); period >= 0 {
|
||||
name = name[period+1:]
|
||||
}
|
||||
name = bytes.Replace(name, centerDot, dot, -1)
|
||||
return name
|
||||
}
|
||||
|
||||
// func convertSlackMessage(
|
||||
// code int, err *string, stack *[]byte,
|
||||
// req *http.Request, httpdumpreq *[]byte) (attachment slack.Attachment) {
|
||||
// errmsg := "N/A"
|
||||
// if err != nil {
|
||||
// errmsg = *err
|
||||
// }
|
||||
// return slack.Attachment{
|
||||
// Color: &color,
|
||||
// PreText: &preText,
|
||||
// Title: &title,
|
||||
// Footer: &footer,
|
||||
// Text: err,
|
||||
// Fields: []*slack.Field{
|
||||
// {Title: "status", Value: fmt.Sprintf("%s(%d)", http.StatusText(code), code), Short: true},
|
||||
// {Title: "method", Value: req.Method, Short: true},
|
||||
// {Title: "path", Value: req.URL.Path, Short: true},
|
||||
// {Title: "phase", Value: util.Config.Phase, Short: true},
|
||||
// {Title: "version", Value: util.Config.Version(), Short: true},
|
||||
// {Title: "message", Value: errmsg, Short: true},
|
||||
// {Title: "request", Value: string(*httpdumpreq), Short: false},
|
||||
// {Title: "stack", Value: string(*stack), Short: false},
|
||||
// },
|
||||
// }
|
||||
// }
|
|
@ -0,0 +1,179 @@
|
|||
package secure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
stsHeader = "Strict-Transport-Security"
|
||||
stsSubdomainString = "; includeSubdomains"
|
||||
frameOptionsHeader = "X-Frame-Options"
|
||||
frameOptionsValue = "DENY"
|
||||
contentTypeHeader = "X-Content-Type-Options"
|
||||
contentTypeValue = "nosniff"
|
||||
xssProtectionHeader = "X-XSS-Protection"
|
||||
xssProtectionValue = "1; mode=block"
|
||||
cspHeader = "Content-Security-Policy"
|
||||
)
|
||||
|
||||
func defaultBadHostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "Bad Host", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// Options is a struct for specifying configuration options for the secure.Secure middleware.
|
||||
type Options struct {
|
||||
// AllowedHosts is a list of fully qualified domain names that are allowed. Default is empty list, which allows any and all host names.
|
||||
AllowedHosts []string
|
||||
// If SSLRedirect is set to true, then only allow https requests. Default is false.
|
||||
SSLRedirect bool
|
||||
// If SSLTemporaryRedirect is true, the a 302 will be used while redirecting. Default is false (301).
|
||||
SSLTemporaryRedirect bool
|
||||
// SSLHost is the host name that is used to redirect http requests to https. Default is "", which indicates to use the same host.
|
||||
SSLHost string
|
||||
// SSLProxyHeaders is set of header keys with associated values that would indicate a valid https request. Useful when using Nginx: `map[string]string{"X-Forwarded-Proto": "https"}`. Default is blank map.
|
||||
SSLProxyHeaders map[string]string
|
||||
// STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header.
|
||||
STSSeconds int64
|
||||
// If STSIncludeSubdomains is set to true, the `includeSubdomains` will be appended to the Strict-Transport-Security header. Default is false.
|
||||
STSIncludeSubdomains bool
|
||||
// If FrameDeny is set to true, adds the X-Frame-Options header with the value of `DENY`. Default is false.
|
||||
FrameDeny bool
|
||||
// CustomFrameOptionsValue allows the X-Frame-Options header value to be set with a custom value. This overrides the FrameDeny option.
|
||||
CustomFrameOptionsValue string
|
||||
// If ContentTypeNosniff is true, adds the X-Content-Type-Options header with the value `nosniff`. Default is false.
|
||||
ContentTypeNosniff bool
|
||||
// If BrowserXssFilter is true, adds the X-XSS-Protection header with the value `1; mode=block`. Default is false.
|
||||
BrowserXssFilter bool
|
||||
// ContentSecurityPolicy allows the Content-Security-Policy header value to be set with a custom value. Default is "".
|
||||
ContentSecurityPolicy string
|
||||
// When developing, the AllowedHosts, SSL, and STS options can cause some unwanted effects. Usually testing happens on http, not https, and on localhost, not your production domain... so set this to true for dev environment.
|
||||
// If you would like your development environment to mimic production with complete Host blocking, SSL redirects, and STS headers, leave this as false. Default if false.
|
||||
IsDevelopment bool
|
||||
|
||||
// Handlers for when an error occurs (ie bad host).
|
||||
BadHostHandler http.Handler
|
||||
}
|
||||
|
||||
// Secure is a middleware that helps setup a few basic security features. A single secure.Options struct can be
|
||||
// provided to configure which features should be enabled, and the ability to override a few of the default values.
|
||||
type secure struct {
|
||||
// Customize Secure with an Options struct.
|
||||
opt Options
|
||||
}
|
||||
|
||||
// Constructs a new Secure instance with supplied options.
|
||||
func New(options Options) *secure {
|
||||
if options.BadHostHandler == nil {
|
||||
options.BadHostHandler = http.HandlerFunc(defaultBadHostHandler)
|
||||
}
|
||||
|
||||
return &secure{
|
||||
opt: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *secure) process(w http.ResponseWriter, r *http.Request) error {
|
||||
// Allowed hosts check.
|
||||
if len(s.opt.AllowedHosts) > 0 && !s.opt.IsDevelopment {
|
||||
isGoodHost := false
|
||||
for _, allowedHost := range s.opt.AllowedHosts {
|
||||
if strings.EqualFold(allowedHost, r.Host) {
|
||||
isGoodHost = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isGoodHost {
|
||||
s.opt.BadHostHandler.ServeHTTP(w, r)
|
||||
return fmt.Errorf("Bad host name: %s", r.Host)
|
||||
}
|
||||
}
|
||||
|
||||
// SSL check.
|
||||
if s.opt.SSLRedirect && s.opt.IsDevelopment == false {
|
||||
isSSL := false
|
||||
if strings.EqualFold(r.URL.Scheme, "https") || r.TLS != nil {
|
||||
isSSL = true
|
||||
} else {
|
||||
for k, v := range s.opt.SSLProxyHeaders {
|
||||
if r.Header.Get(k) == v {
|
||||
isSSL = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isSSL == false {
|
||||
url := r.URL
|
||||
url.Scheme = "https"
|
||||
url.Host = r.Host
|
||||
|
||||
if len(s.opt.SSLHost) > 0 {
|
||||
url.Host = s.opt.SSLHost
|
||||
}
|
||||
|
||||
status := http.StatusMovedPermanently
|
||||
if s.opt.SSLTemporaryRedirect {
|
||||
status = http.StatusTemporaryRedirect
|
||||
}
|
||||
|
||||
http.Redirect(w, r, url.String(), status)
|
||||
return fmt.Errorf("Redirecting to HTTPS")
|
||||
}
|
||||
}
|
||||
|
||||
// Strict Transport Security header.
|
||||
if s.opt.STSSeconds != 0 && !s.opt.IsDevelopment {
|
||||
stsSub := ""
|
||||
if s.opt.STSIncludeSubdomains {
|
||||
stsSub = stsSubdomainString
|
||||
}
|
||||
|
||||
w.Header().Add(stsHeader, fmt.Sprintf("max-age=%d%s", s.opt.STSSeconds, stsSub))
|
||||
}
|
||||
|
||||
// Frame Options header.
|
||||
if len(s.opt.CustomFrameOptionsValue) > 0 {
|
||||
w.Header().Add(frameOptionsHeader, s.opt.CustomFrameOptionsValue)
|
||||
} else if s.opt.FrameDeny {
|
||||
w.Header().Add(frameOptionsHeader, frameOptionsValue)
|
||||
}
|
||||
|
||||
// Content Type Options header.
|
||||
if s.opt.ContentTypeNosniff {
|
||||
w.Header().Add(contentTypeHeader, contentTypeValue)
|
||||
}
|
||||
|
||||
// XSS Protection header.
|
||||
if s.opt.BrowserXssFilter {
|
||||
w.Header().Add(xssProtectionHeader, xssProtectionValue)
|
||||
}
|
||||
|
||||
// Content Security Policy header.
|
||||
if len(s.opt.ContentSecurityPolicy) > 0 {
|
||||
w.Header().Add(cspHeader, s.opt.ContentSecurityPolicy)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func Secure(options Options) gin.HandlerFunc {
|
||||
s := New(options)
|
||||
|
||||
return func(c *gin.Context) {
|
||||
err := s.process(c.Writer, c.Request)
|
||||
if err != nil {
|
||||
if c.Writer.Written() {
|
||||
c.AbortWithStatus(c.Writer.Status())
|
||||
} else {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package session
|
||||
|
||||
import "bytes"
|
||||
|
||||
type userDetail struct {
|
||||
id string
|
||||
name string
|
||||
mail string
|
||||
role Role
|
||||
}
|
||||
type UserDetail interface {
|
||||
Id() string
|
||||
Name() string
|
||||
Mail() string
|
||||
Role() Role
|
||||
ToModelContext() map[string]interface{}
|
||||
}
|
||||
|
||||
func (detail *userDetail) Id() string {
|
||||
return detail.id
|
||||
}
|
||||
func (detail *userDetail) Name() string {
|
||||
return detail.name
|
||||
}
|
||||
func (detail *userDetail) Mail() string {
|
||||
return detail.mail
|
||||
}
|
||||
func (detail *userDetail) Role() Role {
|
||||
return detail.role
|
||||
}
|
||||
|
||||
func (detail *userDetail) ToModelContext()map[string]interface{} {
|
||||
context:=make(map[string]interface{})
|
||||
context["user_id"] = detail.id
|
||||
context["user_name"] = detail.name
|
||||
context["user_mail"] = detail.mail
|
||||
context["user_role"] = detail.role.Type().Name()
|
||||
return context
|
||||
}
|
||||
|
||||
func (detail *userDetail) String() string {
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString("UserData(")
|
||||
if detail != nil {
|
||||
buffer.WriteString("id:")
|
||||
buffer.WriteString(detail.id)
|
||||
buffer.WriteString("name:")
|
||||
buffer.WriteString(detail.name)
|
||||
buffer.WriteString("role:")
|
||||
if detail.role != nil {
|
||||
buffer.WriteString(detail.role.String())
|
||||
} else {
|
||||
buffer.WriteString("nil")
|
||||
}
|
||||
} else {
|
||||
buffer.WriteString("nil")
|
||||
}
|
||||
buffer.WriteString(")")
|
||||
return buffer.String()
|
||||
}
|