├── .editorconfig
├── .gitattributes
├── .gitignore
├── .idea
├── codeStyleSettings.xml
└── encodings.xml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── config.template.js
├── db
└── .gitignore
├── logs
└── .gitignore
├── package.json
├── public
├── images
│ ├── favicon.ico
│ └── logo.png
├── index.html
├── scripts
│ ├── global.js
│ ├── interval.js
│ ├── lang.js
│ ├── lib
│ │ ├── bootstrap-notify.min.js
│ │ ├── bootstrap-select.min.js
│ │ ├── bootstrap.min.js
│ │ ├── jquery-3.1.1.min.js
│ │ ├── showdown.license.txt
│ │ └── showdown.min.js
│ ├── modal.js
│ ├── option.js
│ ├── socket.js
│ ├── storage.js
│ ├── view.js
│ └── widget.js
├── stylesheets
│ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.svg
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ └── glyphicons-halflings-regular.woff2
│ ├── lib
│ │ ├── bootstrap-select.min.css
│ │ └── bootstrap.min.css
│ └── page.css
├── views
│ ├── index.html
│ ├── index.js
│ ├── login.html
│ ├── login.js
│ ├── logout.html
│ ├── logout.js
│ ├── servers.html
│ ├── servers.js
│ ├── settings.html
│ ├── settings.js
│ ├── users.html
│ ├── users.js
│ ├── widgets.html
│ └── widgets.js
└── widgets
│ ├── .gitignore
│ └── README.md
├── src
├── base64.js
├── config.js
├── core.js
├── db.js
├── fstools.js
├── hash.js
├── main.js
├── rcon.js
├── rconserver.js
├── request.js
├── routes.js
├── steamapi.js
├── views
│ ├── index.js
│ ├── login.js
│ ├── logout.js
│ ├── servers.js
│ ├── settings.js
│ ├── users.js
│ └── widgets.js
├── websocketmgr.js
├── websocketuser.js
└── widget.js
└── startscripts
├── .gitignore
├── start-linux.sh
└── start-windows.bat
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = crlf
6 | indent_style = space
7 | indent_size = 4
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 | scripts/lib/* binary
3 | stylesheets/lib/* binary
4 | *.ttf binary
5 | *.jpg binary
6 | *.png binary
7 | *.ico binary
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea
3 | /config.js
--------------------------------------------------------------------------------
/.idea/codeStyleSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | - 10.02.2019
4 | - fixed deprecated warnings for newer nodejs versions
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Roland Eigelsreiter
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Project discontinued
2 | I've initially created RCON Web Admin but now this repository is out of date. Other people have taken over development of this project.
3 | This fork is actively maintained -> https://github.com/rcon-web-admin/rcon-web-admin
4 |
5 | # RCON Web Admin
6 |
7 | RCON Web Admin as a powerful web interface to control your RCON server. Every RCON server will work.
8 |
9 | The most powerful feature is that this web admin can run on a server, raspberry pi or another device that is online 24/7. It does all jobs for you, even if you are not connected to the interface. You can install it almost everywhere.
10 |
11 | So imagine you've set-up rcon web admin so that it check users for high ping, VAC status or chat filter. The RCON web admin does it 24/7 for you, no need to have a tool opened all the time.
12 |
13 | ## Features
14 |
15 | * Full administration via your browser, it's just a website
16 | * Unlimited users and servers, Admin roles can manage servers, users can use the dashboard
17 | * Ever more fine granulated user permissions to restrict access to specific server commands and interface features. If you want a user that only can use the 'say' command, you can do it.
18 | * Powerful widget system - Developers can add new features for the dashboard easily
19 | * Responsible - The frontend is designed for every device, desktop or smartphone.
20 | * Run on every device that can install *node.js*
21 | * Multilanguage interface
22 | * One-Click update for the core and all installed widgets
23 | * rcon.web support (Even better as normal RCON sockets because of better stability)
24 | * Core widgets and their top features
25 | * Console - Provide a console interface to directly use rcon web commands in the most low level form
26 | * Autobot - For advanced users, a programmatic interface to write your own little code with high level features
27 | * Rustboard - A dedicated widget for the game 'Rust', provides a lot usefull tools such as playerlist, banlist, chat, kick/ban/admins/mods, steam information incl. VAC ban checks, and a lot more
28 | * Timed Commands - As the name say, you can easily schedule any server command you want to execute on a specific date or time.
29 | * So many more... Give it a try
30 |
31 | ## Supported/tested games
32 |
33 | * Rust (Most tested at the moment)
34 | * Counter-Strike: Go (Basic tests with the console widget)
35 | * Minecraft (Basic tests with the console widget)
36 | * Note: Every other RCON supporting game server will work, it's just untested but console widget is generic for all games
37 |
38 | ## Widgets
39 | The widgets are powerful, they deserve an extra header here. All dashboard things are written in widgets. From the simplest to the most powerful tool, widgets are the way to go. They are some sort of "High level" programs inside the rcon web admin. You don't have to dig much into the code to write widgets. It's basically HTML and JS.
40 |
41 | ## Requirements
42 | - NodeJS min. v5.10.0
43 |
44 | ## Installation Windows
45 | * Download and install node.js (https://nodejs.org)
46 | * Download zip repository and unpack wherever you want
47 | * Open command line and goto unpacked folder (root of the application where the `package.json` is)
48 |
49 | Run following commands
50 |
51 | npm install
52 | node src/main.js install-core-widgets
53 |
54 | ## Installation Linux
55 | Just run all of this commands in the shell. **Note**: Never run this application as root via `sudo`, it is not required. Also never install this application in a webserver directory than can be accessed from the web. The application create an own webserver with limited access to the public folder.
56 |
57 | sudo apt-get install nodejs npm
58 | sudo npm update npm -g
59 | wget https://codeload.github.com/brainfoolong/rcon-web-admin/zip/master -O rcon-web-admin.zip
60 | unzip rcon-web-admin.zip
61 | mv rcon-web-admin-master rcon-web-admin
62 | cd rcon-web-admin
63 | npm install
64 | node src/main.js install-core-widgets
65 | chmod 0755 -R startscripts *
66 |
67 | ## Installation Raspberry pi
68 | Same as linux. You may not be able to run the server or `npm install`, or even the node modules do not download. This will be because of a very old npm/nodejs version (for old raspberry pi for example). So you have to update nodejs and npm to a new version. **Warning**: This will delete old nodejs and npm installation. Make some backups before you do this.
69 |
70 | sudo apt-get purge nodejs npm
71 | ## Pi2 | wget https://nodejs.org/dist/v6.9.3/node-v6.9.3-linux-armv7l.tar.xz -O node.tar.xz
72 | ## Pi A/A+, B/B+ und Zero (ARMv6) | wget https://nodejs.org/dist/v6.9.3/node-v6.9.3-linux-armv6l.tar.xz -O node.tar.xz
73 | tar -xvf node.tar.xz
74 | cd node-v6.9.3-linux-armv*
75 |
76 | ## Installation Docker
77 | [itzg](https://hub.docker.com/r/itzg/) have made a great docker container for rcon web admin. If you prefer docker, you can do it with https://hub.docker.com/r/itzg/rcon/
78 |
79 | ## Start/Stop on Linux
80 |
81 | sh startscripts/start-linux.sh start
82 | sh startscripts/start-linux.sh stop
83 | sh startscripts/start-linux.sh restart
84 |
85 | ## Start/Stop on Windows - Close cmd window to close
86 |
87 | startscripts/start-windows.bat
88 |
89 | ## Open in browser
90 | Goto: http://yourserverip:4326 (You can also use your hostname instead of ip).
91 | To modify the :4326 port or allowed hosts, have a look in the `config.template.js` file in the root folder.
92 |
93 | ## Boot scripts
94 |
95 | On linux you can start the rcon web admin with your server start. For example on ubuntu you can simply add a `crontab -e` line. Do this with the user you want to start the script with, not `sudo`.
96 |
97 | @reboot /path/to/startscripts/start-linux.sh start
98 |
99 | ## Widget developers
100 |
101 | Goto https://github.com/brainfoolong/rcon-web-admin/tree/master/public/widgets for more information.
102 |
103 | ## Troubleshooting
104 |
105 | Linux: If you've installed it and `node` as not available but `nodejs` is, than create a symlink with
106 |
107 | sudo ln -s `which nodejs` /usr/bin/node
108 |
--------------------------------------------------------------------------------
/config.template.js:
--------------------------------------------------------------------------------
1 | /**
2 | * User configuration
3 | * Copy to config.js to enable it
4 | */
5 | var config = {
6 | /**
7 | * The host to bind the webinterface to
8 | * null if you want allow every hostname
9 | */
10 | "host": null,
11 |
12 | /**
13 | * The full wss:// url to the websocket
14 | * Null if default, only required to change when you proxy your application
15 | */
16 | "websocketUrlSsl": null,
17 |
18 | /**
19 | * The full ws://
20 | * Null if default, only required to change when you proxy your application
21 | */
22 | "websocketUrl": null,
23 |
24 | /**
25 | * The port for the server and websocket
26 | * The given number is the one for the webinterface
27 | * The given number + 1 is the websocket port
28 | * Notice that both given number and the number+1 will be required
29 | */
30 | "port": 4326
31 | };
32 |
33 | module.exports = config;
--------------------------------------------------------------------------------
/db/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "BrainFooLong (https://github.com/brainfoolong/rcon-web-admin)",
3 | "name": "rcon-web-admin",
4 | "description": "Self hosted, online RCON administration tool for your server.",
5 | "version": "0.13.3",
6 | "license": "MIT",
7 | "main": "src/main.js",
8 | "repository": {
9 | "type": "git",
10 | "url": "git://github.com/brainfoolong/rcon-web-admin.git"
11 | },
12 | "dependencies": {
13 | "express": "^4.14.0",
14 | "lowdb": "^0.14.0",
15 | "unzip": "^0.1.11",
16 | "ws": "^1.1.1"
17 | },
18 | "engines": {
19 | "node": ">=4.0.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/public/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brainfoolong/rcon-web-admin/0a256c739d3d3c76257e01d97689dbc749eff6ce/public/images/favicon.ico
--------------------------------------------------------------------------------
/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brainfoolong/rcon-web-admin/0a256c739d3d3c76257e01d97689dbc749eff6ce/public/images/logo.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
155 |
156 |
--------------------------------------------------------------------------------
/public/scripts/global.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /**
4 | * Simple debug message, must be enabled via debug.set() in browser console
5 | */
6 | function debug() {
7 | var flag = sessionStorage.getItem("debug");
8 | if (!flag) return;
9 | console[flag].apply(this, arguments);
10 | }
11 |
12 | /**
13 | * Set debug flag
14 | * @param {string} flag Set to "log" or "trace", set false to disable it
15 | */
16 | debug.set = function (flag) {
17 | sessionStorage.setItem("debug", flag ? flag : "");
18 | };
19 |
20 | /**
21 | * Just get a translation value for given key
22 | * @param {string} key
23 | * @param {object=} params
24 | * @return {string}
25 | */
26 | function t(key, params) {
27 | return lang.get(key, params)
28 | }
29 |
30 | /**
31 | * Display a loading spinner in a given element
32 | * @param {string|JQuery} el
33 | */
34 | function spinner(el) {
35 | el = $(el);
36 | el.html('
' +
37 | '' +
38 | '' +
39 | '' +
40 | '
');
41 | }
42 |
43 | /**
44 | * Show a note message on top
45 | * @param {string} message
46 | * @param {string=} type
47 | * @param {number=} delay
48 | */
49 | function note(message, type, delay) {
50 | if (delay === -1) delay = 99999999;
51 | $.notify({
52 | "message": t(message)
53 | }, {
54 | "type": typeof type == "undefined" ? "info" : type,
55 | placement: {
56 | from: "top",
57 | align: "center"
58 | },
59 | "delay": delay || 5000,
60 | });
61 | }
62 |
63 | /**
64 | * Populate form data properties
65 | * @param {JQuery} form
66 | * @param {object} data
67 | */
68 | function populateForm(form, data) {
69 | if (!form || !form.length) return;
70 | $.each(data, function (key, value) {
71 | var ctrl = $('[name=' + key + ']', form);
72 | if (ctrl.is("select.selectpicker")) {
73 | if (value === true) value = "yes";
74 | if (value === false) value = "no";
75 | ctrl.val(value);
76 | } else {
77 | switch (ctrl.prop("type")) {
78 | case "radio":
79 | case "checkbox":
80 | ctrl.each(function () {
81 | if ($(this).attr('value') == value) $(this).attr("checked", value);
82 | });
83 | break;
84 | default:
85 | ctrl.val(value);
86 | }
87 | }
88 | });
89 | }
90 |
91 | /**
92 | * Escape html characters for secure dom injection
93 | * @param {string} string
94 | * @return {string}
95 | */
96 | function escapeHtml(string) {
97 | return String(string).replace(/[&<>"'\/]/g, function (s) {
98 | return escapeHtml.map[s];
99 | });
100 | }
101 |
102 | /**
103 | * Initialize all collapsables in given container
104 | * @param {JQuery} container
105 | */
106 | function collapsable(container) {
107 | container.find(".collapsable-trigger").not("activated").addClass("activated").trigger("collapsable-init");
108 | }
109 |
110 | /**
111 | * Initialize all dismissable in given container
112 | * @param {JQuery} container
113 | */
114 | function dismissable(container) {
115 | container.find(".dismissable").not("activated").addClass("activated").each(function () {
116 | if (!$(this).find("button").length) {
117 | $(this).prepend('');
118 | }
119 | }).find("button.close").trigger("dismissable-init")
120 | }
121 |
122 | /**
123 | * Initialize all textarea autoheights
124 | * @param {JQuery} container
125 | */
126 | function textareaAutoheight(container) {
127 | container.find('textarea.autoheight').not(".autoheight-activated").each(function () {
128 | this.setAttribute('style', 'height:' + (Math.max(20, this.scrollHeight)) + 'px;overflow-y:hidden;');
129 | }).addClass("autoheight-activated").on('input focus', function () {
130 | this.style.height = 'auto';
131 | this.style.height = (Math.max(20, this.scrollHeight)) + 'px';
132 | }).triggerHandler("input");
133 | }
134 |
135 | /**
136 | * Start downloading a file with given contents
137 | * @param {string} data
138 | * @param {string} fileName
139 | */
140 | function downloadFile(data, fileName) {
141 | var a = document.createElement("a");
142 | document.body.appendChild(a);
143 | a.style.display = "none";
144 | var blob = new Blob([data], {type: "octet/stream"});
145 | var url = window.URL.createObjectURL(blob);
146 | a.href = url;
147 | a.download = fileName;
148 | a.click();
149 | window.URL.revokeObjectURL(url);
150 | }
151 |
152 | /**
153 | * The escape html mapping
154 | * @type {{}}
155 | */
156 | escapeHtml.map = {
157 | "&": "&",
158 | "<": "<",
159 | ">": ">",
160 | '"': '"',
161 | "'": ''',
162 | "/": '/'
163 | };
164 |
165 | $(function () {
166 | if (typeof WebSocket == "undefined") {
167 | note("Your browser is not supported in this application (Outdated Browser). Please upgrade to the newest version");
168 | return;
169 | }
170 | // do some hamburger and navigation magic
171 | (function () {
172 | var trigger = $('.hamburger'),
173 | overlay = $('.overlay'),
174 | isClosed = false;
175 |
176 | trigger.click(function () {
177 | hamburger_cross();
178 | });
179 |
180 | function hamburger_cross() {
181 |
182 | if (isClosed == true) {
183 | overlay.hide();
184 | trigger.removeClass('is-open');
185 | trigger.addClass('is-closed');
186 | isClosed = false;
187 | } else {
188 | overlay.show();
189 | trigger.removeClass('is-closed');
190 | trigger.addClass('is-open');
191 | isClosed = true;
192 | }
193 | }
194 |
195 | $('[data-toggle="offcanvas"]').click(function () {
196 | $('#wrapper').toggleClass('toggled');
197 | });
198 | })();
199 | var body = $("body");
200 | var hasTouch = true == ("ontouchstart" in window || window.DocumentTouch && document instanceof DocumentTouch);
201 | body.addClass(hasTouch ? "no-touch" : "touch");
202 | // bind tooltips
203 | $(document).tooltip({
204 | "selector": '[data-tooltip]',
205 | "container": "body",
206 | "html": true,
207 | "title": function () {
208 | return t($(this).attr("data-tooltip"));
209 | }
210 | }).on("inserted.bs.tooltip", function (ev) {
211 | // hide if we are on mobile touch device
212 | if (hasTouch) {
213 | setTimeout(function () {
214 | $(ev.target).trigger("mouseout");
215 | }, 1000);
216 | }
217 | }).on("click collapsable-init", ".collapsable-trigger", function (ev) {
218 | var e = $(this);
219 | var targetId = e.attr("data-collapsable-target");
220 | var target = $(".collapsable-target").filter("[data-collapsable-id='" + targetId + "']");
221 | if (target.length) {
222 | if (ev.type != "collapsable-init") {
223 | e.toggleClass("collapsed");
224 | target.toggleClass("collapsed");
225 | Storage.set("collapsable." + targetId, target.hasClass("collapsed"));
226 | } else {
227 | // collapsed is stored or initially collapsed
228 | var flag = Storage.get("collapsable." + targetId) || target.hasClass("collapsed") || false;
229 | e.toggleClass("collapsed", flag);
230 | target.toggleClass("collapsed", flag);
231 | }
232 | }
233 | }).on("click dismissable-init", ".dismissable button", function (ev) {
234 | var target = $(this).closest(".alert");
235 | var targetId = target.attr("data-id");
236 | if (!targetId) {
237 | note("Dismissable data-id attribute missing", "danger");
238 | return;
239 | }
240 | if (target.length) {
241 | var o = Storage.get("dismissable") || {};
242 | if (ev.type != "dismissable-init") {
243 | target.removeClass("visible");
244 | o[targetId] = true;
245 | Storage.set("dismissable", o);
246 | note("dismissed.info", "info");
247 | } else {
248 | if (o[targetId] !== true) {
249 | target.addClass("visible");
250 | }
251 | }
252 | }
253 | }).on("click", ".show-dismissable", function (ev) {
254 | Storage.set("dismissable", null);
255 | window.location.reload();
256 | });
257 | collapsable(body);
258 | dismissable(body);
259 | lang.replaceInHtml(body);
260 | // socket stuff
261 | Socket.connectAndLoadView();
262 | });
263 |
264 | $(window).on("popstate", function (ev) {
265 | // if the state is the page you expect, pull the name and load it.
266 | if (ev.originalEvent.state && ev.originalEvent.state.hash) {
267 | var hashData = View.getViewDataByHash("#" + ev.originalEvent.state.hash);
268 | View.load(hashData.view, hashData.messageData);
269 | }
270 | });
271 |
272 | // here we have defined all possible callbacks just for the sake of IDE auto completion
273 |
274 | /**
275 | * Node Message Callback
276 | * @callback NodeMessageCallback
277 | * @param {{action: string, messageData: *, callbackId: =int}} responseData
278 | */
--------------------------------------------------------------------------------
/public/scripts/interval.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /**
4 | * Global Interval Handling
5 | */
6 | var Interval = {};
7 |
8 | /** @type {object} */
9 | Interval.list = {};
10 |
11 | /**
12 | * Create an interval, automatically destroy previous interval if exist
13 | * @param {string} id
14 | * @param {function} func
15 | * @param {number} step
16 | */
17 | Interval.create = function (id, func, step) {
18 | Interval.destroy(id);
19 | Interval.list[id] = setInterval(func, step);
20 | };
21 |
22 | /**
23 | * Destroy an interval
24 | * @param {string} id
25 | */
26 | Interval.destroy = function (id) {
27 | if (typeof Interval.list[id] != "undefined" && Interval.list[id] !== null) {
28 | clearInterval(Interval.list[id]);
29 | Interval.list[id] = null;
30 | }
31 | };
--------------------------------------------------------------------------------
/public/scripts/lib/bootstrap-notify.min.js:
--------------------------------------------------------------------------------
1 | /* Project: Bootstrap Growl = v3.1.3 | Description: Turns standard Bootstrap alerts into "Growl-like" notifications. | Author: Mouse0270 aka Robert McIntosh | License: MIT License | Website: https://github.com/mouse0270/bootstrap-growl */
2 | !function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t("object"==typeof exports?require("jquery"):jQuery)}(function(t){function e(e,i,n){var i={content:{message:"object"==typeof i?i.message:i,title:i.title?i.title:"",icon:i.icon?i.icon:"",url:i.url?i.url:"#",target:i.target?i.target:"-"}};n=t.extend(!0,{},i,n),this.settings=t.extend(!0,{},s,n),this._defaults=s,"-"==this.settings.content.target&&(this.settings.content.target=this.settings.url_target),this.animations={start:"webkitAnimationStart oanimationstart MSAnimationStart animationstart",end:"webkitAnimationEnd oanimationend MSAnimationEnd animationend"},"number"==typeof this.settings.offset&&(this.settings.offset={x:this.settings.offset,y:this.settings.offset}),this.init()}var s={element:"body",position:null,type:"info",allow_dismiss:!0,newest_on_top:!1,showProgressbar:!1,placement:{from:"top",align:"right"},offset:20,spacing:10,z_index:1031,delay:5e3,timer:1e3,url_target:"_blank",mouse_over:null,animate:{enter:"animated fadeInDown",exit:"animated fadeOutUp"},onShow:null,onShown:null,onClose:null,onClosed:null,icon_type:"class",template:'
{1}{2}
'};String.format=function(){for(var t=arguments[0],e=1;e .progress-bar').removeClass("progress-bar-"+t.settings.type),t.settings.type=i[e],this.$ele.addClass("alert-"+i[e]).find('[data-notify="progressbar"] > .progress-bar').addClass("progress-bar-"+i[e]);break;case"icon":var n=this.$ele.find('[data-notify="icon"]');"class"==t.settings.icon_type.toLowerCase()?n.removeClass(t.settings.content.icon).addClass(i[e]):(n.is("img")||n.find("img"),n.attr("src",i[e]));break;case"progress":var a=t.settings.delay-t.settings.delay*(i[e]/100);this.$ele.data("notify-delay",a),this.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i[e]).css("width",i[e]+"%");break;case"url":this.$ele.find('[data-notify="url"]').attr("href",i[e]);break;case"target":this.$ele.find('[data-notify="url"]').attr("target",i[e]);break;default:this.$ele.find('[data-notify="'+e+'"]').html(i[e])}var o=this.$ele.outerHeight()+parseInt(t.settings.spacing)+parseInt(t.settings.offset.y);t.reposition(o)},close:function(){t.close()}}},buildNotify:function(){var e=this.settings.content;this.$ele=t(String.format(this.settings.template,this.settings.type,e.title,e.message,e.url,e.target)),this.$ele.attr("data-notify-position",this.settings.placement.from+"-"+this.settings.placement.align),this.settings.allow_dismiss||this.$ele.find('[data-notify="dismiss"]').css("display","none"),(this.settings.delay<=0&&!this.settings.showProgressbar||!this.settings.showProgressbar)&&this.$ele.find('[data-notify="progressbar"]').remove()},setIcon:function(){"class"==this.settings.icon_type.toLowerCase()?this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon):this.$ele.find('[data-notify="icon"]').is("img")?this.$ele.find('[data-notify="icon"]').attr("src",this.settings.content.icon):this.$ele.find('[data-notify="icon"]').append('')},styleURL:function(){this.$ele.find('[data-notify="url"]').css({backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)",height:"100%",left:"0px",position:"absolute",top:"0px",width:"100%",zIndex:this.settings.z_index+1}),this.$ele.find('[data-notify="dismiss"]').css({position:"absolute",right:"10px",top:"5px",zIndex:this.settings.z_index+2})},placement:function(){var e=this,s=this.settings.offset.y,i={display:"inline-block",margin:"0px auto",position:this.settings.position?this.settings.position:"body"===this.settings.element?"fixed":"absolute",transition:"all .5s ease-in-out",zIndex:this.settings.z_index},n=!1,a=this.settings;switch(t('[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])').each(function(){return s=Math.max(s,parseInt(t(this).css(a.placement.from))+parseInt(t(this).outerHeight())+parseInt(a.spacing))}),1==this.settings.newest_on_top&&(s=this.settings.offset.y),i[this.settings.placement.from]=s+"px",this.settings.placement.align){case"left":case"right":i[this.settings.placement.align]=this.settings.offset.x+"px";break;case"center":i.left=0,i.right=0}this.$ele.css(i).addClass(this.settings.animate.enter),t.each(Array("webkit","moz","o","ms",""),function(t,s){e.$ele[0].style[s+"AnimationIterationCount"]=1}),t(this.settings.element).append(this.$ele),1==this.settings.newest_on_top&&(s=parseInt(s)+parseInt(this.settings.spacing)+this.$ele.outerHeight(),this.reposition(s)),t.isFunction(e.settings.onShow)&&e.settings.onShow.call(this.$ele),this.$ele.one(this.animations.start,function(){n=!0}).one(this.animations.end,function(){t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)}),setTimeout(function(){n||t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)},100)},bind:function(){var e=this;if(this.$ele.find('[data-notify="dismiss"]').on("click",function(){e.close()}),this.$ele.mouseover(function(){t(this).data("data-hover","true")}).mouseout(function(){t(this).data("data-hover","false")}),this.$ele.data("data-hover","false"),this.settings.delay>0){e.$ele.data("notify-delay",e.settings.delay);var s=setInterval(function(){var t=parseInt(e.$ele.data("notify-delay"))-e.settings.timer;if("false"===e.$ele.data("data-hover")&&"pause"==e.settings.mouse_over||"pause"!=e.settings.mouse_over){var i=(e.settings.delay-t)/e.settings.delay*100;e.$ele.data("notify-delay",t),e.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i).css("width",i+"%")}t<=-e.settings.timer&&(clearInterval(s),e.close())},e.settings.timer)}},close:function(){var e=this,s=parseInt(this.$ele.css(this.settings.placement.from)),i=!1;this.$ele.data("closing","true").addClass(this.settings.animate.exit),e.reposition(s),t.isFunction(e.settings.onClose)&&e.settings.onClose.call(this.$ele),this.$ele.one(this.animations.start,function(){i=!0}).one(this.animations.end,function(){t(this).remove(),t.isFunction(e.settings.onClosed)&&e.settings.onClosed.call(this)}),setTimeout(function(){i||(e.$ele.remove(),e.settings.onClosed&&e.settings.onClosed(e.$ele))},100)},reposition:function(e){var s=this,i='[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])',n=this.$ele.nextAll(i);1==this.settings.newest_on_top&&(n=this.$ele.prevAll(i)),n.each(function(){t(this).css(s.settings.placement.from,e),e=parseInt(e)+parseInt(s.settings.spacing)+t(this).outerHeight()})}}),t.notify=function(t,s){var i=new e(this,t,s);return i.notify},t.notifyDefaults=function(e){return s=t.extend(!0,{},s,e)},t.notifyClose=function(e){"undefined"==typeof e||"all"==e?t("[data-notify]").find('[data-notify="dismiss"]').trigger("click"):t('[data-notify-position="'+e+'"]').find('[data-notify="dismiss"]').trigger("click")}});
--------------------------------------------------------------------------------
/public/scripts/lib/showdown.license.txt:
--------------------------------------------------------------------------------
1 | Showdown Copyright (c) 2007, John Fraser
2 |
3 | All rights reserved.
4 |
5 | Original Markdown copyright (c) 2004, John Gruber
6 |
7 | All rights reserved.
8 |
9 | Redistribution and use in source and binary forms, with or without
10 | modification, are permitted provided that the following conditions are
11 | met:
12 |
13 | * Redistributions of source code must retain the above copyright notice,
14 | this list of conditions and the following disclaimer.
15 |
16 | * Redistributions in binary form must reproduce the above copyright
17 | notice, this list of conditions and the following disclaimer in the
18 | documentation and/or other materials provided with the distribution.
19 |
20 | * Neither the name "Markdown" nor the names of its contributors may
21 | be used to endorse or promote products derived from this software
22 | without specific prior written permission.
23 |
24 | This software is provided by the copyright holders and contributors "as
25 | is" and any express or implied warranties, including, but not limited
26 | to, the implied warranties of merchantability and fitness for a
27 | particular purpose are disclaimed. In no event shall the copyright owner
28 | or contributors be liable for any direct, indirect, incidental, special,
29 | exemplary, or consequential damages (including, but not limited to,
30 | procurement of substitute goods or services; loss of use, data, or
31 | profits; or business interruption) however caused and on any theory of
32 | liability, whether in contract, strict liability, or tort (including
33 | negligence or otherwise) arising in any way out of the use of this
34 | software, even if advised of the possibility of such damage.
35 |
--------------------------------------------------------------------------------
/public/scripts/modal.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /**
4 | * Modal handling
5 | */
6 | var Modal = {};
7 |
8 | /**
9 | * Show alert box
10 | * @param {string|JQuery} message
11 | * @param {function=} callback
12 | */
13 | Modal.alert = function (message, callback) {
14 | var e = $("#alert");
15 | e.modal().one("hidden.bs.modal", callback).find(".modal-body").html(message);
16 | e.find(".btn").on("click", function () {
17 | e.modal("hide");
18 | }
19 | );
20 | e.find(".selectpicker").selectpicker();
21 | };
22 |
23 | /**
24 | * Show confirm box
25 | * @param {string|JQuery} message
26 | * @param {function=} callback
27 | */
28 | Modal.confirm = function (message, callback) {
29 | var e = $("#confirm");
30 | e.modal().one("hidden.bs.modal", function () {
31 | if (callback) callback(false);
32 | }
33 | ).find(".modal-body").html(message);
34 | e.find(".btn-primary").on("click", function () {
35 | if (callback) callback(true);
36 | callback = null;
37 | e.modal("hide");
38 | }
39 | );
40 | e.find(".btn-default").on("click", function () {
41 | if (callback) callback(false);
42 | callback = null;
43 | e.modal("hide");
44 | }
45 | );
46 | e.find(".selectpicker").selectpicker();
47 | };
48 |
49 | /**
50 | * Show confirm box
51 | * @param {string|JQuery} message
52 | * @param {string} placeholder
53 | * @param {function=} callback
54 | */
55 | Modal.prompt = function (message, placeholder, callback) {
56 | var e = $("#prompt");
57 | e.modal().one("hidden.bs.modal", function () {
58 | if (callback) callback(false);
59 | }
60 | ).find(".modal-body .message").html(message);
61 | var i = e.find(".modal-body input");
62 | i.val('');
63 | i.off("keyup").on("keyup", function (ev) {
64 | if (ev.keyCode == 13) {
65 | if (callback) callback(i.val());
66 | callback = null;
67 | e.modal("hide");
68 | }
69 | });
70 | i.attr("placeholder", placeholder);
71 | e.find(".btn-primary").on("click", function () {
72 | if (callback) callback(i.val());
73 | callback = null;
74 | e.modal("hide");
75 | }
76 | );
77 | e.find(".btn-default").on("click", function () {
78 | if (callback) callback(false);
79 | callback = null;
80 | e.modal("hide");
81 | }
82 | );
83 | i.focus();
84 | e.find(".selectpicker").selectpicker();
85 | };
--------------------------------------------------------------------------------
/public/scripts/option.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /**
4 | * Some helpful stuff for options handling
5 | */
6 | var option = {};
7 |
8 | /**
9 | * Create html element from given data
10 | * @param {string} id
11 | * @param {string|null} label
12 | * @param {string|null} info
13 | * @param {*} value
14 | * @param {*} data
15 | * @returns {JQuery}
16 | */
17 | option.createHtmlFromData = function (id, label, info, value, data) {
18 | var el = $('