├── .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 | 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 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | RCON Web Admin by BrainFooLong 28 | 29 | 30 | 31 |
32 |
33 | 39 | 82 |
83 |
84 |
85 | 103 |
104 |
105 |
106 | 120 | 135 | 154 |
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:''};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('Notify Icon')},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 = $('
'); 19 | el.append('
'); 20 | if (label) { 21 | el.find(".title").append($('').text(label)); 22 | } 23 | if (info) { 24 | el.find(".title").append($('').text(" " + info)); 25 | } 26 | var formEl = el.find(".form-element"); 27 | var input = null; 28 | if (data.type == "switch") { 29 | formEl.append(''); 33 | input = formEl.find("select"); 34 | } 35 | if (data.type == "number" || data.type == "text") { 36 | formEl.append(''); 37 | input = formEl.find("input"); 38 | if (typeof data.default != "undefined") { 39 | input.attr("placeholder", data.default); 40 | } 41 | if (typeof data.min == "number") { 42 | input.attr("min", data.min); 43 | } 44 | if (typeof data.max == "number") { 45 | input.attr("max", data.max); 46 | } 47 | if (typeof data.step == "number") { 48 | input.attr("step", data.step); 49 | } 50 | } 51 | if (data.type == "textarea") { 52 | formEl.append('