├── .editorconfig
├── .github
└── FUNDING.yml
├── .gitignore
├── .travis.yml
├── LICENCE
├── README.md
├── _scripts
├── config
│ ├── config.build.json
│ └── config.dev.json
├── csgo-configs
│ ├── knife
│ │ └── default.txt
│ └── main
│ │ ├── competitive-esl5v5.txt
│ │ ├── dangerzone-default.txt
│ │ └── wingman-esl2v2.txt
└── webpack
│ └── webpack.config.js
├── app
├── src
│ ├── bundle.js
│ ├── config
│ │ ├── baseConfig.js
│ │ ├── index.js
│ │ └── version
│ │ │ └── index.js
│ ├── controllers
│ │ ├── Api
│ │ │ ├── BaseController.js
│ │ │ ├── CsgoController.js
│ │ │ └── IndexController.js
│ │ └── Web
│ │ │ ├── BaseController.js
│ │ │ └── IndexController.js
│ ├── log
│ │ └── .gitkeep
│ ├── modules
│ │ ├── assets.js
│ │ ├── challonge.js
│ │ ├── csgoConfig.js
│ │ ├── database.js
│ │ ├── logger.js
│ │ ├── queue.js
│ │ ├── rcon.js
│ │ ├── router.js
│ │ ├── socket.js
│ │ └── web.js
│ ├── routers
│ │ ├── Api.js
│ │ └── Web.js
│ ├── server.js
│ ├── utils
│ │ ├── Arrays.js
│ │ └── Strings.js
│ └── views
│ │ ├── general
│ │ └── notfound.ejs
│ │ ├── index.ejs
│ │ ├── index
│ │ └── index.ejs
│ │ └── partials
│ │ └── head.ejs
└── test
│ └── functions.test.js
├── frontend
├── components
│ ├── About.js
│ ├── Create.js
│ ├── Detail.js
│ ├── Edit.js
│ ├── Home.js
│ ├── NotFound.js
│ ├── Servers.js
│ ├── Settings.js
│ ├── flags
│ │ ├── DE.js
│ │ ├── FR.js
│ │ ├── GB.js
│ │ └── NL.js
│ ├── icons
│ │ ├── Add.js
│ │ ├── Close.js
│ │ ├── Details.js
│ │ ├── Edit.js
│ │ ├── Home.js
│ │ ├── Language.js
│ │ ├── Notification.js
│ │ ├── Servers.js
│ │ └── Settings.js
│ ├── integrations
│ │ ├── Archive.js
│ │ ├── Challonge.js
│ │ ├── Csv.js
│ │ ├── ForceArchive.js
│ │ └── MatchGroups.js
│ ├── partials
│ │ ├── Alert.js
│ │ ├── Breadcrumbs.js
│ │ ├── Footer.js
│ │ ├── Header.js
│ │ └── Notification.js
│ └── settings
│ │ └── Log.js
├── lang
│ ├── de
│ │ ├── about.json
│ │ ├── create.json
│ │ ├── detail.json
│ │ ├── edit.json
│ │ ├── general.json
│ │ ├── home.json
│ │ ├── index.js
│ │ ├── servers.json
│ │ └── settings.json
│ ├── en
│ │ ├── about.json
│ │ ├── create.json
│ │ ├── detail.json
│ │ ├── edit.json
│ │ ├── general.json
│ │ ├── home.json
│ │ ├── index.js
│ │ ├── servers.json
│ │ └── settings.json
│ ├── fr
│ │ ├── about.json
│ │ ├── create.json
│ │ ├── detail.json
│ │ ├── edit.json
│ │ ├── general.json
│ │ ├── home.json
│ │ ├── index.js
│ │ ├── servers.json
│ │ └── settings.json
│ ├── index.js
│ └── nl
│ │ ├── about.json
│ │ ├── create.json
│ │ ├── detail.json
│ │ ├── edit.json
│ │ ├── general.json
│ │ ├── home.json
│ │ ├── index.js
│ │ ├── servers.json
│ │ └── settings.json
├── main.js
├── modules
│ ├── language.js
│ ├── socket.js
│ ├── storage.js
│ ├── store.js
│ └── systemNotification.js
├── scss
│ ├── components
│ │ ├── create.scss
│ │ ├── detail.scss
│ │ ├── home.scss
│ │ ├── icons
│ │ │ ├── add.scss
│ │ │ ├── close.scss
│ │ │ ├── details.scss
│ │ │ ├── edit.scss
│ │ │ ├── home.scss
│ │ │ ├── language.scss
│ │ │ ├── notification.scss
│ │ │ ├── servers.scss
│ │ │ └── settings.scss
│ │ ├── partials
│ │ │ ├── alert.scss
│ │ │ ├── breadcrumbs.scss
│ │ │ ├── footer.scss
│ │ │ ├── header.scss
│ │ │ └── notification.scss
│ │ └── settings
│ │ │ └── log.scss
│ ├── global
│ │ ├── base.scss
│ │ ├── normalize.scss
│ │ └── utils.scss
│ ├── mixins
│ │ ├── helpers.scss
│ │ ├── mediaqueries.scss
│ │ └── typography.scss
│ ├── style.scss
│ └── variables
│ │ ├── animations.scss
│ │ ├── breakpoints.scss
│ │ ├── colors.scss
│ │ └── fonts.scss
├── test
│ └── lang.test.js
└── utils
│ ├── Arrays.js
│ └── Strings.js
├── package-lock.json
├── package.json
└── public
├── assets
└── csgo-remote_matches.csv
└── manifest.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Top-most EditorConfig file
2 | root = true
3 |
4 | # Set default charset with unix-style newlines with a newline ending every file
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | # PHP PSR2 overrides
12 | [*.php]
13 | indent_style = space
14 | indent_size = 4
15 |
16 | # JS overrides
17 | [*.js]
18 | indent_style = space
19 | indent_size = 4
20 |
21 | # SCSS and JSON overrides
22 | [*.{scss,json}]
23 | indent_style = space
24 | indent_size = 2
25 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | liberapay: glenndehaan
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # PhpStorm
2 | .idea
3 |
4 | # General files
5 | *~
6 | *.DS_STORE
7 |
8 | # Js & css build files
9 | dist/
10 |
11 | # Dependency managers
12 | node_modules/
13 | npm-debug.log
14 |
15 | # App
16 | csgo-rcon.json
17 | *.log
18 | config.json
19 | app/src/config/version/version.txt
20 |
21 | # Tests
22 | coverage/
23 | .nyc_output/
24 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "8"
4 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Glenn de Haan
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 | # Counter-Strike Global Offensive Web Panel
2 |
3 | A web panel to control a CS:GO server
4 |
5 | [](https://travis-ci.org/glenndehaan/csgo-rcon-nodejs) [](https://github.com/glenndehaan/csgo-rcon-nodejs/releases) [](https://github.com/glenndehaan/csgo-rcon-nodejs/blob/master/package.json) [](https://github.com/glenndehaan/csgo-rcon-nodejs/blob/master/LICENCE)
6 |
7 | ## Backend Structure
8 | - NodeJS
9 | - Simple Node Logger
10 | - srcds-rcon
11 | - Json DB
12 | - Express
13 | - Express-WS
14 |
15 | ## Frontend Structure
16 | - Webpack
17 | - Preact
18 | - Preact Router
19 | - Bootstrap
20 | - Sass
21 | - Sockette
22 |
23 | ## Basic Usage
24 | - Download the latest version from the releases page on GitHub
25 | - Save the binary in it's own folder
26 | - Run the binary (this will create some additional files/folders)
27 | - Adjust the `config.json`
28 | - Restart the binary
29 |
30 | Then open up a webbrowser and go to the site
31 |
32 | ## Development Usage
33 | - Install NodeJS 8.0 or higher
34 | - Copy the `_scripts/config/config.dev.json` to here `app/src/config/config.json`
35 | - Run `npm install` in the root project folder
36 | - Run `npm run webpack` in the root project folder
37 | - Run `npm run dev` in the root project folder
38 |
39 | Then open up a webbrowser and go to the site
40 |
41 | ## Logging
42 | All logs will be written to the `csgo-rcon.log` file in the node folder.
43 |
44 | To increase the logging change the logger level in the `config.json` file from `info` to `debug`.
45 |
46 | ## Database
47 | To make this as simple as it is I use a local Json database.
48 |
49 | Checkout `csgo-rcon.json` since this is the db file.
50 |
51 | ## Plugin
52 | To enable livescoring and auto match configuration please install the SourceMod Plugin:
53 |
54 | https://github.com/glenndehaan/csgo-rcon-plugin
55 |
56 | ## Language Support
57 | - English
58 | - French
59 | - German
60 | - Dutch
61 |
62 | ## config.json Explanation
63 | ```
64 | {
65 | "application": {
66 | "companyName": "A Company", <<- This name will be prefixed in the servername
67 | "baseUrl": "http://CURRENTIP:3542" <<- Change 'CURRENTIP' to the IP of the CSGO-Remote server. Your CS:GO servers must be able to connect to the CSGO-Remote app
68 | },
69 | "servers": [ <<- Put all your CS:GO server in this block
70 | { <<- A server block
71 | "ip": "192.168.1.xx", <<- CS:GO server IP/Hostname
72 | "port": 27015, <<- CS:GO server Port
73 | "password": "anrconpassword", <<- CS:GO server password
74 | "default_map": "de_dust2", <<- CS:GO server default loading map
75 | "server_restore_config": "server" <<- Leave this default
76 | }
77 | ],
78 | "broadcaster": { <<- The broadcaster sends messages to the CS:GO server chat
79 | "enabled": true, <<- Enables the broadcaster
80 | "speed": 120, <<- After how long do we need to send the next message in seconds
81 | "messages": [ <<- This block contains all the messages
82 | "This is message 1", <<- This is one message
83 | "This is message 2",
84 | "This is message 3"
85 | ]
86 | },
87 | "authentication": { <<- This block is for the /settings pages
88 | "username": "root", <<- Username for the /settings pages
89 | "password": "password123!" <<- Password for the /settings pages
90 | }
91 | }
92 | ```
93 |
94 | ## v2 TODO's
95 | * GitHub Request: https://github.com/glenndehaan/csgo-rcon-nodejs/issues/2
96 | * ~~Wingman: `Connect server` needs to change map to wingman map~~
97 | * ~~Dangerzone: `Connect server` needs to change map to dangerzone map~~
98 | * ~~Dangerzone: `Knife round` needs to be disabled~~
99 | * ~~Dangerzone: `Start match` needs to stop warmup~~
100 | * ~~Dangerzone/Wingman: Restore game_type/game_mode to competitive~~
101 | * ~~Healthcheck needs to be faster~~
102 | * ~~Translations add socket error messages~~
103 | * ~~Add frontend translations~~
104 | * Add Bo1, Bo2, Bo3, Bo4, Bo5 support
105 | * ~~Add wingman and dangerzone support (maps, configs)~~
106 | * Add support for uploading scores back to challonge
107 | * Replace basic auth with frontend login
108 | * Add livescoring to Home/Servers view
109 | * ~~Add Readme `config.json` explanation~~
110 | * Implement LoadBalancing (Redis)?
111 | * Implement MongoDB?
112 | * ~~Implement lang files tests~~
113 | * ~~Implement basic tests~~
114 | * ~~Implement NodeJS server logs web interface~~
115 | * ~~Implement NodeJS server controls~~ (Not possible)
116 | * ~~Better GitHub release integration~~
117 | * ~~Rcon Healthcheck (Auto reinit Rcon connection)~~
118 | * ~~Rcon Command error/timeout (https://github.com/randunel/node-srcds-rcon#specify-command-timeout)~~
119 |
120 | ## v1 TODO's
121 | * ~~Lock matches on same server when one match is running.~~
122 | * ~~Fix production PKG build.~~
123 | * ~~Add say to admin interface.~~
124 | * ~~Add broadcaster to config file.~~
125 | * ~~Fix restore server config file.~~ (Restore config is now optional)
126 | * ~~Catch srcds error's. (Send cmd if rcon fails)~~
127 | * ~~Create one cmd for rcon. (Use in startMatch, reset) Let others use that.~~
128 | * ~~Rcon reconnect?~~ (Not possible with current package!)
129 | * ~~Rcon server/match status? (V2)~~
130 | * ~~Challonge (API/Webhook) match import?~~
131 | * ~~Rewrite (server modules) to ES6 classes?~~
132 | * ~~Bootstrap Notification bar. (Showing that we are sending something)~~
133 | * ~~Add match groups~~
134 | * ~~Edit match~~
135 | * ~~Add native system notifications~~
136 | * ~~Add SVG's to replace bulky buttons~~
137 | * ~~Protect /settings page with basic auth (Username/Password in config file)~~
138 | * ~~Challonge import server option: Next available server~~
139 | * ~~Challonge import complete notification update~~
140 | * ~~Settings icon active state stuck~~
141 | * ~~Restart game implement are you sure dialog~~
142 | * ~~Rewrite queue module~~
143 | * ~~Filter matches on homepage (Not started, Running, Completed)~~
144 | * ~~/settings add archive complete matches function~~
145 | * ~~Disable match/server controls if match isn't started~~
146 | * ~~Server overview page to so available server where no matches are running~~
147 | * ~~Add breadcrumbs~~
148 | * ~~Add /settings force archive match~~
149 | * ~~/about page with software info~~
150 | * ~~Version update available based on GIT (GitHub)~~
151 | * ~~Show development/production version~~
152 | * Autosetup server (V3)
153 | * Match control password protect at match create
154 | * ~~Plugin: Lock match data after match_end~~
155 | * ~~Plugin: Split connect server/start match~~
156 | * ~~Plugin: Add start warmup button~~
157 | * ~~Plugin: Autoflow Connect Server->Warmup->Start Knife->Knife (End)->Warmup->Start match->Match end->Auto restore server~~
158 | * ~~CSV Import matches~~
159 |
160 | ## License
161 |
162 | MIT
163 |
--------------------------------------------------------------------------------
/_scripts/config/config.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "application": {
3 | "companyName": "A Company",
4 | "baseUrl": "http://CURRENTIP:3542"
5 | },
6 | "servers": [
7 | {
8 | "ip": "192.168.1.xx",
9 | "port": 27015,
10 | "password": "anrconpassword",
11 | "default_map": "de_dust2",
12 | "server_restore_config": "server"
13 | }
14 | ],
15 | "broadcaster": {
16 | "enabled": true,
17 | "speed": 120,
18 | "messages": [
19 | "This is message 1",
20 | "This is message 2",
21 | "This is message 3"
22 | ]
23 | },
24 | "authentication": {
25 | "username": "root",
26 | "password": "password123!"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/_scripts/config/config.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "application": {
3 | "companyName": "A Company",
4 | "csgoConfigFolder": "../../_scripts/csgo-configs",
5 | "baseUrl": "http://CURRENTIP:3542"
6 | },
7 | "logger": {
8 | "level": "trace"
9 | },
10 | "servers": [
11 | {
12 | "ip": "192.168.1.xx",
13 | "port": 27015,
14 | "password": "anrconpassword",
15 | "default_map": "de_dust2",
16 | "server_restore_config": "server"
17 | }
18 | ],
19 | "broadcaster": {
20 | "enabled": true,
21 | "speed": 120,
22 | "messages": [
23 | "This is message 1",
24 | "This is message 2",
25 | "This is message 3"
26 | ]
27 | },
28 | "authentication": {
29 | "username": "root",
30 | "password": "password123!"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/_scripts/csgo-configs/knife/default.txt:
--------------------------------------------------------------------------------
1 | mp_warmup_end 1
2 | mp_ct_default_secondary ""
3 | mp_free_armor 1
4 | mp_give_player_c4 0
5 | mp_maxmoney 0
6 | mp_respawn_on_death_ct 0
7 | mp_respawn_on_death_t 0
8 | mp_roundtime 1.92
9 | mp_roundtime_defuse 1.92
10 | mp_roundtime_hostage 1.92
11 | mp_t_default_secondary ""
12 | bot_quota 0
13 |
--------------------------------------------------------------------------------
/_scripts/csgo-configs/main/dangerzone-default.txt:
--------------------------------------------------------------------------------
1 | // Reset Knife data
2 | mp_t_default_secondary "weapon_glock"
3 | mp_ct_default_secondary "weapon_hkp2000"
4 | mp_give_player_c4 1
5 | // End Reset Knife data
6 |
7 | // Gamemode
8 | game_mode 0
9 | game_type 6
10 | // End Gamemode
11 |
12 | // Change map
13 | changelevel dz_blacksite
14 |
--------------------------------------------------------------------------------
/_scripts/csgo-configs/main/wingman-esl2v2.txt:
--------------------------------------------------------------------------------
1 | // ESL - www.eslgaming.com
2 | // CS:GO 2on2 Ladder Config
3 | // 14.01.2016
4 |
5 | // Reset Knife data
6 | mp_t_default_secondary "weapon_glock"
7 | mp_ct_default_secondary "weapon_hkp2000"
8 | mp_give_player_c4 1
9 | // End Reset Knife data
10 |
11 | // Gamemode
12 | game_mode 2
13 | game_type 0
14 | // End Gamemode
15 |
16 | ammo_grenade_limit_default 1
17 | ammo_grenade_limit_flashbang 2
18 | ammo_grenade_limit_total 4
19 |
20 | bot_quota "0" // Determines the total number of bots in the game
21 |
22 | cash_team_terrorist_win_bomb 3500
23 | cash_team_elimination_bomb_map 3250
24 | cash_team_win_by_defusing_bomb 3500
25 | cash_team_win_by_hostage_rescue 3500
26 | cash_team_loser_bonus 1400
27 | cash_team_loser_bonus_consecutive_rounds 500
28 | cash_team_rescued_hostage 750
29 | cash_team_hostage_alive 150
30 | cash_team_planted_bomb_but_defused 800
31 | cash_team_hostage_interaction 150
32 | cash_player_killed_teammate -300
33 | cash_player_killed_enemy_default 300
34 | cash_player_killed_enemy_factor 1
35 | cash_player_bomb_planted 300
36 | cash_player_bomb_defused 300
37 | cash_player_rescued_hostage 1000
38 | cash_player_interact_with_hostage 150
39 | cash_player_damage_hostage -30
40 | cash_player_killed_hostage -1000
41 |
42 | ff_damage_reduction_grenade 0.85 // How much to reduce damage done to teammates by a thrown grenade. Range is from 0 - 1 (with 1 being damage equal to what is done to an enemy)
43 | ff_damage_reduction_bullets 0.33 // How much to reduce damage done to teammates when shot. Range is from 0 - 1 (with 1 being damage equal to what is done to an enemy)
44 | ff_damage_reduction_other 0.4 // How much to reduce damage done to teammates by things other than bullets and grenades. Range is from 0 - 1 (with 1 being damage equal to what is done to an enemy)
45 | ff_damage_reduction_grenade_self 1 // How much to damage a player does to himself with his own grenade. Range is from 0 - 1 (with 1 being damage equal to what is done to an enemy)
46 |
47 | mp_afterroundmoney 0 // amount of money awared to every player after each round
48 | mp_autokick 0 // Kick idle/team-killing players
49 | mp_autoteambalance 0
50 | mp_buytime 15 // How many seconds after round start players can buy items for.
51 | mp_c4timer 40 // How long from when the C4 is armed until it blows
52 | mp_death_drop_defuser 1 // Drop defuser on player death
53 | mp_death_drop_grenade 2 // Which grenade to drop on player death: 0=none, 1=best, 2=current or best
54 | mp_death_drop_gun 1 // Which gun to drop on player death: 0=none, 1=best, 2=current or best
55 | mp_defuser_allocation 0 // How to allocate defusers to CTs at start or round: 0=none, 1=random, 2=everyone
56 | mp_do_warmup_period 1 // Whether or not to do a warmup period at the start of a match.
57 | mp_forcecamera 1 // Restricts spectator modes for dead players
58 | mp_force_pick_time 160 // The amount of time a player has on the team screen to make a selection before being auto-teamed
59 | mp_free_armor 0 // Determines whether armor and helmet are given automatically.
60 | mp_freezetime 10 // How many seconds to keep players frozen when the round starts
61 | mp_friendlyfire 1 // Allows team members to injure other members of their team
62 | mp_halftime 1 // Determines whether or not the match has a team-swapping halftime event.
63 | mp_halftime_duration 15 // Number of seconds that halftime lasts
64 | mp_join_grace_time 30 // Number of seconds after round start to allow a player to join a game
65 | mp_limitteams 0 // Max # of players 1 team can have over another (0 disables check)
66 | mp_logdetail 3 // Logs attacks. Values are: 0=off, 1=enemy, 2=teammate, 3=both)
67 | mp_match_can_clinch 1 // Can a team clinch and end the match by being so far ahead that the other team has no way to catching up
68 | mp_match_end_restart 1 // At the end of the match, perform a restart instead of loading a new map
69 | mp_maxmoney 16000 // maximum amount of money allowed in a player's account
70 | mp_maxrounds 30 // max number of rounds to play before server changes maps
71 | mp_molotovusedelay 0 // Number of seconds to delay before the molotov can be used after acquiring it
72 | mp_playercashawards 1 // Players can earn money by performing in-game actions
73 | mp_playerid 0 // Controls what information player see in the status bar: 0 all names; 1 team names; 2 no names
74 | mp_playerid_delay 0.5 // Number of seconds to delay showing information in the status bar
75 | mp_playerid_hold 0.25 // Number of seconds to keep showing old information in the status bar
76 | mp_round_restart_delay 5 // Number of seconds to delay before restarting a round after a win
77 | mp_roundtime 1.92 // How many minutes each round takes.
78 | mp_roundtime_defuse 1.92 // How many minutes each round takes on defusal maps.
79 | mp_solid_teammates 1 // Determines whether teammates are solid or not.
80 | mp_startmoney 800 // amount of money each player gets when they reset
81 | mp_teamcashawards 1 // Teams can earn money by performing in-game actions
82 | mp_timelimit 0 // game time per map in minutes
83 | mp_tkpunish 0 // Will a TK'er be punished in the next round? {0=no, 1=yes}
84 | mp_warmuptime 1 // If true, there will be a warmup period/round at the start of each match to allow
85 | mp_weapons_allow_map_placed 1 // If this convar is set, when a match starts, the game will not delete weapons placed in the map.
86 | mp_weapons_allow_zeus 1 // Determines whether the Zeus is purchasable or not.
87 | mp_win_panel_display_time 15 // The amount of time to show the win panel between matches / halfs
88 |
89 | spec_freeze_time 2.0 // Time spend frozen in observer freeze cam.
90 | spec_freeze_panel_extended_time 0 // Time spent with the freeze panel still up after observer freeze cam is done.
91 | spec_freeze_time_lock 2
92 | spec_freeze_deathanim_time 0
93 |
94 | sv_accelerate 5.5 // ( def. "10" ) client notify replicated
95 | sv_stopspeed 80 //
96 | sv_allow_votes 0 // Allow voting?
97 | sv_allow_wait_command 0 // Allow or disallow the wait command on clients connected to this server.
98 | sv_alltalk 0 // Players can hear all other players' voice communication, no team restrictions
99 | sv_alternateticks 0 // If set, server only simulates entities on even numbered ticks.
100 | sv_cheats 0 // Allow cheats on server
101 | sv_clockcorrection_msecs 15 // The server tries to keep each player's m_nTickBase withing this many msecs of the server absolute tickcount
102 | sv_consistency 0 // Whether the server enforces file consistency for critical files
103 | sv_contact 0 // Contact email for server sysop
104 | sv_damage_print_enable 0 // Turn this off to disable the player's damage feed in the console after getting killed.
105 | sv_dc_friends_reqd 0 // Set this to 0 to allow direct connects to a game in progress even if no presents
106 | sv_deadtalk 0 // Dead players can speak (voice, text) to the living
107 | sv_forcepreload 0 // Force server side preloading.
108 | sv_friction 5.2 // World friction.
109 | sv_full_alltalk 0 // Any player (including Spectator team) can speak to any other player
110 | sv_gameinstructor_disable 1 // Force all clients to disable their game instructors.
111 | sv_ignoregrenaderadio 0 // Turn off Fire in the hole messages
112 | sv_kick_players_with_cooldown 0 // (0: do not kick; 1: kick Untrusted players; 2: kick players with any cooldown)
113 | sv_kick_ban_duration 0 // How long should a kick ban from the server should last (in minutes)
114 | sv_lan 0 // Server is a lan server ( no heartbeat, no authentication, no non-class C addresses )
115 | sv_log_onefile 0 // Log server information to only one file.
116 | sv_logbans 1 // Log server bans in the server logs.
117 | sv_logecho 1 // Echo log information to the console.
118 | sv_logfile 1 // Log server information in the log file.
119 | sv_logflush 0 // Flush the log file to disk on each write (slow).
120 | sv_logsdir logfiles // Folder in the game directory where server logs will be stored.
121 | sv_maxrate 0 // min. 0.000000 max. 30000.000000 replicated Max bandwidth rate allowed on server, 0 == unlimited
122 | sv_mincmdrate 30 // This sets the minimum value for cl_cmdrate. 0 == unlimited.
123 | sv_minrate 20000 // Min bandwidth rate allowed on server, 0 == unlimited
124 | sv_competitive_minspec 1 // Enable to force certain client convars to minimum/maximum values to help prevent competitive advantages.
125 | sv_pausable 1 // Is the server pausable.
126 | sv_pure 1
127 | sv_pure_kick_clients 1 // If set to 1, the server will kick clients with mismatching files. Otherwise, it will issue a warning to the client.
128 | sv_pure_trace 0 // If set to 1, the server will print a message whenever a client is verifying a CR
129 | sv_spawn_afk_bomb_drop_time 30 // Players that spawn and don't move for longer than sv_spawn_afk_bomb_drop_time (default 15 seconds) will automatically drop the bomb.
130 | sv_steamgroup_exclusive 0 // If set, only members of Steam group will be able to join the server when it's empty, public people will be able to join the server only if it has players.
131 | sv_voiceenable 0
132 |
133 | say "> ESL CS:GO 2v2 Ladder Config loaded - 14.01.2016 <"
134 |
135 | // Change map
136 | changelevel de_shortdust
137 |
--------------------------------------------------------------------------------
/_scripts/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const ManifestPlugin = require('webpack-manifest-plugin');
4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
5 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
6 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
7 | const SizePlugin = require('size-plugin');
8 |
9 | const projectRoot = path.join(__dirname, '../..');
10 | const buildDirectory = path.join(projectRoot, 'frontend');
11 | const distDirectory = path.join(projectRoot, 'public/dist');
12 |
13 | const prod = process.env.NODE_ENV === "production";
14 |
15 | const config = {
16 | entry: {
17 | main: [
18 | path.join(buildDirectory, 'main.js'),
19 | path.join(buildDirectory, 'scss/style.scss')
20 | ]
21 | },
22 | devtool: !prod ? 'inline-source-map' : '',
23 | output: {
24 | path: distDirectory,
25 | filename: '[name].[hash:6].js'
26 | },
27 | module: {
28 | rules: [
29 | {
30 | enforce: "pre",
31 | test: /\.js$/,
32 | exclude: /node_modules/,
33 | loader: 'eslint-loader',
34 | options: {
35 | failOnError: true,
36 | failOnWarning: false
37 | }
38 | },
39 | {
40 | test: /\.js$/,
41 | exclude: /node_modules/,
42 | use: {
43 | loader: 'babel-loader',
44 | query: {
45 | presets: [
46 | require.resolve('babel-preset-env'),
47 | require.resolve('babel-preset-react')
48 | ],
49 | plugins: [
50 | [require.resolve('babel-plugin-transform-react-jsx'), {pragma: 'h'}]
51 | ]
52 | }
53 | }
54 | },
55 | {
56 | test: /\.scss$/,
57 | use: [
58 | MiniCssExtractPlugin.loader,
59 | {loader: 'css-loader', options: {minimize: prod, url: false, sourceMap: !prod}},
60 | {loader: 'sass-loader', options: {sourceMap: !prod}}
61 | ]
62 | }
63 | ]
64 | },
65 | plugins: [
66 | new MiniCssExtractPlugin({
67 | filename: '[name].[hash:6].css'
68 | }),
69 | new ManifestPlugin({
70 | fileName: 'rev-manifest.json'
71 | }),
72 | new webpack.DefinePlugin({
73 | 'process.env': {
74 | 'NODE_ENV': !prod ? JSON.stringify("development") : JSON.stringify("production")
75 | }
76 | }),
77 | new SizePlugin()
78 | ]
79 | };
80 |
81 | if(prod) {
82 | config.plugins.push(
83 | new UglifyJsPlugin({
84 | uglifyOptions: {
85 | mangle: true,
86 | compress: {
87 | negate_iife: false,
88 | unsafe_comps: true,
89 | properties: true,
90 | keep_fargs: false,
91 | pure_getters: true,
92 | collapse_vars: true,
93 | unsafe: true,
94 | warnings: false,
95 | sequences: true,
96 | dead_code: true,
97 | drop_debugger: true,
98 | comparisons: true,
99 | conditionals: true,
100 | evaluate: true,
101 | booleans: true,
102 | loops: true,
103 | unused: true,
104 | hoist_funs: true,
105 | if_return: true,
106 | join_vars: true,
107 | drop_console: true,
108 | pure_funcs: ['classCallCheck', 'invariant', 'warning']
109 | }
110 | }
111 | }),
112 | new OptimizeCssAssetsPlugin({
113 | assetNameRegExp: /\.css$/g,
114 | cssProcessor: require('cssnano'),
115 | cssProcessorOptions: {
116 | discardComments: {removeAll: true}
117 | }
118 | })
119 | );
120 | }
121 |
122 | module.exports = config;
123 |
--------------------------------------------------------------------------------
/app/src/bundle.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = "production";
2 | require(__dirname + '/server.js');
3 |
--------------------------------------------------------------------------------
/app/src/config/baseConfig.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Check if we are using the dev version
3 | */
4 | const dev = process.env.NODE_ENV !== 'production';
5 |
6 | /**
7 | * Exports the base config
8 | */
9 | module.exports = {
10 | application: {
11 | name: "CSGO Remote",
12 | env: dev ? " (local)" : "",
13 | basePath: "/",
14 | csgoConfigFolder: "./csgo-configs",
15 | host: "0.0.0.0",
16 | port: 3542
17 | },
18 | logger: {
19 | location: "./log",
20 | level: "info"
21 | },
22 | maps: {
23 | competitive: [
24 | "de_inferno",
25 | "de_train",
26 | "de_mirage",
27 | "de_nuke",
28 | "de_overpass",
29 | "de_cache",
30 | "de_dust2"
31 | ],
32 | wingman: [
33 | "de_cbble",
34 | "de_inferno",
35 | "de_lake",
36 | "de_overpass",
37 | "gd_rialto",
38 | "de_shortdust",
39 | "de_shortnuke",
40 | "de_train"
41 | ],
42 | dangerzone: [
43 | "dz_blacksite"
44 | ]
45 | },
46 | integrations: {
47 | challonge: {
48 | enabled: false,
49 | username: "challonge username",
50 | key: "api key",
51 | default_country: "NL"
52 | },
53 | csv: {
54 | default_country: "NL"
55 | }
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/app/src/config/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import base config
3 | */
4 | const baseConfig = require("./baseConfig");
5 |
6 | /**
7 | * Import base packages
8 | */
9 | const fs = require('fs');
10 | const deepmerge = require('deepmerge');
11 |
12 | /**
13 | * Check if we are using the dev version
14 | */
15 | const dev = process.env.NODE_ENV !== 'production';
16 |
17 | /**
18 | * Export the main config
19 | */
20 | try {
21 | module.exports = deepmerge(baseConfig, eval('require')(dev ? __dirname + '/config.json' : process.cwd() + '/config.json'));
22 | } catch (e) {
23 | const config = fs.readFileSync(__dirname + '/../../../_scripts/config/config.build.json', 'utf8');
24 | fs.writeFileSync(dev ? __dirname + '/config.json' : process.cwd() + '/config.json', config);
25 |
26 | module.exports = deepmerge(baseConfig, JSON.parse(config));
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/config/version/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import base packages
3 | */
4 | //eslint-disable-next-line
5 | const fs = require('fs');
6 |
7 | /**
8 | * Export the main config
9 | */
10 | try {
11 | module.exports = eval('fs.readFileSync')(__dirname + '/version.txt', 'utf8').split("\n")[0];
12 | } catch (e) {
13 | module.exports = "__DEV__";
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/controllers/Api/BaseController.js:
--------------------------------------------------------------------------------
1 | class BaseController {
2 | constructor() {
3 |
4 | }
5 |
6 | /**
7 | * Send a response to express
8 | *
9 | * @param response
10 | * @param status
11 | * @param data
12 | * @param contentType
13 | */
14 | jsonResponse(response, status, data, contentType = 'application/json') {
15 | response.type(contentType);
16 | response.status(status);
17 | response.json(data);
18 | }
19 | }
20 |
21 | module.exports = BaseController;
22 |
--------------------------------------------------------------------------------
/app/src/controllers/Api/CsgoController.js:
--------------------------------------------------------------------------------
1 | const baseController = require('./BaseController');
2 | const log = require('../../modules/logger');
3 | const db = require('../../modules/database').db;
4 | const socket = require('../../modules/socket');
5 | const rcon = require('../../modules/rcon');
6 | const {findIndexById, checkServerAvailability} = require('../../utils/Arrays');
7 |
8 | class CsgoController extends baseController {
9 | constructor() {
10 | super();
11 | }
12 |
13 | /**
14 | * Action for the default api route
15 | *
16 | * @param req
17 | * @param res
18 | */
19 | indexAction(req, res) {
20 | const match = checkServerAvailability(`${req.params.ip}:${req.params.port}`, db.getData("/match"));
21 | const index = findIndexById(db.getData("/match"), match.id);
22 |
23 | if(match && index) {
24 | if(typeof match.server_data.locked === "undefined" || match.server_data.locked === false) {
25 | log.info(`[API][${req.params.ip}:${req.params.port}] Server send live score update`);
26 | console.log('JSON.stringify(req.body)', JSON.stringify(req.body));
27 |
28 | // Update general match info on round_update
29 | if(req.body.instruction === "round_update") {
30 | const serverData = {};
31 | serverData.status = req.body.data.status;
32 | serverData.locked = req.body.data.locked;
33 | serverData.match = req.body.data.match;
34 | serverData.killFeed = [];
35 | serverData.CT = {
36 | team_name: !req.body.data.half_time ? req.body.data.ct_name : req.body.data.t_name,
37 | players: []
38 | };
39 | serverData.T = {
40 | team_name: !req.body.data.half_time ? req.body.data.t_name : req.body.data.ct_name,
41 | players: []
42 | };
43 |
44 | for (let player = 0; player < req.body.data.players.length; player++) {
45 | const playerData = req.body.data.players[player];
46 |
47 | if (playerData.team === 3) {
48 | serverData.CT.players.push(playerData);
49 | }
50 |
51 | if (playerData.team === 2) {
52 | serverData.T.players.push(playerData);
53 | }
54 | }
55 |
56 | db.push(`/match[${index}]/server_data`, serverData);
57 | socket.sendGeneralUpdate();
58 | }
59 |
60 | // Update kill feed based on player_killed
61 | if(req.body.instruction === "player_killed") {
62 | //todo implement
63 | console.log('Waiting here!');
64 | }
65 |
66 | // Auto start warmup after knife round
67 | if(req.body.instruction === "round_update") {
68 | if (match.status === 2 && (req.body.data.match.CT === 1 || req.body.data.match.T === 1)) {
69 | log.info(`[API][${req.params.ip}:${req.params.port}] Pausing match since knife round is over!`);
70 | db.push(`/match[${index}]/status`, 20);
71 | rcon.cmd(`${req.params.ip}:${req.params.port}`, "say 'Waiting for admin to start match...'", 'Auto pause match say');
72 | rcon.cmd(`${req.params.ip}:${req.params.port}`, 'exec gamemode_competitive', 'Gamemode update');
73 | rcon.startWarmup(`${req.params.ip}:${req.params.port}`);
74 | }
75 | }
76 |
77 | // Auto restore server after the match is complete
78 | if(req.body.instruction === "round_update") {
79 | if (match.status === 3 && req.body.data.status === "match_end") {
80 | log.info(`[API][${req.params.ip}:${req.params.port}] Restoring server since match is over!`);
81 | db.push(`/match[${index}]/status`, 99);
82 | rcon.reset(`${req.params.ip}:${req.params.port}`);
83 | }
84 | }
85 | }
86 | }
87 |
88 | this.jsonResponse(res, 200, { 'message': 'OK' });
89 | }
90 | }
91 |
92 | module.exports = new CsgoController();
93 |
--------------------------------------------------------------------------------
/app/src/controllers/Api/IndexController.js:
--------------------------------------------------------------------------------
1 | const baseController = require('./BaseController');
2 |
3 | class IndexController extends baseController {
4 | constructor() {
5 | super();
6 | }
7 |
8 | /**
9 | * Action for the default api route
10 | *
11 | * @param req
12 | * @param res
13 | */
14 | indexAction(req, res) {
15 | this.jsonResponse(res, 200, { 'message': 'Default API route!' });
16 | }
17 | }
18 |
19 | module.exports = new IndexController();
20 |
--------------------------------------------------------------------------------
/app/src/controllers/Web/BaseController.js:
--------------------------------------------------------------------------------
1 | const config = require("../../config");
2 | const version = require("../../config/version");
3 | const assets = require("../../modules/assets");
4 |
5 | class BaseController {
6 | constructor() {
7 | this.baseConfig = {
8 | config: config,
9 | protocol: '',
10 | hostname: '',
11 | baseUrl: '',
12 | appName: '',
13 | env: '',
14 | version: '',
15 | assets: {
16 | js: false,
17 | css: false
18 | }
19 | }
20 | }
21 |
22 | /**
23 | * Returns the complete config base + page specific
24 | *
25 | * @param request
26 | * @param pageSpecificConfig
27 | */
28 | mergePageConfig(request, pageSpecificConfig) {
29 | const files = assets();
30 |
31 | this.baseConfig.hostname = request.get('host');
32 | this.baseConfig.protocol = request.protocol;
33 | this.baseConfig.baseUrl = `${request.protocol}://${request.get('host')}${config.application.basePath}`;
34 | this.baseConfig.appName = config.application.name;
35 | this.baseConfig.env = config.application.env;
36 | this.baseConfig.version = version;
37 |
38 | this.baseConfig.assets.js = files["main.js"];
39 | this.baseConfig.assets.css = files["main.css"];
40 |
41 | return Object.assign(this.baseConfig, pageSpecificConfig);
42 | }
43 | }
44 |
45 | module.exports = BaseController;
46 |
--------------------------------------------------------------------------------
/app/src/controllers/Web/IndexController.js:
--------------------------------------------------------------------------------
1 | const baseController = require('./BaseController');
2 |
3 | class IndexController extends baseController {
4 | /**
5 | * Renders the Home page
6 | *
7 | * @param req
8 | * @param res
9 | */
10 | indexAction(req, res) {
11 | res.render('index', this.mergePageConfig(req, {
12 | template: 'index/index',
13 | pageTitle: 'Home'
14 | }));
15 | }
16 |
17 | /**
18 | * Renders the 404 page
19 | *
20 | * @param req
21 | * @param res
22 | */
23 | notFoundAction(req, res) {
24 | res.render('index', this.mergePageConfig(req, {
25 | template: 'general/notfound',
26 | pageTitle: 'Not Found'
27 | }));
28 | }
29 | }
30 |
31 | module.exports = new IndexController();
32 |
--------------------------------------------------------------------------------
/app/src/log/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glenndehaan/csgo-rcon-nodejs/8a4529da6041bc44b9935a0fa5e25e4c4d42d61e/app/src/log/.gitkeep
--------------------------------------------------------------------------------
/app/src/modules/assets.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const path = `${__dirname}/../../../public/dist`;
3 |
4 | /**
5 | * Function to get the active asset files for the frontend
6 | *
7 | * @return {any}
8 | */
9 | module.exports = () => {
10 | return JSON.parse(fs.existsSync(path) ? fs.readFileSync(`${path}/rev-manifest.json`) : "{}");
11 | };
12 |
--------------------------------------------------------------------------------
/app/src/modules/challonge.js:
--------------------------------------------------------------------------------
1 | const uuidv4 = require('uuid/v4');
2 | const fetch = require("node-fetch");
3 | const log = require("../modules/logger");
4 | const db = require("../modules/database").db;
5 | const {findByChallonge, getAllChallonge} = require("../utils/Arrays");
6 | const {findServerConfig, findServerConfigIndex} = require("../utils/Strings");
7 | const config = require("../config");
8 |
9 | class challonge {
10 | constructor() {
11 | this.tournaments = [];
12 | }
13 |
14 | /**
15 | * Get all tournaments from Challonge
16 | */
17 | init() {
18 | fetch(`https://${config.integrations.challonge.username}:${config.integrations.challonge.key}@api.challonge.com/v1/tournaments.json`)
19 | .then(res => res.json())
20 | .then(body => {
21 | if (body.length > 0) {
22 | this.tournaments = body;
23 | } else {
24 | this.tournaments = [];
25 | }
26 |
27 | log.info(`[CHALLONGE] ${this.tournaments.length} Tournament(s) found!`);
28 | })
29 | .catch((error) => {
30 | log.error(`[CHALLONGE] Error: ${error}`);
31 | });
32 | }
33 |
34 | /**
35 | * Import all matches from a tournament into the DB
36 | *
37 | * @param tournamentId
38 | * @param server
39 | * @param maxGames
40 | * @param gameMode
41 | * @param knifeConfig
42 | * @param mainConfig
43 | * @param matchGroup
44 | * @param callback
45 | */
46 | importMatches(tournamentId, server, maxGames, gameMode, knifeConfig, mainConfig, matchGroup, callback) {
47 | fetch(`https://${config.integrations.challonge.username}:${config.integrations.challonge.key}@api.challonge.com/v1/tournaments/${tournamentId}/matches.json`)
48 | .then(res => res.json())
49 | .then(body => {
50 | const dbData = db.getData("/match");
51 | let imported = 0;
52 | let completed = 0;
53 |
54 | for (let item = 0; item < body.length; item++) {
55 | const exists = findByChallonge(dbData, body[item].match.id);
56 | const matchId = body[item].match.id;
57 | const teamId1 = body[item].match.player1_id;
58 | const teamId2 = body[item].match.player2_id;
59 |
60 | if (!exists) {
61 | if(teamId1 !== null && teamId2 !== null) {
62 | imported++;
63 |
64 | this.getTeamName(tournamentId, teamId1, (teamName1) => {
65 | this.getTeamName(tournamentId, teamId2, (teamName2) => {
66 | let serverDetails = false;
67 |
68 | if(server !== "next") {
69 | serverDetails = findServerConfig(server);
70 | } else {
71 | const dbMatches = getAllChallonge();
72 |
73 | if(dbMatches.length > 0) {
74 | const server = findServerConfigIndex(dbMatches[dbMatches.length - 1].server);
75 | let serverIndex = 0;
76 |
77 | if((server + 1) < config.servers.length) {
78 | serverIndex = server + 1;
79 | }
80 |
81 | serverDetails = findServerConfig(`${config.servers[serverIndex].ip}:${config.servers[serverIndex].port}`);
82 | } else {
83 | serverDetails = findServerConfig(`${config.servers[0].ip}:${config.servers[0].port}`);
84 | }
85 | }
86 |
87 | db.push("/match[]", {
88 | id: uuidv4(),
89 | team1: {
90 | name: teamName1,
91 | country: config.integrations.challonge.default_country
92 | },
93 | team2: {
94 | name: teamName2,
95 | country: config.integrations.challonge.default_country
96 | },
97 | match_group: matchGroup,
98 | map: serverDetails.default_map,
99 | max_games: parseInt(maxGames),
100 | game_mode: gameMode,
101 | knife_config: knifeConfig,
102 | match_config: mainConfig,
103 | server: `${serverDetails.ip}:${serverDetails.port}`,
104 | status: 0,
105 | challonge: matchId,
106 | server_data: false
107 | });
108 |
109 | completed++;
110 |
111 | if(imported === completed) {
112 | callback(imported);
113 | log.info(`[CHALLONGE] ${body.length} Matches(s) found! ${imported} Matches(s) imported!`);
114 | }
115 | });
116 | });
117 | }
118 | }
119 | }
120 |
121 | if(imported === 0) {
122 | callback(imported);
123 | log.info(`[CHALLONGE] No new matches(s) found!`);
124 | }
125 | })
126 | .catch((error) => {
127 | log.error(`[CHALLONGE] Error: ${error}`);
128 | });
129 | }
130 |
131 | /**
132 | * Returns the team name
133 | *
134 | * @param tournamentId
135 | * @param teamId
136 | * @param callback
137 | */
138 | getTeamName(tournamentId, teamId, callback) {
139 | fetch(`https://${config.integrations.challonge.username}:${config.integrations.challonge.key}@api.challonge.com/v1/tournaments/${tournamentId}/participants/${teamId}.json`)
140 | .then(res => res.json())
141 | .then(body => {
142 | callback(body.participant.name);
143 | })
144 | .catch((error) => {
145 | log.error(`[CHALLONGE] Error: ${error}`);
146 | callback(false);
147 | });
148 | }
149 | }
150 |
151 | module.exports = new challonge();
152 |
--------------------------------------------------------------------------------
/app/src/modules/csgoConfig.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const log = require("./logger");
3 | const config = require("../config");
4 |
5 | class csgoConfig {
6 | /**
7 | * Constructor
8 | */
9 | constructor() {
10 | this.dev = process.env.NODE_ENV !== 'production';
11 | }
12 |
13 | /**
14 | * Writes some default csgo configs to the filesystem
15 | */
16 | init() {
17 | if (!fs.existsSync(`${process.cwd()}/${config.application.csgoConfigFolder}`)) {
18 | fs.mkdirSync(`${process.cwd()}/${config.application.csgoConfigFolder}`);
19 | fs.mkdirSync(`${process.cwd()}/${config.application.csgoConfigFolder}/main`);
20 | fs.mkdirSync(`${process.cwd()}/${config.application.csgoConfigFolder}/knife`);
21 |
22 | const main_files = fs.readdirSync(`${__dirname}/../../../_scripts/csgo-configs/main`);
23 | const knife_files = fs.readdirSync(`${__dirname}/../../../_scripts/csgo-configs/knife`);
24 |
25 | for (let main = 0; main < main_files.length; main++) {
26 | log.info(`[CSGO-CONFIG] Copy file: ${__dirname}/../../../_scripts/csgo-configs/main -> ${process.cwd()}/${config.application.csgoConfigFolder}/main/${main_files[main]} `);
27 |
28 | fs.writeFileSync(`${process.cwd()}/${config.application.csgoConfigFolder}/main/${main_files[main]}`, fs.readFileSync(`${__dirname}/../../../_scripts/csgo-configs/main/${main_files[main]}`, 'utf8'));
29 | }
30 |
31 | for (let knife = 0; knife < knife_files.length; knife++) {
32 | log.info(`[CSGO-CONFIG] Copy file: ${__dirname}/../../../_scripts/csgo-configs/knife/${knife_files[knife]} -> ${process.cwd()}/${config.application.csgoConfigFolder}/knife/${knife_files[knife]}`);
33 |
34 | fs.writeFileSync(`${process.cwd()}/${config.application.csgoConfigFolder}/knife/${knife_files[knife]}`, fs.readFileSync(`${__dirname}/../../../_scripts/csgo-configs/knife/${knife_files[knife]}`, 'utf8'));
35 | }
36 | }
37 | }
38 |
39 | /**
40 | * Grabs CSGO configs from file system
41 | *
42 | * @param callback
43 | */
44 | index(callback) {
45 | fs.readdir(`${this.dev ? __dirname + '/..' : process.cwd()}/${config.application.csgoConfigFolder}/main`, (err, main_files) => {
46 | if (err) {
47 | throw err;
48 | }
49 |
50 | for(let item = 0; item < main_files.length; item++) {
51 | main_files[item] = main_files[item].replace(/\.[^/.]+$/, "");
52 | }
53 |
54 | fs.readdir(`${this.dev ? __dirname + '/..' : process.cwd()}/${config.application.csgoConfigFolder}/knife`, (err, knife_files) => {
55 | if (err) {
56 | throw err;
57 | }
58 |
59 | for(let item = 0; item < knife_files.length; item++) {
60 | knife_files[item] = knife_files[item].replace(/\.[^/.]+$/, "");
61 | }
62 |
63 | const files = {
64 | main: main_files,
65 | knife: knife_files
66 | };
67 |
68 | callback(files);
69 | });
70 | });
71 | }
72 |
73 | /**
74 | * Grabs CSGO configs from file system
75 | *
76 | * @param config_name
77 | * @param type
78 | * @param callback
79 | */
80 | load(config_name, type = "main", callback) {
81 | fs.readFile(`${this.dev ? __dirname + '/..' : process.cwd()}/${config.application.csgoConfigFolder}/${type}/${config_name}.txt`, 'utf8', (err, data) => {
82 | if (err) {
83 | throw err;
84 | }
85 |
86 | callback(this.process(data));
87 | });
88 | }
89 |
90 | /**
91 | * Removes all comments and other unneeded stuff
92 | *
93 | * @param data
94 | * @return array
95 | */
96 | process(data) {
97 | data = data.replace(/^\/\/.*$/m, '');
98 | data = data.split("\n");
99 | const newData = [];
100 | for (let i = 0; i < data.length; i += 1) {
101 | const line = data[i].trim();
102 | const segments = line.split(' ');
103 |
104 | if(segments[0] === 'say') {
105 | newData.push(line);
106 | } else if (segments[0] !== '' && segments[0] !== '//') {
107 | newData.push(`${segments[0]} ${segments[1].split('\t')[0]}`);
108 | }
109 | }
110 |
111 | return newData;
112 | }
113 | }
114 |
115 | module.exports = new csgoConfig();
116 |
--------------------------------------------------------------------------------
/app/src/modules/database.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import base packages
3 | */
4 | const JsonDB = require('node-json-db');
5 | const log = require("./logger");
6 |
7 | class database {
8 | /**
9 | * Constructor
10 | */
11 | constructor() {
12 | this.db = new JsonDB('csgo-rcon', true, false);
13 | }
14 |
15 | /**
16 | * Initial function
17 | */
18 | init() {
19 | /**
20 | * Init the DB object if we launch the app for the first time
21 | */
22 | if(Object.keys(this.db.getData("/")).length === 0 && this.db.getData("/").constructor === Object){
23 | this.db.push("/match", []);
24 | this.db.push("/group", []);
25 |
26 | log.info("[DATABASE] Initialize database for the first time!");
27 | }
28 |
29 | log.info("[DATABASE] Ready!");
30 | }
31 | }
32 |
33 | module.exports = new database();
34 |
--------------------------------------------------------------------------------
/app/src/modules/logger.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import base packages
3 | */
4 | const fs = require('fs');
5 | const config = require("../config");
6 |
7 | /**
8 | * Check if we are using the dev version
9 | */
10 | const dev = process.env.NODE_ENV !== 'production';
11 |
12 | /**
13 | * Create callback storage for socket
14 | */
15 | const callbacks = [];
16 |
17 | /**
18 | * Create log dir if it doesn't exists
19 | */
20 | if (!fs.existsSync(`${dev ? __dirname + '/../' : process.cwd() + '/'}${config.logger.location}`)){
21 | fs.mkdirSync(`${dev ? __dirname + '/../' : process.cwd() + '/'}${config.logger.location}`);
22 | }
23 |
24 | /**
25 | * Setup logger
26 | */
27 | const log = require('simple-node-logger').createSimpleLogger({
28 | logFilePath: `${dev ? __dirname + '/../' : process.cwd() + '/'}${config.logger.location}/server.log`,
29 | timestampFormat: 'YYYY-MM-DD HH:mm:ss.SSS'
30 | });
31 |
32 | /**
33 | * Set log level from config
34 | */
35 | log.setLevel(config.logger.level);
36 |
37 | /**
38 | * Fix zero prefixing
39 | *
40 | * @param number
41 | * @return {*}
42 | */
43 | const fixTimeDateCalculation = (number) => {
44 | if (number <= 9) {
45 | return `0${number}`;
46 | }
47 |
48 | return number;
49 | };
50 |
51 | /**
52 | * Fix zero prefixing
53 | *
54 | * @param number
55 | * @return {*}
56 | */
57 | const fixMilisecondsCalculation = (number) => {
58 | if (number <= 9) {
59 | return `00${number}`;
60 | }
61 |
62 | if (number <= 99) {
63 | return `0${number}`;
64 | }
65 |
66 | return number;
67 | };
68 |
69 | /**
70 | * Return the current time/date string
71 | *
72 | * @return {string}
73 | */
74 | const currentDateTime = () => {
75 | const current = new Date();
76 | const date = `${current.getFullYear()}-${fixTimeDateCalculation(current.getMonth() + 1)}-${fixTimeDateCalculation(current.getDate())}`;
77 | const time = `${fixTimeDateCalculation(current.getHours())}:${fixTimeDateCalculation(current.getMinutes())}:${fixTimeDateCalculation(current.getSeconds())}.${fixMilisecondsCalculation(current.getMilliseconds())}`;
78 |
79 | return `${date} ${time}`;
80 | };
81 |
82 | /**
83 | * Add a callback to the callback storage
84 | *
85 | * @param callback
86 | */
87 | const addCallback = (callback) => {
88 | callbacks.push(callback);
89 | };
90 |
91 | /**
92 | * Trace provider
93 | */
94 | const trace = (message) => {
95 | log.trace(message);
96 |
97 | for(let item = 0; item < callbacks.length; item++) {
98 | callbacks[item](`${currentDateTime()} TRACE ${message}`);
99 | }
100 | };
101 |
102 | /**
103 | * Debug provider
104 | */
105 | const debug = (message) => {
106 | log.debug(message);
107 |
108 | for(let item = 0; item < callbacks.length; item++) {
109 | callbacks[item](`${currentDateTime()} DEBUG ${message}`);
110 | }
111 | };
112 |
113 | /**
114 | * Debug provider
115 | */
116 | const info = (message) => {
117 | log.info(message);
118 |
119 | for(let item = 0; item < callbacks.length; item++) {
120 | callbacks[item](`${currentDateTime()} INFO ${message}`);
121 | }
122 | };
123 |
124 | /**
125 | * Warn provider
126 | */
127 | const warn = (message) => {
128 | log.warn(message);
129 |
130 | for(let item = 0; item < callbacks.length; item++) {
131 | callbacks[item](`${currentDateTime()} WARN ${message}`);
132 | }
133 | };
134 |
135 | /**
136 | * Error provider
137 | */
138 | const error = (message) => {
139 | log.error(message);
140 |
141 | for(let item = 0; item < callbacks.length; item++) {
142 | callbacks[item](`${currentDateTime()} ERROR ${message}`);
143 | }
144 | };
145 |
146 | /**
147 | * Fatal provider
148 | */
149 | const fatal = (message) => {
150 | log.fatal(message);
151 |
152 | for(let item = 0; item < callbacks.length; item++) {
153 | callbacks[item](`${currentDateTime()} FATAL ${message}`);
154 | }
155 | };
156 |
157 | module.exports = {
158 | trace,
159 | debug,
160 | info,
161 | warn,
162 | error,
163 | fatal,
164 | addCallback
165 | };
166 |
--------------------------------------------------------------------------------
/app/src/modules/queue.js:
--------------------------------------------------------------------------------
1 | const log = require("./logger");
2 | const config = require("../config");
3 |
4 | class queue {
5 | /**
6 | * Constructor
7 | */
8 | constructor() {
9 | this.queueFailMax = 35;
10 | this.activeQueue = {};
11 | this.commandRunning = {};
12 | this.queueFailCurrent = {};
13 |
14 | this.init();
15 | }
16 |
17 | /**
18 | * Init function to add the servers to the global objects
19 | */
20 | init() {
21 | for (let item = 0; item < config.servers.length; item++) {
22 | this.activeQueue[`${config.servers[item].ip}:${config.servers[item].port}`] = [];
23 | this.commandRunning[`${config.servers[item].ip}:${config.servers[item].port}`] = false;
24 | this.queueFailCurrent[`${config.servers[item].ip}:${config.servers[item].port}`] = 0;
25 | }
26 |
27 | /**
28 | * Loop over the commands and execute them when possible
29 | */
30 | setInterval(() => {
31 | for (let item = 0; item < config.servers.length; item++) {
32 | if (this.activeQueue[`${config.servers[item].ip}:${config.servers[item].port}`].length > 0) {
33 | if (!this.commandRunning[`${config.servers[item].ip}:${config.servers[item].port}`] || this.queueFailCurrent[`${config.servers[item].ip}:${config.servers[item].port}`] === this.queueFailMax) {
34 | this.commandRunning[`${config.servers[item].ip}:${config.servers[item].port}`] = true;
35 |
36 | this.activeQueue[`${config.servers[item].ip}:${config.servers[item].port}`][0]();
37 | this.activeQueue[`${config.servers[item].ip}:${config.servers[item].port}`].splice(0, 1);
38 |
39 | if (this.queueFailCurrent[`${config.servers[item].ip}:${config.servers[item].port}`] === this.queueFailMax) {
40 | this.queueFailCurrent[`${config.servers[item].ip}:${config.servers[item].port}`] = 0;
41 | this.complete(`${config.servers[item].ip}:${config.servers[item].port}`);
42 |
43 | log.warn(`[QUEUE][${config.servers[item].ip}:${config.servers[item].port}] Max Queue Fail reached! Cleaning up...`);
44 | }
45 | } else {
46 | this.queueFailCurrent[`${config.servers[item].ip}:${config.servers[item].port}`]++;
47 | }
48 | }
49 | }
50 | }, 10);
51 | }
52 |
53 | /**
54 | * Function to add a command to the queue
55 | *
56 | * @param server string
57 | * @param command Function
58 | */
59 | add(server, command) {
60 | this.activeQueue[server].push(command);
61 | }
62 |
63 | /**
64 | * Function that must be run when a command is done
65 | *
66 | * @param server
67 | */
68 | complete(server) {
69 | this.commandRunning[server] = false;
70 | }
71 | }
72 |
73 | module.exports = new queue();
74 |
--------------------------------------------------------------------------------
/app/src/modules/router.js:
--------------------------------------------------------------------------------
1 | const config = require("../config");
2 |
3 | class router {
4 | /**
5 | * An easy to use function to add multiple routes to the Express router
6 | *
7 | * @param router
8 | * @param routes
9 | * @param type
10 | */
11 | routesToRouter(router, routes, type) {
12 | for (let item = 0; item < routes.length; item += 1) {
13 | const route = routes[item];
14 | const controller = route.controller.charAt(0).toUpperCase() + route.controller.slice(1);
15 | let auth = '';
16 | if (route.secured) {
17 | auth = `basicAuth({users:{${config.authentication.username}:'${config.authentication.password}'},challenge: true}),`;
18 | }
19 |
20 | eval(
21 | `
22 | ${route.secured ? 'const basicAuth = require("express-basic-auth");' : ''}
23 | const ${route.controller}Controller = require('../controllers/${type}/${controller}Controller');
24 | router.${route.method}('${route.route}', ${auth} (req, res) => {
25 | ${route.controller}Controller.${route.action}Action(req, res);
26 | });
27 | `
28 | );
29 | }
30 | }
31 | }
32 |
33 | module.exports = new router();
34 |
--------------------------------------------------------------------------------
/app/src/modules/web.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import base packages
3 | */
4 | const express = require('express');
5 | const app = express();
6 | const bodyParser = require('body-parser');
7 | const compression = require('compression');
8 |
9 | /**
10 | * Import own packages
11 | */
12 | const log = require("./logger");
13 | const config = require("../config");
14 | const socket = require("./socket");
15 | const webRouter = require('../routers/Web');
16 | const apiRouter = require('../routers/Api');
17 | const indexController = require('../controllers/Web/IndexController');
18 |
19 | class web {
20 | /**
21 | * Init the express app
22 | */
23 | init() {
24 | /**
25 | * Trust proxy
26 | */
27 | app.enable('trust proxy');
28 |
29 | /**
30 | * Set template engine
31 | */
32 | app.set('view engine', 'ejs');
33 | app.set('views', `${__dirname}/../views`);
34 |
35 | /**
36 | * Setup socket
37 | */
38 | socket.init(app);
39 |
40 | /**
41 | * Enable compression
42 | */
43 | app.use(compression({ threshold: 0 }));
44 |
45 | /**
46 | * Serve static public dir
47 | */
48 | app.use(express.static(`${__dirname}/../../../public`));
49 |
50 | /**
51 | * Configure app to use bodyParser()
52 | */
53 | app.use(bodyParser.urlencoded({extended: true}));
54 | app.use(bodyParser.json());
55 |
56 | /**
57 | * Request logger
58 | */
59 | app.use((req, res, next) => {
60 | log.trace(`[WEB][REQUEST]: ${req.originalUrl}`);
61 | next();
62 | });
63 |
64 | /**
65 | * Configure routers
66 | */
67 | app.use('/', webRouter.router);
68 | app.use('/api', apiRouter.router);
69 |
70 | /**
71 | * Setup default 404 message
72 | */
73 | app.use((req, res) => {
74 | res.status(404);
75 |
76 | // respond with json
77 | if (req.originalUrl.split('/')[1] === 'api') {
78 |
79 | /**
80 | * API 404 not found
81 | */
82 | res.send({error: 'This API route is not implemented yet'});
83 | return;
84 | }
85 |
86 | indexController.notFoundAction(req, res);
87 | });
88 |
89 | /**
90 | * Disable powered by header for security reasons
91 | */
92 | app.disable('x-powered-by');
93 |
94 | /**
95 | * Start listening on port
96 | */
97 | app.listen(config.application.port, config.application.host, () => {
98 | log.info(`[WEB] App is running on: ${config.application.host}:${config.application.port}`);
99 | });
100 | }
101 | }
102 |
103 | module.exports = new web();
104 |
--------------------------------------------------------------------------------
/app/src/routers/Api.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import base packages
3 | */
4 | const express = require('express');
5 | const router = express.Router();
6 | const routerUtils = require('../modules/router');
7 |
8 | /**
9 | * Define routes
10 | */
11 | const routes = [
12 | {
13 | route: '/',
14 | method: 'get',
15 | controller: 'Index',
16 | action: 'index'
17 | },
18 | {
19 | route: '/csgo/:ip/:port',
20 | method: 'post',
21 | controller: 'Csgo',
22 | action: 'index'
23 | }
24 | ];
25 |
26 | routerUtils.routesToRouter(router, routes, 'Api');
27 |
28 | module.exports = {router, routes};
29 |
--------------------------------------------------------------------------------
/app/src/routers/Web.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import base packages
3 | */
4 | const express = require('express');
5 | const router = express.Router();
6 | const routerUtils = require('../modules/router');
7 |
8 | /**
9 | * Define routes
10 | */
11 | const routes = [
12 | {
13 | route: '/',
14 | method: 'get',
15 | controller: 'Index',
16 | action: 'index'
17 | },
18 | {
19 | route: '/servers',
20 | method: 'get',
21 | controller: 'Index',
22 | action: 'index'
23 | },
24 | {
25 | route: '/match/create',
26 | method: 'get',
27 | controller: 'Index',
28 | action: 'index'
29 | },
30 | {
31 | route: '/match/:id',
32 | method: 'get',
33 | controller: 'Index',
34 | action: 'index'
35 | },
36 | {
37 | route: '/match/:id/edit',
38 | method: 'get',
39 | controller: 'Index',
40 | action: 'index'
41 | },
42 | {
43 | route: '/settings',
44 | method: 'get',
45 | controller: 'Index',
46 | action: 'index',
47 | secured: true
48 | },
49 | {
50 | route: '/settings/log',
51 | method: 'get',
52 | controller: 'Index',
53 | action: 'index',
54 | secured: true
55 | },
56 | {
57 | route: '/about',
58 | method: 'get',
59 | controller: 'Index',
60 | action: 'index'
61 | }
62 | ];
63 |
64 | routerUtils.routesToRouter(router, routes, 'Web');
65 |
66 | module.exports = {router, routes};
67 |
--------------------------------------------------------------------------------
/app/src/server.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import external modules
3 | */
4 | const fs = require("fs");
5 |
6 | /**
7 | * Import own modules
8 | */
9 | const version = require("./config/version");
10 | const config = require("./config");
11 | const log = require("./modules/logger");
12 | const database = require("./modules/database");
13 | const csgoConfig = require("./modules/csgoConfig");
14 | const queue = require("./modules/queue");
15 | const web = require("./modules/web");
16 | const challonge = require("./modules/challonge");
17 |
18 | /**
19 | * Hack since the srcds-rcon package isn't handling rejections
20 | */
21 | process.on('unhandledRejection', () => {});
22 |
23 | /**
24 | * Check if we are running as dev
25 | */
26 | const dev = process.env.NODE_ENV !== 'production';
27 |
28 | /**
29 | * Init modules
30 | */
31 | if(!dev) {
32 | csgoConfig.init();
33 | }
34 | database.init();
35 |
36 | log.info("[SYSTEM] App running");
37 | log.info(`[SYSTEM] Version: ${version}`);
38 | log.info(`[SYSTEM] Support and Help: https://github.com/glenndehaan/csgo-rcon-nodejs`);
39 |
40 | /**
41 | * Check if this is the first time running the app
42 | */
43 | if(!dev) {
44 | if (!fs.existsSync(`${process.cwd()}/LICENCE`) || !fs.existsSync(`${process.cwd()}/README.md`)) {
45 | fs.writeFileSync(process.cwd() + '/LICENCE', fs.readFileSync(__dirname + '/../../LICENCE', 'utf8'));
46 | fs.writeFileSync(process.cwd() + '/README.md', fs.readFileSync(__dirname + '/../../README.md', 'utf8'));
47 |
48 | log.info("------------------------------------------");
49 | log.info("Hi and thank you for using this piece of software!");
50 | log.info("Go ahead and update the config.json to your needs then relaunch the software!");
51 | log.info("The software will close in 5 seconds!");
52 | log.info("------------------------------------------");
53 |
54 | setTimeout(() => {
55 | process.exit(0);
56 | }, 5000)
57 | } else {
58 | queue.init();
59 | web.init();
60 | if(config.integrations.challonge.enabled) {
61 | challonge.init();
62 | }
63 | }
64 | } else {
65 | queue.init();
66 | web.init();
67 | if(config.integrations.challonge.enabled) {
68 | challonge.init();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/utils/Arrays.js:
--------------------------------------------------------------------------------
1 | const database = require("../modules/database").db;
2 |
3 | /**
4 | * Find index by id in an array
5 | *
6 | * @param array
7 | * @param id
8 | * @return bool|object
9 | */
10 | function findIndexById(array, id) {
11 | for(let item = 0; item < array.length; item++) {
12 | if(array[item].id === id) {
13 | return item;
14 | }
15 | }
16 |
17 | return false;
18 | }
19 |
20 | /**
21 | * Find the challonge param in an array
22 | *
23 | * @param array
24 | * @param challonge
25 | * @return bool|object
26 | */
27 | function findByChallonge(array, challonge) {
28 | for(let item = 0; item < array.length; item++) {
29 | if(array[item].challonge === challonge) {
30 | return array[item];
31 | }
32 | }
33 |
34 | return false;
35 | }
36 |
37 | /**
38 | * Get all matches from challonge that are stored in the DB
39 | *
40 | * @return {*}
41 | */
42 | function getAllChallonge() {
43 | const matches = database.getData("/match");
44 | const challongeMatches = [];
45 |
46 | for(let item = 0; item < matches.length; item++) {
47 | if(matches[item].challonge !== false) {
48 | challongeMatches.push(matches[item]);
49 | }
50 | }
51 |
52 | return challongeMatches;
53 | }
54 |
55 | /**
56 | * Checks if a server is in use by a match
57 | *
58 | * @param server
59 | * @param matches
60 | * @return {*}
61 | */
62 | function checkServerAvailability(server, matches) {
63 | for(let item = 0; item < matches.length; item++) {
64 | const match = matches[item];
65 |
66 | if(match.status > 0 && match.status < 99 && match.server === server) {
67 | return match;
68 | }
69 | }
70 |
71 | return false;
72 | }
73 |
74 | module.exports = {findIndexById, findByChallonge, getAllChallonge, checkServerAvailability};
75 |
--------------------------------------------------------------------------------
/app/src/utils/Strings.js:
--------------------------------------------------------------------------------
1 | const config = require("../config");
2 |
3 | /**
4 | * Function to split the string by byte length
5 | *
6 | * @param data
7 | * @param length
8 | * @param lineEndChar
9 | * @return {*}
10 | */
11 | function splitByByteLength(data, length = 1024, lineEndChar = '') {
12 | const lines = data;
13 | const exportedLines = [];
14 | let index = 0;
15 |
16 | for(let item = 0; item < lines.length; item++) {
17 | if(typeof exportedLines[index] === "undefined") {
18 | exportedLines[index] = "";
19 | }
20 |
21 | const lineFormatted = `${lines[item]}${lineEndChar}`;
22 | const lineBytes = Buffer.byteLength(lineFormatted, 'utf8');
23 | const bufferBytes = Buffer.byteLength(exportedLines[index], 'utf8');
24 |
25 | if((bufferBytes + lineBytes) < length) {
26 | exportedLines[index] += lineFormatted;
27 | } else {
28 | index++;
29 | }
30 | }
31 |
32 | return exportedLines;
33 | }
34 |
35 | /**
36 | * Splits a string by line break and returns an array
37 | *
38 | * @param data
39 | * @return {*}
40 | */
41 | function splitByLinkBreak(data) {
42 | return data.split('\n');
43 | }
44 |
45 | /**
46 | * Function find the server config that belongs to the server name
47 | *
48 | * @param server
49 | * @return object
50 | */
51 | function findServerConfig(server) {
52 | for(let item = 0; item < config.servers.length; item++) {
53 | const splitted = server.split(":");
54 |
55 | if(config.servers[item].ip === splitted[0] && config.servers[item].port === parseInt(splitted[1])) {
56 | return config.servers[item];
57 | }
58 | }
59 |
60 | return {};
61 | }
62 |
63 | /**
64 | * Function find the server config that belongs to the server name
65 | *
66 | * @param server
67 | * @return int
68 | */
69 | function findServerConfigIndex(server) {
70 | for(let item = 0; item < config.servers.length; item++) {
71 | const splitted = server.split(":");
72 |
73 | if(config.servers[item].ip === splitted[0] && config.servers[item].port === parseInt(splitted[1])) {
74 | return item;
75 | }
76 | }
77 |
78 | return 0;
79 | }
80 |
81 | module.exports = {splitByByteLength, splitByLinkBreak, findServerConfig, findServerConfigIndex};
82 |
--------------------------------------------------------------------------------
/app/src/views/general/notfound.ejs:
--------------------------------------------------------------------------------
1 |
404 Not Found !
2 |
--------------------------------------------------------------------------------
/app/src/views/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <% include ./partials/head.ejs %>
5 |
6 |
7 |
30 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/views/index/index.ejs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glenndehaan/csgo-rcon-nodejs/8a4529da6041bc44b9935a0fa5e25e4c4d42d61e/app/src/views/index/index.ejs
--------------------------------------------------------------------------------
/app/src/views/partials/head.ejs:
--------------------------------------------------------------------------------
1 | <%= pageTitle %> | <%= config.application.name %> <%= config.application.env %>
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
31 |
32 |
33 |
36 |
--------------------------------------------------------------------------------
/app/test/functions.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import test suite
3 | */
4 | const should = require('should');
5 | const request = require('request');
6 |
7 | /**
8 | * Import packages needed for tests
9 | */
10 | const log = require("../src/modules/logger");
11 | const database = require("../src/modules/database");
12 | const csgoConfig = require("../src/modules/csgoConfig");
13 | const challonge = require("../src/modules/challonge");
14 |
15 | /**
16 | * Launch test
17 | */
18 | describe("APP - Functions", () => {
19 | it('./modules/logger should have a all logger functions', (done) => {
20 | log.trace.should.be.an.Function();
21 | log.debug.should.be.an.Function();
22 | log.info.should.be.an.Function();
23 | log.warn.should.be.an.Function();
24 | log.error.should.be.an.Function();
25 | log.fatal.should.be.an.Function();
26 | done();
27 | });
28 |
29 | it('./modules/database should have an init function', (done) => {
30 | database.init.should.be.a.Function();
31 | done();
32 | });
33 |
34 | it('./modules/csgoConfig should have an init function', (done) => {
35 | csgoConfig.init.should.be.a.Function();
36 | done();
37 | });
38 |
39 | it('./modules/challonge should have an init function', (done) => {
40 | challonge.init.should.be.a.Function();
41 | done();
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/frontend/components/About.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import fetch from 'unfetch';
3 | import {connect} from "unistore/preact";
4 |
5 | class About extends Component {
6 | /**
7 | * Constructor
8 | */
9 | constructor() {
10 | super();
11 |
12 | this.state = {
13 | currentCommit: window.expressConfig.version,
14 | latestCommit: false,
15 | dev: process.env.NODE_ENV !== 'production'
16 | };
17 | }
18 |
19 | /**
20 | * Runs then component mounts
21 | */
22 | componentDidMount() {
23 | this.updateGeneralPageData();
24 | this.checkCurrentVersion();
25 | }
26 |
27 | /**
28 | * Runs when component updates
29 | */
30 | componentDidUpdate() {
31 | this.updateGeneralPageData();
32 | }
33 |
34 | /**
35 | * Updates some general page data
36 | */
37 | updateGeneralPageData() {
38 | document.title = `${this.props.lang.about.title} | ${window.expressConfig.appName} ${window.expressConfig.env}`;
39 | window.events.emit('breadcrumbs', [
40 | {
41 | "name": this.props.lang.home.title,
42 | "url": "/"
43 | },
44 | {
45 | "name": this.props.lang.about.title,
46 | "url": false
47 | }
48 | ]);
49 | }
50 |
51 | /**
52 | * Fetch the latest version from GitHub
53 | */
54 | checkCurrentVersion() {
55 | fetch('https://api.github.com/repos/glenndehaan/csgo-rcon-nodejs/releases')
56 | .then(r => r.json())
57 | .then(data => {
58 | if(data.length > 0) {
59 | if(data[0].tag_name) {
60 | this.setState({
61 | latestCommit: data[0].tag_name
62 | })
63 | }
64 | }
65 | })
66 | }
67 |
68 | /**
69 | * Preact render function
70 | *
71 | * @returns {*}
72 | */
73 | render() {
74 | return (
75 |
76 |
{this.props.lang.about.subtitle}
77 | {this.state.dev &&
Warning: {this.props.lang.about.devWarning}
}
78 | {this.state.currentCommit !== this.state.latestCommit &&
Warning: {this.props.lang.about.oldWarning}
}
79 | {(this.state.currentCommit !== this.state.latestCommit || this.state.dev) &&
}
80 |
81 |
{this.props.lang.about.descriptionTitle}
82 | {this.props.lang.about.description}
83 |
84 |
85 |
86 |
{this.props.lang.about.version}
87 | {this.props.lang.about.currentVersion}: {this.state.dev ? '__DEV__' : this.state.currentCommit}
88 | {this.props.lang.about.latestVersion}: {!this.state.latestCommit ? 'Checking...' : this.state.latestCommit}
89 |
90 |
91 |
92 |
{this.props.lang.about.contributors}
93 | glenndehaan (
GitHub)
94 | ChrisEKN (
GitHub)
95 |
96 |
97 |
98 |
{this.props.lang.about.project}
99 | GitHub:
https://github.com/glenndehaan/csgo-rcon-nodejs
100 | Star:
101 | Fork:
102 |
103 |
104 |
105 |
{this.props.lang.about.backendStructure}
106 | - NodeJS
107 | - Simple Node Logger
108 | - srcds-rcon
109 | - Json DB
110 | - Express
111 | - Express-WS
112 |
113 |
114 |
115 |
{this.props.lang.about.frontendStructure}
116 | - Webpack
117 | - Preact
118 | - Preact Router
119 | - Bootstrap
120 | - Sass
121 | - Sockette
122 |
123 |
124 | );
125 | }
126 | }
127 |
128 | /**
129 | * Connect the store to the component
130 | */
131 | export default connect('lang')(About);
132 |
--------------------------------------------------------------------------------
/frontend/components/NotFound.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import {connect} from "unistore/preact";
3 |
4 | class NotFound extends Component {
5 | /**
6 | * Runs then component mounts
7 | */
8 | componentDidMount() {
9 | this.updateGeneralPageData();
10 | }
11 |
12 | /**
13 | * Runs when component updates
14 | */
15 | componentDidUpdate() {
16 | this.updateGeneralPageData();
17 | }
18 |
19 | /**
20 | * Updates some general page data
21 | */
22 | updateGeneralPageData() {
23 | document.title = `${this.props.lang.general.notFound.title} | ${window.expressConfig.appName} ${window.expressConfig.env}`;
24 | window.events.emit('breadcrumbs', [
25 | {
26 | "name": this.props.lang.home.title,
27 | "url": "/"
28 | }
29 | ]);
30 | }
31 |
32 | /**
33 | * Preact render function
34 | *
35 | * @returns {*}
36 | */
37 | render() {
38 | return (
39 |
40 |
{this.props.lang.general.notFound.subtitle}
41 |
42 | {this.props.lang.general.notFound.body}
43 |
44 |
45 | );
46 | }
47 | }
48 |
49 | /**
50 | * Connect the store to the component
51 | */
52 | export default connect('lang')(NotFound);
53 |
--------------------------------------------------------------------------------
/frontend/components/Servers.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import { Link } from 'preact-router/match';
3 | import { connect } from "unistore/preact";
4 |
5 | import {checkServerAvailability} from "../utils/Arrays";
6 |
7 | class Servers extends Component {
8 | /**
9 | * Constructor
10 | */
11 | constructor() {
12 | super();
13 |
14 | this.state = {
15 | availability: []
16 | };
17 | }
18 |
19 | /**
20 | * Runs then component mounts
21 | */
22 | componentDidMount() {
23 | this.updateGeneralPageData();
24 | this.checkAvailability();
25 | }
26 |
27 | /**
28 | * Runs when the component updates
29 | *
30 | * @param previousProps
31 | */
32 | componentDidUpdate(previousProps) {
33 | if(previousProps !== this.props) {
34 | this.checkAvailability();
35 | }
36 |
37 | this.updateGeneralPageData();
38 | }
39 |
40 | /**
41 | * Updates some general page data
42 | */
43 | updateGeneralPageData() {
44 | document.title = `${this.props.lang.servers.title} | ${window.expressConfig.appName} ${window.expressConfig.env}`;
45 | window.events.emit('breadcrumbs', [
46 | {
47 | "name": this.props.lang.home.title,
48 | "url": "/"
49 | },
50 | {
51 | "name": this.props.lang.servers.title,
52 | "url": false
53 | }
54 | ]);
55 | }
56 |
57 | /**
58 | * Checks if a servers are available
59 | */
60 | checkAvailability() {
61 | const availability = [];
62 |
63 | for(let item = 0; item < this.props.servers.length; item++) {
64 | const server = this.props.servers[item];
65 | const available = checkServerAvailability(`${server.ip}:${server.port}`, this.props.matches);
66 |
67 | availability.push({
68 | ip: server.ip,
69 | port: server.port,
70 | available: available === false ? this.props.lang.servers.available : ({this.props.lang.servers.matchIsRunning}: {available.team1.name} v/s {available.team2.name}),
71 | color: available === false ? "success" : "warning"
72 | });
73 | }
74 |
75 | this.setState({
76 | availability
77 | });
78 | }
79 |
80 | /**
81 | * Connects steam to a CS:GO server
82 | */
83 | connectServer(e, server) {
84 | e.preventDefault();
85 | window.location = server;
86 | }
87 |
88 | /**
89 | * Preact render function
90 | *
91 | * @returns {*}
92 | */
93 | render() {
94 | return (
95 |
96 |
{this.props.lang.servers.subtitle}
97 |
98 |
99 |
100 |
101 | {this.props.lang.servers.table.server} |
102 | {this.props.lang.servers.table.status} |
103 |
104 |
105 |
106 | {this.state.availability.map((server, index) => (
107 |
108 | this.connectServer(e, `steam://connect/${server.ip}/${server.port}`)} >{`${server.ip}:${server.port}`} |
109 | {server.available} |
110 |
111 | ))}
112 |
113 |
114 |
115 |
116 | );
117 | }
118 | }
119 |
120 | /**
121 | * Connect the store to the component
122 | */
123 | export default connect('servers,matches,lang')(Servers);
124 |
--------------------------------------------------------------------------------
/frontend/components/Settings.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import { Link } from 'preact-router/match';
3 |
4 | import Challonge from "./integrations/Challonge";
5 | import Csv from "./integrations/Csv";
6 | import Archive from "./integrations/Archive";
7 | import MatchGroups from "./integrations/MatchGroups";
8 | import ForceArchive from "./integrations/ForceArchive";
9 | import {connect} from "unistore/preact";
10 |
11 | class Settings extends Component {
12 | /**
13 | * Runs then component mounts
14 | */
15 | componentDidMount() {
16 | this.updateGeneralPageData();
17 | }
18 |
19 | /**
20 | * Runs when the component updates
21 | */
22 | componentDidUpdate() {
23 | this.updateGeneralPageData();
24 | }
25 |
26 | /**
27 | * Updates some general page data
28 | */
29 | updateGeneralPageData() {
30 | document.title = `${this.props.lang.settings.title} | ${window.expressConfig.appName} ${window.expressConfig.env}`;
31 | window.events.emit('breadcrumbs', [
32 | {
33 | "name": this.props.lang.home.title,
34 | "url": "/"
35 | },
36 | {
37 | "name": this.props.lang.settings.title,
38 | "url": false
39 | }
40 | ]);
41 | }
42 |
43 | /**
44 | * Preact render function
45 | *
46 | * @returns {*}
47 | */
48 | render() {
49 | return (
50 |
51 |
{this.props.lang.settings.subtitle}
52 |
{this.props.lang.settings.log.title}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | /**
69 | * Connect the store to the component
70 | */
71 | export default connect('lang')(Settings);
72 |
--------------------------------------------------------------------------------
/frontend/components/flags/DE.js:
--------------------------------------------------------------------------------
1 | import {h} from 'preact';
2 |
3 | /**
4 | * Returns the Add SVG
5 | *
6 | * @return {*}
7 | * @constructor
8 | */
9 | const DE = () => {
10 | return (
11 |
16 | )
17 | };
18 |
19 | export default DE;
20 |
--------------------------------------------------------------------------------
/frontend/components/flags/FR.js:
--------------------------------------------------------------------------------
1 | import {h} from 'preact';
2 |
3 | /**
4 | * Returns the Add SVG
5 | *
6 | * @return {*}
7 | * @constructor
8 | */
9 | const FR = () => {
10 | return (
11 |
16 | )
17 | };
18 |
19 | export default FR;
20 |
--------------------------------------------------------------------------------
/frontend/components/flags/GB.js:
--------------------------------------------------------------------------------
1 | import {h} from 'preact';
2 |
3 | /**
4 | * Returns the Add SVG
5 | *
6 | * @return {*}
7 | * @constructor
8 | */
9 | const GB = () => {
10 | return (
11 |
16 | )
17 | };
18 |
19 | export default GB;
20 |
--------------------------------------------------------------------------------
/frontend/components/flags/NL.js:
--------------------------------------------------------------------------------
1 | import {h} from 'preact';
2 |
3 | /**
4 | * Returns the Add SVG
5 | *
6 | * @return {*}
7 | * @constructor
8 | */
9 | const NL = () => {
10 | return (
11 |
16 | )
17 | };
18 |
19 | export default NL;
20 |
--------------------------------------------------------------------------------
/frontend/components/icons/Add.js:
--------------------------------------------------------------------------------
1 | import {h} from 'preact';
2 |
3 | /**
4 | * Returns the Add SVG
5 | *
6 | * @return {*}
7 | * @constructor
8 | */
9 | const Add = () => {
10 | return (
11 |
14 | )
15 | };
16 |
17 | export default Add;
18 |
--------------------------------------------------------------------------------
/frontend/components/icons/Close.js:
--------------------------------------------------------------------------------
1 | import {h} from 'preact';
2 |
3 | /**
4 | * Returns the Close SVG
5 | *
6 | * @return {*}
7 | * @constructor
8 | */
9 | const Close = () => {
10 | return (
11 |
14 | )
15 | };
16 |
17 | export default Close;
18 |
--------------------------------------------------------------------------------
/frontend/components/icons/Details.js:
--------------------------------------------------------------------------------
1 | import {h} from 'preact';
2 |
3 | /**
4 | * Returns the Details SVG
5 | *
6 | * @return {*}
7 | * @constructor
8 | */
9 | const Details = () => {
10 | return (
11 |
14 | )
15 | };
16 |
17 | export default Details;
18 |
--------------------------------------------------------------------------------
/frontend/components/icons/Edit.js:
--------------------------------------------------------------------------------
1 | import {h} from 'preact';
2 |
3 | /**
4 | * Returns the Edit SVG
5 | *
6 | * @return {*}
7 | * @constructor
8 | */
9 | const Edit = () => {
10 | return (
11 |
14 | )
15 | };
16 |
17 | export default Edit;
18 |
--------------------------------------------------------------------------------
/frontend/components/icons/Home.js:
--------------------------------------------------------------------------------
1 | import {h} from 'preact';
2 |
3 | /**
4 | * Returns the Home SVG
5 | *
6 | * @return {*}
7 | * @constructor
8 | */
9 | const Home = () => {
10 | return (
11 |
14 | )
15 | };
16 |
17 | export default Home;
18 |
--------------------------------------------------------------------------------
/frontend/components/icons/Language.js:
--------------------------------------------------------------------------------
1 | import {h} from 'preact';
2 |
3 | /**
4 | * Returns the Add SVG
5 | *
6 | * @return {*}
7 | * @constructor
8 | */
9 | const Language = () => {
10 | return (
11 |
14 | )
15 | };
16 |
17 | export default Language;
18 |
--------------------------------------------------------------------------------
/frontend/components/icons/Notification.js:
--------------------------------------------------------------------------------
1 | import {h} from 'preact';
2 |
3 | /**
4 | * Returns the Notification SVG
5 | *
6 | * @param props
7 | * @return {*}
8 | * @constructor
9 | */
10 | const Notification = (props) => {
11 | if (props.enabled) {
12 | return (
13 |
16 | )
17 | } else {
18 | return (
19 |
22 | )
23 | }
24 | };
25 |
26 | export default Notification;
27 |
--------------------------------------------------------------------------------
/frontend/components/icons/Servers.js:
--------------------------------------------------------------------------------
1 | import {h} from 'preact';
2 |
3 | /**
4 | * Returns the Servers SVG
5 | *
6 | * @return {*}
7 | * @constructor
8 | */
9 | const Servers = () => {
10 | return (
11 |
14 | )
15 | };
16 |
17 | export default Servers;
18 |
--------------------------------------------------------------------------------
/frontend/components/icons/Settings.js:
--------------------------------------------------------------------------------
1 | import {h} from 'preact';
2 |
3 | /**
4 | * Returns the Settings SVG
5 | *
6 | * @return {*}
7 | * @constructor
8 | */
9 | const Settings = () => {
10 | return (
11 |
14 | )
15 | };
16 |
17 | export default Settings;
18 |
--------------------------------------------------------------------------------
/frontend/components/integrations/Archive.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 |
3 | import Socket from "../../modules/socket";
4 | import {route} from "preact-router";
5 | import {connect} from "unistore/preact";
6 |
7 | class Archive extends Component {
8 | /**
9 | * Send the request to the socket to start archiving the completed matches
10 | */
11 | archive() {
12 | Socket.send("integrations_archive", {});
13 |
14 | window.events.emit("notification", {
15 | title: "Archiving ended matches...",
16 | color: "primary"
17 | });
18 |
19 | route('/');
20 | }
21 |
22 | /**
23 | * Preact render function
24 | *
25 | * @returns {*}
26 | */
27 | render() {
28 | return (
29 |
30 |
{this.props.lang.settings.archive.title}
31 | {this.props.lang.settings.archive.description}
32 |
33 |
36 |
37 | );
38 | }
39 | }
40 |
41 | /**
42 | * Connect the store to the component
43 | */
44 | export default connect('lang')(Archive);
45 |
--------------------------------------------------------------------------------
/frontend/components/integrations/Challonge.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import { connect } from "unistore/preact";
3 |
4 | import Socket from "../../modules/socket";
5 | import {route} from "preact-router";
6 |
7 | class Challonge extends Component {
8 | /**
9 | * Constructor
10 | */
11 | constructor() {
12 | super();
13 |
14 | this.fields = {
15 | tournament: null,
16 | server: null,
17 | match_type: null,
18 | game_mode: null,
19 | knife_config: null,
20 | main_config: null,
21 | match_group: null
22 | };
23 | }
24 |
25 | /**
26 | * Checks if all fields are correct
27 | *
28 | * @return {boolean}
29 | */
30 | checkFields() {
31 | let errors = false;
32 |
33 | // Reset checks
34 | this.fields.tournament.classList.remove("error");
35 | this.fields.knife_config.classList.remove("error");
36 | this.fields.main_config.classList.remove("error");
37 | this.fields.match_type.classList.remove("error");
38 | this.fields.game_mode.classList.remove("error");
39 | this.fields.server.classList.remove("error");
40 | this.fields.match_group.classList.remove("error");
41 |
42 | if(this.fields.tournament.value === "false" || this.fields.tournament.value === false) {
43 | errors = true;
44 | this.fields.tournament.classList.add("error");
45 | }
46 | if(this.fields.server.value === "false" || this.fields.server.value === false) {
47 | errors = true;
48 | this.fields.server.classList.add("error");
49 | }
50 | if(this.fields.match_type.value === "false" || this.fields.match_type.value === false) {
51 | errors = true;
52 | this.fields.match_type.classList.add("error");
53 | }
54 | if(this.fields.game_mode.value === "false" || this.fields.game_mode.value === false) {
55 | errors = true;
56 | this.fields.game_mode.classList.add("error");
57 | }
58 | if(this.fields.knife_config.value === "false" || this.fields.knife_config.value === false) {
59 | errors = true;
60 | this.fields.knife_config.classList.add("error");
61 | }
62 | if(this.fields.main_config.value === "false" || this.fields.main_config.value === false) {
63 | errors = true;
64 | this.fields.main_config.classList.add("error");
65 | }
66 | if(this.fields.match_group.value === "false" || this.fields.match_group.value === false) {
67 | errors = true;
68 | this.fields.match_group.classList.add("error");
69 | }
70 |
71 | return errors;
72 | }
73 |
74 | /**
75 | * Send the request to the socket to start importing the challonge matches
76 | */
77 | importTournament() {
78 | if(!this.checkFields()) {
79 | Socket.send("integrations_challonge_import", {
80 | knife_config: this.fields.knife_config.value,
81 | match_config: this.fields.main_config.value,
82 | max_games: parseInt(this.fields.match_type.value),
83 | game_mode: this.fields.game_mode.value,
84 | server: this.fields.server.value,
85 | tournament: this.fields.tournament.value,
86 | match_group: this.fields.match_group.value
87 | });
88 |
89 | this.fields.tournament.selectedIndex = 0;
90 | this.fields.server.selectedIndex = 0;
91 | this.fields.match_type.selectedIndex = 0;
92 | this.fields.game_mode.selectedIndex = 0;
93 | this.fields.knife_config.selectedIndex = 0;
94 | this.fields.main_config.selectedIndex = 0;
95 | this.fields.match_group.selectedIndex = 0;
96 |
97 | window.events.emit("notification", {
98 | title: "Challonge import started...",
99 | color: "primary"
100 | });
101 |
102 | route('/');
103 | }
104 | }
105 |
106 | /**
107 | * Preact render function
108 | *
109 | * @returns {*}
110 | */
111 | render() {
112 | return (
113 |
114 |
{this.props.lang.settings.challonge.title}
115 | {this.props.lang.settings.challonge.description}
116 |
117 | {this.props.lang.settings.challonge.tournament}
118 |
124 | {this.props.lang.settings.challonge.matchGroup}
125 |
131 | {this.props.lang.settings.challonge.matchType}
132 |
140 | {this.props.lang.settings.challonge.gameMode}
141 |
147 | {this.props.lang.settings.challonge.server}
148 |
155 | {this.props.lang.settings.challonge.knifeConfig}
156 |
162 | {this.props.lang.settings.challonge.mainConfig}
163 |
169 |
172 |
173 | );
174 | }
175 | }
176 |
177 | /**
178 | * Connect the store to the component
179 | */
180 | export default connect('challonge,groups,servers,configs,lang')(Challonge);
181 |
--------------------------------------------------------------------------------
/frontend/components/integrations/Csv.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import { connect } from "unistore/preact";
3 |
4 | import Socket from "../../modules/socket";
5 | import {route} from "preact-router";
6 |
7 | import csvtojson from "csvtojson";
8 |
9 | class Csv extends Component {
10 | /**
11 | * Constructor
12 | */
13 | constructor() {
14 | super();
15 |
16 | this.fields = {
17 | csv: null,
18 | server: null,
19 | match_type: null,
20 | game_mode: null,
21 | knife_config: null,
22 | main_config: null,
23 | match_group: null
24 | };
25 |
26 | this.fileContents = [];
27 | }
28 |
29 | /**
30 | * Checks if all fields are correct
31 | *
32 | * @return {boolean}
33 | */
34 | checkFields() {
35 | let errors = false;
36 |
37 | // Reset checks
38 | this.fields.csv.classList.remove("error");
39 | this.fields.knife_config.classList.remove("error");
40 | this.fields.main_config.classList.remove("error");
41 | this.fields.match_type.classList.remove("error");
42 | this.fields.game_mode.classList.remove("error");
43 | this.fields.server.classList.remove("error");
44 | this.fields.match_group.classList.remove("error");
45 |
46 | if(this.fileContents.length < 1) {
47 | errors = true;
48 | this.fields.csv.classList.add("error");
49 | }
50 | if(this.fields.server.value === "false" || this.fields.server.value === false) {
51 | errors = true;
52 | this.fields.server.classList.add("error");
53 | }
54 | if(this.fields.match_type.value === "false" || this.fields.match_type.value === false) {
55 | errors = true;
56 | this.fields.match_type.classList.add("error");
57 | }
58 | if(this.fields.game_mode.value === "false" || this.fields.game_mode.value === false) {
59 | errors = true;
60 | this.fields.game_mode.classList.add("error");
61 | }
62 | if(this.fields.knife_config.value === "false" || this.fields.knife_config.value === false) {
63 | errors = true;
64 | this.fields.knife_config.classList.add("error");
65 | }
66 | if(this.fields.main_config.value === "false" || this.fields.main_config.value === false) {
67 | errors = true;
68 | this.fields.main_config.classList.add("error");
69 | }
70 | if(this.fields.match_group.value === "false" || this.fields.match_group.value === false) {
71 | errors = true;
72 | this.fields.match_group.classList.add("error");
73 | }
74 |
75 | return errors;
76 | }
77 |
78 | /**
79 | * Send the request to the socket to start importing the csv matches
80 | */
81 | importCsv() {
82 | if(!this.checkFields()) {
83 | Socket.send("integrations_csv_import", {
84 | knife_config: this.fields.knife_config.value,
85 | match_config: this.fields.main_config.value,
86 | max_games: parseInt(this.fields.match_type.value),
87 | game_mode: this.fields.game_mode.value,
88 | server: this.fields.server.value,
89 | match_group: this.fields.match_group.value,
90 | csv: this.fileContents
91 | });
92 |
93 | this.fields.csv.value = "";
94 | this.fields.server.selectedIndex = 0;
95 | this.fields.match_type.selectedIndex = 0;
96 | this.fields.game_mode.selectedIndex = 0;
97 | this.fields.knife_config.selectedIndex = 0;
98 | this.fields.main_config.selectedIndex = 0;
99 | this.fields.match_group.selectedIndex = 0;
100 |
101 | window.events.emit("notification", {
102 | title: "CSV import started...",
103 | color: "primary"
104 | });
105 |
106 | route('/');
107 | }
108 | }
109 |
110 | /**
111 | * Check's if a file is uploaded correctly
112 | *
113 | * @param e
114 | */
115 | handleFileChange(e) {
116 | let reader = new FileReader();
117 |
118 | reader.onload = event => {
119 | csvtojson()
120 | .fromString(event.target.result)
121 | .then((result) => {
122 | console.log('result', result);
123 | this.fileContents = result;
124 | })
125 | };
126 |
127 | reader.readAsText(e.target.files[0]);
128 | }
129 |
130 | /**
131 | * Preact render function
132 | *
133 | * @returns {*}
134 | */
135 | render() {
136 | return (
137 |
138 |
{this.props.lang.settings.csv.title}
139 |
{this.props.lang.settings.csv.descriptionPart1} ({this.props.lang.settings.csv.descriptionPart2}) {this.props.lang.settings.csv.descriptionPart3}
140 |
141 |
{this.props.lang.settings.csv.csv}
142 |
this.fields.csv = c} onChange={(e) => this.handleFileChange(e)} />
143 |
{this.props.lang.settings.csv.matchGroup}
144 |
150 |
{this.props.lang.settings.csv.matchType}
151 |
159 |
{this.props.lang.settings.csv.gameMode}
160 |
166 |
{this.props.lang.settings.csv.server}
167 |
173 |
{this.props.lang.settings.csv.knifeConfig}
174 |
180 |
{this.props.lang.settings.csv.mainConfig}
181 |
187 |
190 |
191 | );
192 | }
193 | }
194 |
195 | /**
196 | * Connect the store to the component
197 | */
198 | export default connect('groups,servers,configs,lang')(Csv);
199 |
--------------------------------------------------------------------------------
/frontend/components/integrations/ForceArchive.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import { connect } from "unistore/preact";
3 |
4 | import Socket from "../../modules/socket";
5 | import {route} from "preact-router";
6 |
7 | class ForceArchive extends Component {
8 | /**
9 | * Send the request to the socket to start archiving the selected match
10 | *
11 | * @param index
12 | */
13 | archive(index) {
14 | Socket.send("integrations_force_archive", {
15 | id: index
16 | });
17 |
18 | window.events.emit("notification", {
19 | title: "Force archiving match...",
20 | color: "primary"
21 | });
22 |
23 | route('/');
24 | }
25 |
26 | /**
27 | * Preact render function
28 | *
29 | * @returns {*}
30 | */
31 | render() {
32 | return (
33 |
34 |
{this.props.lang.settings.forceArchive.title}
35 |
{this.props.lang.settings.forceArchive.description}
36 |
37 |
38 | {this.props.matches.map((match, index) => {
39 | if(match.status < 100) {
40 | return (
41 | -
42 | {match.team1.name} v/s {match.team2.name}
43 |
46 |
47 | )
48 | }
49 | })}
50 |
51 |
52 | );
53 | }
54 | }
55 |
56 | /**
57 | * Connect the store to the component
58 | */
59 | export default connect('matches,lang')(ForceArchive);
60 |
--------------------------------------------------------------------------------
/frontend/components/integrations/MatchGroups.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import { connect } from "unistore/preact";
3 |
4 | import Socket from "../../modules/socket";
5 |
6 | class MatchGroups extends Component {
7 | /**
8 | * Constructor
9 | */
10 | constructor() {
11 | super();
12 |
13 | this.fields = {
14 | group: null
15 | };
16 | }
17 |
18 | /**
19 | * Creates a new match group
20 | */
21 | createMatchGroup() {
22 | if(!this.checkFields()) {
23 | Socket.send("group_create", {
24 | group: this.fields.group.value
25 | });
26 |
27 | this.fields.group.value = "";
28 |
29 | window.events.emit("notification", {
30 | title: "Group created!",
31 | color: "success"
32 | });
33 | }
34 | }
35 |
36 | /**
37 | * Checks if all fields are correct
38 | *
39 | * @return {boolean}
40 | */
41 | checkFields() {
42 | let errors = false;
43 |
44 | // Reset checks
45 | this.fields.group.classList.remove("error");
46 |
47 | if(this.fields.group.value === "") {
48 | errors = true;
49 | this.fields.group.classList.add("error");
50 | }
51 |
52 | return errors;
53 | }
54 |
55 | /**
56 | * Preact render function
57 | *
58 | * @returns {*}
59 | */
60 | render() {
61 | return (
62 |
63 |
{this.props.lang.settings.matchGroups.title}
64 |
65 |
{this.props.lang.settings.matchGroups.available}:
66 | {this.props.groups.length < 1 ? (
No groups available!
) : null}
67 |
68 | {this.props.groups.map((group, index) => (
69 | - {group}
70 | ))}
71 |
72 |
73 |
{this.props.lang.settings.matchGroups.create}:
74 |
this.fields.group = c} />
75 |
76 |
77 | );
78 | }
79 | }
80 |
81 | /**
82 | * Connect the store to the component
83 | */
84 | export default connect('groups,lang')(MatchGroups)
85 |
--------------------------------------------------------------------------------
/frontend/components/partials/Alert.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import {connect} from "unistore/preact";
3 |
4 | class Alert extends Component {
5 | /**
6 | * Preact render function
7 | *
8 | * @returns {*}
9 | */
10 | render() {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
{this.props.title}
18 |
19 |
20 | {this.props.body}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | }
31 | }
32 |
33 | /**
34 | * Connect the store to the component
35 | */
36 | export default connect('lang')(Alert);
37 |
--------------------------------------------------------------------------------
/frontend/components/partials/Breadcrumbs.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import { Link } from 'preact-router/match';
3 |
4 | export default class Breadcrumbs extends Component {
5 | /**
6 | * Constructor
7 | */
8 | constructor() {
9 | super();
10 |
11 | this.state = {
12 | breadcrumbs: []
13 | };
14 |
15 | window.events.on('breadcrumbs', (e) => this.update(e));
16 | }
17 |
18 | /**
19 | * Updates the breadcrumbs
20 | *
21 | * @param breadcrumbs
22 | */
23 | update(breadcrumbs) {
24 | this.setState({
25 | breadcrumbs
26 | });
27 | }
28 |
29 | /**
30 | * Preact render function
31 | *
32 | * @returns {*}
33 | */
34 | render() {
35 | return (
36 |
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/frontend/components/partials/Footer.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import {Link} from 'preact-router/match';
3 | import {connect} from "unistore/preact";
4 | import storage from "../../modules/storage";
5 | import language from "../../modules/language";
6 |
7 | import Language from "../icons/Language";
8 | import GB from "../flags/GB";
9 | import FR from "../flags/FR";
10 | import DE from "../flags/DE";
11 | import NL from "../flags/NL";
12 |
13 | class Footer extends Component {
14 | /**
15 | * Constructor
16 | */
17 | constructor() {
18 | super();
19 |
20 | this.state = {
21 | loadTime: 0.00,
22 | langToggleOpen: false,
23 | currentLang: storage.get("lang")
24 | }
25 | }
26 |
27 | /**
28 | * Runs then the component mounts
29 | */
30 | componentDidMount() {
31 | const loadTime = (Date.now() - window.loadTime) / 1000;
32 |
33 | this.setState({
34 | loadTime: loadTime.toFixed(2)
35 | });
36 | }
37 |
38 | /**
39 | * Toggles the lang dropdown
40 | */
41 | toggleButton() {
42 | if(this.state.langToggleOpen) {
43 | this.setState({
44 | langToggleOpen: false
45 | });
46 | } else {
47 | this.setState({
48 | langToggleOpen: true
49 | });
50 | }
51 | }
52 |
53 | /**
54 | * Switches the language
55 | *
56 | * @param lang
57 | */
58 | switchLang(lang) {
59 | language.set(lang);
60 |
61 | this.setState({
62 | langToggleOpen: false,
63 | currentLang: lang
64 | });
65 | }
66 |
67 | /**
68 | * Preact render function
69 | *
70 | * @returns {*}
71 | */
72 | render() {
73 | return (
74 |
90 | );
91 | }
92 | }
93 |
94 | /**
95 | * Connect the store to the component
96 | */
97 | export default connect('lang')(Footer);
98 |
--------------------------------------------------------------------------------
/frontend/components/partials/Header.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import {Link} from 'preact-router/match';
3 | import systemNotification from '../../modules/systemNotification';
4 |
5 | import Home from '../icons/Home';
6 | import Add from '../icons/Add';
7 | import Servers from '../icons/Servers';
8 | import Settings from '../icons/Settings';
9 | import Notification from '../icons/Notification';
10 | import {connect} from "unistore/preact";
11 |
12 | class Header extends Component {
13 | /**
14 | * Constructor
15 | */
16 | constructor() {
17 | super();
18 |
19 | this.state = {
20 | connection: {
21 | className: "text-danger",
22 | text: "Disconnected"
23 | },
24 | notificationEnabled: systemNotification.currentPermissionStatus()
25 | };
26 |
27 | this.settings = null;
28 |
29 | window.events.on('router', (e) => {
30 | if(e.route !== "/settings") {
31 | this.settings.classList.remove("active");
32 | }
33 | });
34 | }
35 |
36 | /**
37 | * Function when component mounts
38 | */
39 | componentDidMount() {
40 | this.updateConnectionStatus();
41 | }
42 |
43 | /**
44 | * Function when component updates
45 | *
46 | * @param prevProps
47 | */
48 | componentDidUpdate(prevProps) {
49 | if (prevProps.connected !== this.props.connected || prevProps.reconnecting !== this.props.reconnecting || prevProps.lang !== this.props.lang) {
50 | this.updateConnectionStatus();
51 | }
52 | }
53 |
54 | /**
55 | * Function to update the connection status
56 | */
57 | updateConnectionStatus() {
58 | if (this.props.connected && !this.props.reconnecting) {
59 | this.setState({
60 | connection: {
61 | className: "text-success",
62 | text: this.props.lang.general.header.connected
63 | }
64 | });
65 | }
66 |
67 | if (this.props.connected && this.props.reconnecting) {
68 | this.setState({
69 | connection: {
70 | className: "text-warning",
71 | text: this.props.lang.general.header.reconnecting
72 | }
73 | });
74 | }
75 |
76 | if (!this.props.connected && !this.props.reconnecting) {
77 | this.setState({
78 | connection: {
79 | className: "text-danger",
80 | text: this.props.lang.general.header.disconnected
81 | }
82 | });
83 | }
84 | }
85 |
86 | /**
87 | * Enables or disables the notifications
88 | */
89 | toggleNotification() {
90 | systemNotification.toggleNotifications((status) => {
91 | this.setState({
92 | notificationEnabled: status
93 | })
94 | })
95 | }
96 |
97 | /**
98 | * Preact render function
99 | *
100 | * @returns {*}
101 | */
102 | render() {
103 | return (
104 |
137 | );
138 | }
139 | }
140 |
141 | /**
142 | * Connect the store to the component
143 | */
144 | export default connect('lang')(Header);
145 |
--------------------------------------------------------------------------------
/frontend/components/partials/Notification.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import Close from "../icons/Close";
3 |
4 | export default class Notification extends Component {
5 | /**
6 | * Constructor
7 | */
8 | constructor() {
9 | super();
10 |
11 | this.state = {
12 | notifications: []
13 | };
14 |
15 | window.events.on('notification', (e) => this.add(e.title, e.color));
16 | }
17 |
18 | /**
19 | * Add's a new notification
20 | *
21 | * @param title
22 | * @param color
23 | */
24 | add(title, color) {
25 | const tempState = this.state.notifications;
26 | const length = tempState.push({
27 | title,
28 | color
29 | });
30 |
31 | setTimeout(() => {
32 | this.close((length - 1));
33 | }, 5000);
34 |
35 | this.setState({
36 | notifications: tempState
37 | });
38 | }
39 |
40 | /**
41 | * Closes a notification
42 | *
43 | * @param index
44 | */
45 | close(index) {
46 | const tempState = this.state.notifications;
47 | delete tempState[index];
48 |
49 | this.setState({
50 | notifications: tempState
51 | });
52 | }
53 |
54 | /**
55 | * Preact render function
56 | *
57 | * @returns {*}
58 | */
59 | render() {
60 | return (
61 |
62 | {this.state.notifications.map((notification, index) => this.renderNotification(notification, index))}
63 |
64 | );
65 | }
66 |
67 | /**
68 | * Renders a single notification
69 | *
70 | * @param notification
71 | * @param index
72 | * @return {*}
73 | */
74 | renderNotification(notification, index) {
75 | return (
76 |
77 | {notification.title}
78 |
81 |
82 | );
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/frontend/components/settings/Log.js:
--------------------------------------------------------------------------------
1 | import {h, Component} from 'preact';
2 | import {connect} from "unistore/preact";
3 |
4 | class Log extends Component {
5 | /**
6 | * Runs then component mounts
7 | */
8 | componentDidMount() {
9 | this.updateGeneralPageData();
10 | }
11 |
12 | /**
13 | * Runs when the component updates
14 | */
15 | componentDidUpdate() {
16 | this.updateGeneralPageData();
17 | }
18 |
19 | /**
20 | * Updates some general page data
21 | */
22 | updateGeneralPageData() {
23 | document.title = `Server Logs | ${window.expressConfig.appName} ${window.expressConfig.env}`;
24 | window.events.emit('breadcrumbs', [
25 | {
26 | "name": this.props.lang.home.title,
27 | "url": "/"
28 | },
29 | {
30 | "name": this.props.lang.settings.title,
31 | "url": "/settings"
32 | },
33 | {
34 | "name": this.props.lang.settings.log.title,
35 | "url": false
36 | }
37 | ]);
38 | }
39 |
40 | /**
41 | * Preact render function
42 | *
43 | * @returns {*}
44 | */
45 | render() {
46 | return (
47 |
48 |
{this.props.lang.settings.log.title}
49 |
50 | {this.props.logs.map((log, key) => (
51 |
{log}
52 | ))}
53 |
54 |
55 | );
56 | }
57 | }
58 |
59 | /**
60 | * Connect the store to the component
61 | */
62 | export default connect('lang,logs')(Log);
63 |
--------------------------------------------------------------------------------
/frontend/lang/de/about.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Über",
3 | "subtitle": "Über",
4 | "devWarning": "Sie betreiben eine Entwicklungsversion !!",
5 | "oldWarning": "Sie führen eine ältere Version aus !! Bitte aktualisieren Sie Ihre Version bald ...",
6 | "descriptionTitle": "Beschreibung",
7 | "description": "Ein Web-Panel zur Steuerung eines CS: GO-Servers",
8 | "version": "Ausführung",
9 | "currentVersion": "Aktuelle Version",
10 | "latestVersion": "Letzte Version",
11 | "contributors": "Mitwirkende",
12 | "project": "Projekt",
13 | "backendStructure": "Backend-Struktur",
14 | "frontendStructure": "Frontend-Struktur"
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/lang/de/create.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Neues Spiel erstellen",
3 | "subtitle": "Neues Spiel erstellen",
4 | "team1": "Team 1",
5 | "team2": "Team 2",
6 | "name": "Name",
7 | "countryCode": "Landesvorwahl",
8 | "selectCountry": "Land auswählen",
9 | "generalMatchSettings": "Allgemeine Übereinstimmungseinstellungen",
10 | "matchGroup": "Spielgruppe",
11 | "server": "Server",
12 | "defaultServerMap": "Standard-Serverzuordnung",
13 | "matchType": "Übereinstimmungstyp",
14 | "gameMode": "Spielmodus",
15 | "csgoKnifeConfig": "CSGO Knife Config",
16 | "csgoMainConfig": "CSGO-Hauptkonfiguration",
17 | "selectGroup": "Wählen Sie eine Gruppe aus",
18 | "selectMatchType": "Wählen Sie einen Übereinstimmungstyp aus",
19 | "selectGameMode": "Wählen Sie einen Spielmodus",
20 | "selectServer": "Wählen Sie einen Server",
21 | "selectConfig": "Wählen Sie eine Konfiguration aus",
22 | "create": "Erstellen"
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/lang/de/detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Spiel",
3 | "subtitle": "Angaben zum Spiel",
4 | "matchGroup": "Spielgruppe",
5 | "matchType": "Übereinstimmungstyp",
6 | "gameMode": "Spielmodus",
7 | "team1Name": "Team 1 Name",
8 | "team1Country": "Team 1 Land",
9 | "team2Name": "Team 2 Name",
10 | "team2Country": "Team 2 Land",
11 | "server": "Server",
12 | "map": "Karte",
13 | "csgoKnifeConfig": "CSGO Knife Config",
14 | "csgoMainConfig": "CSGO-Hauptkonfiguration",
15 | "currentMatchStatus": "Aktueller Spielstatus",
16 | "liveScoring": "Live Scoring",
17 | "noScoresAvailable": "Noch keine Bewertungen vorhanden...",
18 | "matchControls": "Match-Steuerelemente",
19 | "matchControlsLocked": "Spielsteuerung gesperrt! Grund:",
20 | "matchControlsLockedReason1": "Dieses Spiel ist bereits beendet!",
21 | "matchControlsLockedReason2": "Ein weiteres Spiel läuft bereits auf dem gleichen Server!",
22 | "connectServer": "Server verbinden",
23 | "startWarmup": "Starten Sie das Aufwärmen",
24 | "startKnifeRound": "Beginnen Sie mit dem Messer",
25 | "startMatch": "Spiel beginnen",
26 | "endMatch": "Spiel beenden",
27 | "serverControls": "Serversteuerelemente",
28 | "serverControlsLocked": "Serversteuerungen gesperrt! Grund:",
29 | "serverControlsLockedReason1": "Dieses Spiel wird nicht begonnen!",
30 | "serverControlsLockedReason2": "Dieses Spiel ist bereits beendet!",
31 | "resumeGame": "Spiel fortsetzen",
32 | "pauseGame": "Spiel anhalten",
33 | "switchTeamSides": "Wechseln Sie die Teamseiten",
34 | "selectMap": "Karte auswählen",
35 | "switchMap": "Karte wechseln",
36 | "say": "Sagen",
37 | "restartGame": "Spiel neustarten",
38 | "connectServerAlert": {
39 | "title": "Verbinden zum Server?",
40 | "body": "Bitte beachten Sie: Stellen Sie sicher, dass mindestens ein CS:GO-Client mit dem Server verbunden ist, bevor Sie auf Ja klicken!"
41 | },
42 | "restartGameAlert": {
43 | "title": "Spiel neustarten?",
44 | "body": "Warnung! Dadurch werden alle Punkte auf 0 gesetzt und das gesamte Spiel neu gestartet!"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/frontend/lang/de/edit.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Spiel bearbeiten",
3 | "subtitle": "Spiel bearbeiten",
4 | "match": "Spiel",
5 | "team1": "Team 1",
6 | "team2": "Team 2",
7 | "name": "Name",
8 | "countryCode": "Landesvorwahl",
9 | "generalMatchSettings": "Allgemeine Übereinstimmungseinstellungen",
10 | "matchGroup": "Spielgruppe",
11 | "server": "Server",
12 | "defaultServerMap": "Standard-Serverzuordnung",
13 | "matchType": "Übereinstimmungstyp",
14 | "gameMode": "Spielmodus",
15 | "csgoKnifeConfig": "CSGO Knife Config",
16 | "csgoMainConfig": "CSGO-Hauptkonfiguration",
17 | "save": "Sparen"
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/lang/de/general.json:
--------------------------------------------------------------------------------
1 | {
2 | "header": {
3 | "connected": "In Verbindung gebracht",
4 | "reconnecting": "Verbindung wird wiederhergestellt ...",
5 | "disconnected": "Getrennt",
6 | "links": {
7 | "home": "Zuhause",
8 | "serverOverview": "Server-Übersicht",
9 | "createMatch": "Spiel erstellen",
10 | "settings": "die Einstellungen"
11 | }
12 | },
13 | "footer": {
14 | "about": "Über",
15 | "loadedIn": "Geladen in",
16 | "seconds": "Sekunden"
17 | },
18 | "notFound": {
19 | "title": "Nicht gefunden",
20 | "subtitle": "Nicht gefunden",
21 | "body": "Verwenden Sie das Menü oben, um zu einer anderen Seite zu navigieren."
22 | },
23 | "alert": {
24 | "yes": "Ja",
25 | "no": "Nein"
26 | },
27 | "connection": {
28 | "error": {
29 | "title": "Hoppla!",
30 | "line1": "Die Verbindung zum Server ist verloren!",
31 | "line2": "Es wurde ein erneuter Verbindungsversuch unternommen, der Server hat jedoch nicht rechtzeitig geantwortet.",
32 | "line3": "Bitte versuchen Sie diese Seite zu aktualisieren..."
33 | },
34 | "reconnecting": {
35 | "title": "Verbindung wieder herstellen!",
36 | "line1": "Die Verbindung zum Server ist verloren!",
37 | "line2": "Wir versuchen, die Verbindung zum Server wiederherzustellen.",
38 | "line3": "Warten Sie mal..."
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/frontend/lang/de/home.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Zuhause",
3 | "subtitle": "Streichhölzer",
4 | "filters": {
5 | "notStarted": "Nicht angefangen",
6 | "running": "Laufen",
7 | "completed": "Abgeschlossen",
8 | "archived": "Archiviert"
9 | },
10 | "table": {
11 | "server": "Server",
12 | "map": "Karte",
13 | "team1": "Team 1",
14 | "team2": "Team 2",
15 | "status": "Status"
16 | },
17 | "links": {
18 | "edit": "Spiel bearbeiten",
19 | "details": "Angaben zum Spiel"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/lang/de/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import all sub translation files
3 | */
4 | import general from './general';
5 | import home from './home';
6 | import servers from './servers';
7 | import about from './about';
8 | import detail from './detail';
9 | import create from './create';
10 | import edit from './edit';
11 | import settings from './settings';
12 |
13 | /**
14 | * Export as one translation file
15 | */
16 | export default {general,home,servers,about,detail,create,edit,settings};
17 |
--------------------------------------------------------------------------------
/frontend/lang/de/servers.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Server",
3 | "subtitle": "Server",
4 | "table": {
5 | "server": "Server",
6 | "status": "Status"
7 | },
8 | "available": "Verfügbar",
9 | "matchIsRunning": "Spiel läuft"
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/lang/de/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Einstellungen",
3 | "subtitle": "Einstellungen",
4 | "matchGroups": {
5 | "title": "Spielgruppen",
6 | "available": "Hier sind alle verfügbaren Matchgruppen",
7 | "create": "Sie können auch eine neue erstellen",
8 | "createButton": "Erstellen"
9 | },
10 | "challonge": {
11 | "title": "Challonge",
12 | "description": "Um die Challonge-Übereinstimmungen in die Datenbank zu importieren, wählen Sie die folgenden Optionen aus und klicken Sie auf: Importieren",
13 | "tournament": "Turnier",
14 | "matchGroup": "Spielgruppe",
15 | "matchType": "Übereinstimmungstyp",
16 | "gameMode": "Spielmodus",
17 | "server": "Server",
18 | "knifeConfig": "CSGO Knife Config",
19 | "mainConfig": "CSGO-Hauptkonfiguration",
20 | "import": "Einführen"
21 | },
22 | "csv": {
23 | "title": "CSV",
24 | "descriptionPart1": "So importieren Sie eine CSV",
25 | "descriptionPart2": "Beispiel",
26 | "descriptionPart3": "Laden Sie Ihre CSV-Datei hoch und klicken Sie auf: Importieren",
27 | "csv": "CSV",
28 | "matchGroup": "Spielgruppe",
29 | "matchType": "Übereinstimmungstyp",
30 | "gameMode": "Spielmodus",
31 | "server": "Server",
32 | "knifeConfig": "CSGO Knife Config",
33 | "mainConfig": "CSGO-Hauptkonfiguration",
34 | "import": "Einführen"
35 | },
36 | "archive": {
37 | "title": "Archiv",
38 | "description": "Klicken Sie auf die Schaltfläche Archiv, um die beendeten Übereinstimmungen zu archivieren",
39 | "archive": "Archiv"
40 | },
41 | "forceArchive": {
42 | "title": "Archiv-Übereinstimmungen erzwingen",
43 | "description": "Klicken Sie auf die Schaltfläche "Archiv", um ein Match zu erzwingen",
44 | "archive": "Archiv"
45 | },
46 | "log": {
47 | "title": "Serverprotokolle"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/frontend/lang/en/about.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "About",
3 | "subtitle": "About",
4 | "devWarning": "You are running a development version!!",
5 | "oldWarning": "You are running an older version!! Please update your version soon...",
6 | "descriptionTitle": "Description",
7 | "description": "A web panel to control a CS:GO server",
8 | "version": "Version",
9 | "currentVersion": "Current version",
10 | "latestVersion": "Latest version",
11 | "contributors": "Contributors",
12 | "project": "Project",
13 | "backendStructure": "Backend structure",
14 | "frontendStructure": "Frontend structure"
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/lang/en/create.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Create new match",
3 | "subtitle": "Create new match",
4 | "team1": "Team 1",
5 | "team2": "Team 2",
6 | "name": "Name",
7 | "countryCode": "Country Code",
8 | "selectCountry": "Select country",
9 | "generalMatchSettings": "General Match Settings",
10 | "matchGroup": "Match Group",
11 | "server": "Server",
12 | "defaultServerMap": "Default Server Map",
13 | "matchType": "Match Type",
14 | "gameMode": "Game Mode",
15 | "csgoKnifeConfig": "CSGO Knife Config",
16 | "csgoMainConfig": "CSGO Main Config",
17 | "selectGroup": "Select a group",
18 | "selectMatchType": "Select a match type",
19 | "selectGameMode": "Select a game mode",
20 | "selectServer": "Select a server",
21 | "selectConfig": "Select a config",
22 | "create": "Create"
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/lang/en/detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Match",
3 | "subtitle": "Match details",
4 | "matchGroup": "Match Group",
5 | "matchType": "Match Type",
6 | "gameMode": "Game Mode",
7 | "team1Name": "Team 1 Name",
8 | "team1Country": "Team 1 Country",
9 | "team2Name": "Team 2 Name",
10 | "team2Country": "Team 2 Country",
11 | "server": "Server",
12 | "map": "Map",
13 | "csgoKnifeConfig": "CSGO Knife Config",
14 | "csgoMainConfig": "CSGO Main Config",
15 | "currentMatchStatus": "Current match status",
16 | "liveScoring": "Live scoring",
17 | "noScoresAvailable": "No scores available yet...",
18 | "matchControls": "Match controls",
19 | "matchControlsLocked": "Match controls locked! Reason:",
20 | "matchControlsLockedReason1": "This match has already ended!",
21 | "matchControlsLockedReason2": "Another match is already running on the same server!",
22 | "connectServer": "Connect server",
23 | "startWarmup": "Start warmup",
24 | "startKnifeRound": "Start knife round",
25 | "startMatch": "Start match",
26 | "endMatch": "End Match & Restore Server",
27 | "serverControls": "Server controls",
28 | "serverControlsLocked": "Server controls locked! Reason:",
29 | "serverControlsLockedReason1": "This match is not started!",
30 | "serverControlsLockedReason2": "This match has already ended!",
31 | "resumeGame": "Resume game",
32 | "pauseGame": "Pause game",
33 | "switchTeamSides": "Switch team sides",
34 | "selectMap": "Select map",
35 | "switchMap": "Switch map",
36 | "say": "Say",
37 | "restartGame": "Restart game",
38 | "connectServerAlert": {
39 | "title": "Connect to server?",
40 | "body": "Please note: Make sure that at least one CS:GO client is connected with the server before clicking yes!"
41 | },
42 | "restartGameAlert": {
43 | "title": "Restart game?",
44 | "body": "Warning! This will set all scores to 0 and restart the complete match!"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/frontend/lang/en/edit.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Edit match",
3 | "subtitle": "Edit match",
4 | "match": "Match",
5 | "team1": "Team 1",
6 | "team2": "Team 2",
7 | "name": "Name",
8 | "countryCode": "Country Code",
9 | "generalMatchSettings": "General Match Settings",
10 | "matchGroup": "Match Group",
11 | "server": "Server",
12 | "defaultServerMap": "Default Server Map",
13 | "matchType": "Match Type",
14 | "gameMode": "Game Mode",
15 | "csgoKnifeConfig": "CSGO Knife Config",
16 | "csgoMainConfig": "CSGO Main Config",
17 | "save": "Save"
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/lang/en/general.json:
--------------------------------------------------------------------------------
1 | {
2 | "header": {
3 | "connected": "Connected",
4 | "reconnecting": "Reconnecting...",
5 | "disconnected": "Disconnected",
6 | "links": {
7 | "home": "Home",
8 | "serverOverview": "Server overview",
9 | "createMatch": "Create match",
10 | "settings": "Settings"
11 | }
12 | },
13 | "footer": {
14 | "about": "About",
15 | "loadedIn": "Loaded in",
16 | "seconds": "seconds"
17 | },
18 | "notFound": {
19 | "title": "Not Found",
20 | "subtitle": "Not Found",
21 | "body": "Use the menu above to navigate to another page."
22 | },
23 | "alert": {
24 | "yes": "Yes",
25 | "no": "No"
26 | },
27 | "connection": {
28 | "error": {
29 | "title": "Whoops!",
30 | "line1": "The connection to the server has been lost!",
31 | "line2": "A reconnect attempt has been made but the server didn't respond in time.",
32 | "line3": "Please try to refresh this page..."
33 | },
34 | "reconnecting": {
35 | "title": "Reconnecting!",
36 | "line1": "The connection to the server has been lost!",
37 | "line2": "We are trying to reconnect to the server.",
38 | "line3": "Please wait..."
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/frontend/lang/en/home.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Home",
3 | "subtitle": "Matches",
4 | "filters": {
5 | "notStarted": "Not started",
6 | "running": "Running",
7 | "completed": "Completed",
8 | "archived": "Archived"
9 | },
10 | "table": {
11 | "server": "Server",
12 | "map": "Map",
13 | "team1": "Team 1",
14 | "team2": "Team 2",
15 | "status": "Status"
16 | },
17 | "links": {
18 | "edit": "Edit match",
19 | "details": "Match details"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/lang/en/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import all sub translation files
3 | */
4 | import general from './general';
5 | import home from './home';
6 | import servers from './servers';
7 | import about from './about';
8 | import detail from './detail';
9 | import create from './create';
10 | import edit from './edit';
11 | import settings from './settings';
12 |
13 | /**
14 | * Export as one translation file
15 | */
16 | export default {general,home,servers,about,detail,create,edit,settings};
17 |
--------------------------------------------------------------------------------
/frontend/lang/en/servers.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Servers",
3 | "subtitle": "Servers",
4 | "table": {
5 | "server": "Server",
6 | "status": "Status"
7 | },
8 | "available": "Available",
9 | "matchIsRunning": "Match is running"
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/lang/en/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Settings",
3 | "subtitle": "Settings",
4 | "matchGroups": {
5 | "title": "Match Groups",
6 | "available": "Here are all available match groups",
7 | "create": "You can also create a new one",
8 | "createButton": "Create"
9 | },
10 | "challonge": {
11 | "title": "Challonge",
12 | "description": "To import the challonge matches to the database select the options below and click: Import",
13 | "tournament": "Tournament",
14 | "matchGroup": "Match Group",
15 | "matchType": "Match Type",
16 | "gameMode": "Game Mode",
17 | "server": "Server",
18 | "knifeConfig": "CSGO Knife Config",
19 | "mainConfig": "CSGO Main Config",
20 | "import": "Import"
21 | },
22 | "csv": {
23 | "title": "CSV",
24 | "descriptionPart1": "To import a CSV",
25 | "descriptionPart2": "Example",
26 | "descriptionPart3": "upload you CSV below and click: Import",
27 | "csv": "CSV",
28 | "matchGroup": "Match Group",
29 | "matchType": "Match Type",
30 | "gameMode": "Game Mode",
31 | "server": "Server",
32 | "knifeConfig": "CSGO Knife Config",
33 | "mainConfig": "CSGO Main Config",
34 | "import": "Import"
35 | },
36 | "archive": {
37 | "title": "Archive",
38 | "description": "Click the archive button below to archive ended matches",
39 | "archive": "Archive"
40 | },
41 | "forceArchive": {
42 | "title": "Force archive matches",
43 | "description": "Click the archive button below to force archive a match",
44 | "archive": "Archive"
45 | },
46 | "log": {
47 | "title": "Server Logs"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/frontend/lang/fr/about.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Sur",
3 | "subtitle": "Sur",
4 | "devWarning": "Vous utilisez une version de développement!!",
5 | "oldWarning": "Vous utilisez une version plus ancienne!! Veuillez mettre à jour votre version bientôt...",
6 | "descriptionTitle": "La description",
7 | "description": "Un panneau Web pour contrôler un serveur CS:GO",
8 | "version": "Version",
9 | "currentVersion": "Version actuelle",
10 | "latestVersion": "Dernière version",
11 | "contributors": "Contributeurs",
12 | "project": "Projet",
13 | "backendStructure": "Structure du backend",
14 | "frontendStructure": "Structure du frontend"
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/lang/fr/create.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Créer un nouveau match",
3 | "subtitle": "Créer un nouveau match",
4 | "team1": "Équipe 1",
5 | "team2": "Équipe 2",
6 | "name": "Prénom",
7 | "countryCode": "Code postal",
8 | "selectCountry": "Choisissez le pays",
9 | "generalMatchSettings": "Paramètres de correspondance généraux",
10 | "matchGroup": "Groupe de match",
11 | "server": "Serveur",
12 | "defaultServerMap": "Carte du serveur par défaut",
13 | "matchType": "Type de match",
14 | "gameMode": "Mode de jeu",
15 | "csgoKnifeConfig": "CSGO Knife Config",
16 | "csgoMainConfig": "Configuration principale de CSGO",
17 | "selectGroup": "Sélectionner un groupe",
18 | "selectMatchType": "Sélectionnez un type de match",
19 | "selectGameMode": "Sélectionnez un mode de jeu",
20 | "selectServer": "Sélectionnez un serveur",
21 | "selectConfig": "Sélectionnez une configuration",
22 | "create": "Créer"
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/lang/fr/detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Rencontre",
3 | "subtitle": "Détails du match",
4 | "matchGroup": "Groupe de match",
5 | "matchType": "Type de match",
6 | "gameMode": "Mode de jeu",
7 | "team1Name": "Nom de l'équipe 1",
8 | "team1Country": "Équipe 1 pays",
9 | "team2Name": "Nom de l'équipe 2",
10 | "team2Country": "Équipe 2 pays",
11 | "server": "Serveur",
12 | "map": "Carte",
13 | "csgoKnifeConfig": "CSGO Knife Config",
14 | "csgoMainConfig": "Configuration principale de CSGO",
15 | "currentMatchStatus": "Statut actuel du match",
16 | "liveScoring": "Score en direct",
17 | "noScoresAvailable": "Pas encore de partitions disponibles ...",
18 | "matchControls": "Contrôles de correspondance",
19 | "matchControlsLocked": "Les contrôles de match sont verrouillés! Raison:",
20 | "matchControlsLockedReason1": "Ce match est déjà terminé!",
21 | "matchControlsLockedReason2": "Une autre correspondance est déjà en cours sur le même serveur!",
22 | "connectServer": "Connecter le serveur",
23 | "startWarmup": "Commencer l'échauffement",
24 | "startKnifeRound": "Commencez le couteau rond",
25 | "startMatch": "Commencer le match",
26 | "endMatch": "Fin du match",
27 | "serverControls": "Contrôles serveur",
28 | "serverControlsLocked": "Contrôles du serveur verrouillés! Raison:",
29 | "serverControlsLockedReason1": "Ce match n'est pas commencé!",
30 | "serverControlsLockedReason2": "Ce match est déjà terminé!",
31 | "resumeGame": "Reprendre jeu",
32 | "pauseGame": "Jeu de pause",
33 | "switchTeamSides": "Changer d'équipe",
34 | "selectMap": "Sélectionnez la carte",
35 | "switchMap": "Changer de carte",
36 | "say": "Dire",
37 | "restartGame": "Recommencer le jeu",
38 | "connectServerAlert": {
39 | "title": "Connecter au serveur?",
40 | "body": "Remarque: assurez-vous qu'au moins un client CS:GO est connecté au serveur avant de cliquer sur yes!"
41 | },
42 | "restartGameAlert": {
43 | "title": "Recommencer le jeu?",
44 | "body": "Attention! Cela mettra tous les scores à 0 et relancera le match complet!"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/frontend/lang/fr/edit.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Editer le match",
3 | "subtitle": "Editer le match",
4 | "match": "Rencontre",
5 | "team1": "Équipe 1",
6 | "team2": "Équipe 2",
7 | "name": "Prénom",
8 | "countryCode": "Code postal",
9 | "generalMatchSettings": "Paramètres de correspondance généraux",
10 | "matchGroup": "Groupe de match",
11 | "server": "Serveur",
12 | "defaultServerMap": "Carte du serveur par défaut",
13 | "matchType": "Type de match",
14 | "gameMode": "Mode de jeu",
15 | "csgoKnifeConfig": "CSGO Knife Config",
16 | "csgoMainConfig": "Configuration principale de CSGO",
17 | "save": "Sauvegarder"
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/lang/fr/general.json:
--------------------------------------------------------------------------------
1 | {
2 | "header": {
3 | "connected": "Connecté",
4 | "reconnecting": "Reconnexion...",
5 | "disconnected": "Débranché",
6 | "links": {
7 | "home": "Accueil",
8 | "serverOverview": "Vue d'ensemble du serveur",
9 | "createMatch": "Créer un match",
10 | "settings": "Réglages"
11 | }
12 | },
13 | "footer": {
14 | "about": "Sur",
15 | "loadedIn": "Chargé dans",
16 | "seconds": "seconds"
17 | },
18 | "notFound": {
19 | "title": "Pas trouvé",
20 | "subtitle": "Pas trouvé",
21 | "body": "Utilisez le menu ci-dessus pour naviguer vers une autre page."
22 | },
23 | "alert": {
24 | "yes": "Oui",
25 | "no": "Non"
26 | },
27 | "connection": {
28 | "error": {
29 | "title": "Oups!",
30 | "line1": "La connexion au serveur a été perdue!",
31 | "line2": "A reconnect attempt has been made but the server didn't respond in time.",
32 | "line3": "S'il vous plaît essayez de rafraîchir cette page..."
33 | },
34 | "reconnecting": {
35 | "title": "Reconnecter!",
36 | "line1": "La connexion au serveur a été perdue!",
37 | "line2": "Nous essayons de nous reconnecter au serveur.",
38 | "line3": "S'il vous plaît, attendez..."
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/frontend/lang/fr/home.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Accueil",
3 | "subtitle": "Allumettes",
4 | "filters": {
5 | "notStarted": "Pas commencé",
6 | "running": "Fonctionnement",
7 | "completed": "Terminé",
8 | "archived": "Archivé"
9 | },
10 | "table": {
11 | "server": "Serveur",
12 | "map": "Carte",
13 | "team1": "Équipe 1",
14 | "team2": "Équipe 2",
15 | "status": "Statut"
16 | },
17 | "links": {
18 | "edit": "Editer le match",
19 | "details": "Détails du match"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/lang/fr/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import all sub translation files
3 | */
4 | import general from './general';
5 | import home from './home';
6 | import servers from './servers';
7 | import about from './about';
8 | import detail from './detail';
9 | import create from './create';
10 | import edit from './edit';
11 | import settings from './settings';
12 |
13 | /**
14 | * Export as one translation file
15 | */
16 | export default {general,home,servers,about,detail,create,edit,settings};
17 |
--------------------------------------------------------------------------------
/frontend/lang/fr/servers.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Les serveurs",
3 | "subtitle": "Les serveurs",
4 | "table": {
5 | "server": "Serveur",
6 | "status": "Statut"
7 | },
8 | "available": "Disponible",
9 | "matchIsRunning": "Le match est en cours"
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/lang/fr/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Réglages",
3 | "subtitle": "Réglages",
4 | "matchGroups": {
5 | "title": "Groupes de match",
6 | "available": "Voici tous les groupes de match disponibles",
7 | "create": "Vous pouvez aussi en créer un nouveau",
8 | "createButton": "Créer"
9 | },
10 | "challonge": {
11 | "title": "Challonge",
12 | "description": "Pour importer les correspondances dans la base de données, sélectionnez les options ci-dessous et cliquez sur: Importer.",
13 | "tournament": "Tournoi",
14 | "matchGroup": "Groupe de match",
15 | "matchType": "Type de match",
16 | "gameMode": "Mode de jeu",
17 | "server": "Serveur",
18 | "knifeConfig": "CSGO Knife Config",
19 | "mainConfig": "Configuration principale de CSGO",
20 | "import": "Importation"
21 | },
22 | "csv": {
23 | "title": "CSV",
24 | "descriptionPart1": "Pour importer un fichier CSV",
25 | "descriptionPart2": "Exemple",
26 | "descriptionPart3": "téléchargez votre fichier CSV ci-dessous et cliquez sur: Importer",
27 | "csv": "CSV",
28 | "matchGroup": "Groupe de match",
29 | "matchType": "Type de match",
30 | "gameMode": "Mode de jeu",
31 | "server": "Serveur",
32 | "knifeConfig": "CSGO Knife Config",
33 | "mainConfig": "Configuration principale de CSGO",
34 | "import": "Importation"
35 | },
36 | "archive": {
37 | "title": "Archiver",
38 | "description": "Cliquez sur le bouton d'archive ci-dessous pour archiver les matchs terminés.",
39 | "archive": "Archiver"
40 | },
41 | "forceArchive": {
42 | "title": "Forcer les correspondances d'archives",
43 | "description": "Cliquez sur le bouton d'archive ci-dessous pour forcer l'archivage d'une correspondance.",
44 | "archive": "Archiver"
45 | },
46 | "log": {
47 | "title": "Journaux du serveur"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/frontend/lang/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import all language packs
3 | */
4 | import en from "./en";
5 | import fr from "./fr";
6 | import de from "./de";
7 | import nl from "./nl";
8 |
9 | /**
10 | * Export all languages as one object
11 | */
12 | export default {en, fr, de, nl};
13 |
--------------------------------------------------------------------------------
/frontend/lang/nl/about.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Info over",
3 | "subtitle": "Info over",
4 | "devWarning": "U voert een ontwikkelingsversie uit !!",
5 | "oldWarning": "U gebruikt een oudere versie !! Werk uw versie binnenkort bij ...",
6 | "descriptionTitle": "Omschrijving",
7 | "description": "Een webpaneel om een CS:GO-server te besturen",
8 | "version": "Versie",
9 | "currentVersion": "Huidige versie",
10 | "latestVersion": "Laatste versie",
11 | "contributors": "Medewerkers",
12 | "project": "Project",
13 | "backendStructure": "Backend structuur",
14 | "frontendStructure": "Frontend structuur"
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/lang/nl/create.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Creër een nieuwe match",
3 | "subtitle": "Creër een nieuwe match",
4 | "team1": "Team 1",
5 | "team2": "Team 2",
6 | "name": "Naam",
7 | "countryCode": "Landcode",
8 | "selectCountry": "Selecteer land",
9 | "generalMatchSettings": "Algemene instellingen",
10 | "matchGroup": "Match Groep",
11 | "server": "Server",
12 | "defaultServerMap": "Standaard serverkaart",
13 | "matchType": "Match Type",
14 | "gameMode": "Spelmodus",
15 | "csgoKnifeConfig": "CSGO Knife Config",
16 | "csgoMainConfig": "CSGO Main Config",
17 | "selectGroup": "Selecteer een groep",
18 | "selectMatchType": "Selecteer een matchtype",
19 | "selectGameMode": "Selecteer een spelmodus",
20 | "selectServer": "Selecteer een server",
21 | "selectConfig": "Selecteer een config",
22 | "create": "Creëren"
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/lang/nl/detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Wedstrijd",
3 | "subtitle": "Match details",
4 | "matchGroup": "Match Group",
5 | "matchType": "Match Type",
6 | "gameMode": "Spelmodus",
7 | "team1Name": "Team 1 Naam",
8 | "team1Country": "Team 1 land",
9 | "team2Name": "Team 2 Naam",
10 | "team2Country": "Team 2 Land",
11 | "server": "Server",
12 | "map": "Kaart",
13 | "csgoKnifeConfig": "CSGO Knife Config",
14 | "csgoMainConfig": "CSGO Main Config",
15 | "currentMatchStatus": "Huidige wedstrijdstatus",
16 | "liveScoring": "Live scores",
17 | "noScoresAvailable": "Nog geen scores beschikbaar...",
18 | "matchControls": "Overeenkomsten aanpassen",
19 | "matchControlsLocked": "Match controls locked! Reden:",
20 | "matchControlsLockedReason1": "Deze wedstrijd is al afgelopen!",
21 | "matchControlsLockedReason2": "Een andere match draait al op dezelfde server!",
22 | "connectServer": "Verbind met server",
23 | "startWarmup": "Begin met opwarmen",
24 | "startKnifeRound": "Begin met het mes ronde",
25 | "startMatch": "Begin wedstrijd",
26 | "endMatch": "Einde wedstrijd",
27 | "serverControls": "Serverbesturingselementen",
28 | "serverControlsLocked": "Serverbesturing vergrendeld! Reden:",
29 | "serverControlsLockedReason1": "Deze wedstrijd is niet gestart!",
30 | "serverControlsLockedReason2": "Deze wedstrijd is al afgelopen!",
31 | "resumeGame": "Spel hervatten",
32 | "pauseGame": "Spel pauzeren",
33 | "switchTeamSides": "Van teamzijde wisselen",
34 | "selectMap": "Selecteer kaart",
35 | "switchMap": "Wissel van kaart",
36 | "say": "Zeggen",
37 | "restartGame": "Spel opnieuw opstarten",
38 | "connectServerAlert": {
39 | "title": "Connecteer met de server?",
40 | "body": "Let op: zorg ervoor dat er ten minste één CS:GO-client is verbonden met de server voordat u op Ja klikt!"
41 | },
42 | "restartGameAlert": {
43 | "title": "Spel opnieuw opstarten?",
44 | "body": "Waarschuwing! Hiermee worden alle scores op 0 gezet en wordt de volledige match opnieuw gestart!"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/frontend/lang/nl/edit.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Bewerk de overeenkomst",
3 | "subtitle": "Bewerk de overeenkomst",
4 | "match": "Wedstrijd",
5 | "team1": "Team 1",
6 | "team2": "Team 2",
7 | "name": "Naam",
8 | "countryCode": "Landcode",
9 | "generalMatchSettings": "Algemene instellingen",
10 | "matchGroup": "Match Groep",
11 | "server": "Server",
12 | "defaultServerMap": "Standaard serverkaart",
13 | "matchType": "Match Type",
14 | "gameMode": "Spelmodus",
15 | "csgoKnifeConfig": "CSGO Knife Config",
16 | "csgoMainConfig": "CSGO Main Config",
17 | "save": "Opslaan"
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/lang/nl/general.json:
--------------------------------------------------------------------------------
1 | {
2 | "header": {
3 | "connected": "Verbonden",
4 | "reconnecting": "Opnieuw verbinding maken ...",
5 | "disconnected": "Verbinding verbroken",
6 | "links": {
7 | "home": "Thuis",
8 | "serverOverview": "Server overzicht",
9 | "createMatch": "Creër een match",
10 | "settings": "Instellingen"
11 | }
12 | },
13 | "footer": {
14 | "about": "Info over",
15 | "loadedIn": "Geladen in",
16 | "seconds": "seconden"
17 | },
18 | "notFound": {
19 | "title": "Niet gevonden",
20 | "subtitle": "Niet gevonden",
21 | "body": "Gebruik het menu hierboven om naar een andere pagina te gaan."
22 | },
23 | "alert": {
24 | "yes": "Ja",
25 | "no": "Nee"
26 | },
27 | "connection": {
28 | "error": {
29 | "title": "Whoops!",
30 | "line1": "De verbinding met de server is verloren!",
31 | "line2": "Er is een poging tot opnieuw verbinden gemaakt, maar de server reageerde niet op tijd.",
32 | "line3": "Probeer deze pagina te verversen..."
33 | },
34 | "reconnecting": {
35 | "title": "Reconnecting!",
36 | "line1": "De verbinding met de server is verloren!",
37 | "line2": "We proberen opnieuw verbinding te maken met de server.",
38 | "line3": "Even geduld aub..."
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/frontend/lang/nl/home.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Thuis",
3 | "subtitle": "Wedstrijden",
4 | "filters": {
5 | "notStarted": "Niet begonnen",
6 | "running": "Bezig",
7 | "completed": "Voltooid",
8 | "archived": "Gearchiveerd"
9 | },
10 | "table": {
11 | "server": "Server",
12 | "map": "Kaart",
13 | "team1": "Team 1",
14 | "team2": "Team 2",
15 | "status": "Staat"
16 | },
17 | "links": {
18 | "edit": "Bewerken",
19 | "details": "Match details"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/lang/nl/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import all sub translation files
3 | */
4 | import general from './general';
5 | import home from './home';
6 | import servers from './servers';
7 | import about from './about';
8 | import detail from './detail';
9 | import create from './create';
10 | import edit from './edit';
11 | import settings from './settings';
12 |
13 | /**
14 | * Export as one translation file
15 | */
16 | export default {general,home,servers,about,detail,create,edit,settings};
17 |
--------------------------------------------------------------------------------
/frontend/lang/nl/servers.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Servers",
3 | "subtitle": "Servers",
4 | "table": {
5 | "server": "Server",
6 | "status": "Staat"
7 | },
8 | "available": "Beschikbaar",
9 | "matchIsRunning": "Wedstrijd loopt"
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/lang/nl/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Instellingen",
3 | "subtitle": "Instellingen",
4 | "matchGroups": {
5 | "title": "Groepen",
6 | "available": "Hier zijn alle beschikbare matchgroepen",
7 | "create": "U kunt ook een nieuwe maken",
8 | "createButton": "Creëren"
9 | },
10 | "challonge": {
11 | "title": "Challonge",
12 | "description": "Om de challonge-overeenkomsten met de database te importeren, selecteert u de onderstaande opties en klikt u op: Importeren",
13 | "tournament": "Toernooi",
14 | "matchGroup": "Match Group",
15 | "matchType": "Match Type",
16 | "gameMode": "Spelmodus",
17 | "server": "Server",
18 | "knifeConfig": "CSGO Knife Config",
19 | "mainConfig": "CSGO Main Config",
20 | "import": "Importeren"
21 | },
22 | "csv": {
23 | "title": "CSV",
24 | "descriptionPart1": "Om een CSV te importeren",
25 | "descriptionPart2": "Voorbeeld",
26 | "descriptionPart3": "upload hieronder CSV en klik op: Importeren",
27 | "csv": "CSV",
28 | "matchGroup": "Match Group",
29 | "matchType": "Match Type",
30 | "gameMode": "Spelmodus",
31 | "server": "Server",
32 | "knifeConfig": "CSGO Knife Config",
33 | "mainConfig": "CSGO Main Config",
34 | "import": "Importeren"
35 | },
36 | "archive": {
37 | "title": "Archief",
38 | "description": "Klik op de archiefknop hieronder om voltooide wedstrijden te archiveren",
39 | "archive": "Archief"
40 | },
41 | "forceArchive": {
42 | "title": "Forceer archief",
43 | "description": "Klik op de archiefknop hieronder om het archief te forceren",
44 | "archive": "Archief"
45 | },
46 | "log": {
47 | "title": "Server Logs"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/frontend/main.js:
--------------------------------------------------------------------------------
1 | import {h, Component, render} from 'preact';
2 | import Router from 'preact-router';
3 | import {connect, Provider} from 'unistore/preact';
4 | import mitt from 'mitt';
5 |
6 | import Socket from './modules/socket';
7 | import store from './modules/store';
8 | import language from './modules/language';
9 |
10 | import Header from "./components/partials/Header";
11 | import Home from './components/Home';
12 | import Servers from "./components/Servers";
13 | import Create from './components/Create';
14 | import Edit from './components/Edit';
15 | import Detail from './components/Detail';
16 | import Settings from './components/Settings';
17 | import Log from './components/settings/Log';
18 | import NotFound from './components/NotFound';
19 |
20 | import Notification from './components/partials/Notification';
21 | import Breadcrumbs from "./components/partials/Breadcrumbs";
22 | import Footer from "./components/partials/Footer";
23 | import About from "./components/About";
24 |
25 | class App extends Component {
26 | /**
27 | * Constructor
28 | */
29 | constructor() {
30 | super();
31 |
32 | this.state = {
33 | connected: false,
34 | reconnecting: false
35 | };
36 |
37 | Socket.initialize(window.location.host, () => this.connected(), () => this.disconnected() , () => this.reconnecting());
38 | window.events = mitt();
39 | window.site = {};
40 | window.site.production = process.env.NODE_ENV === 'production';
41 |
42 | language.init();
43 | }
44 |
45 | /**
46 | * Function when socket connects
47 | */
48 | connected() {
49 | this.setState({
50 | connected: true,
51 | reconnecting: false
52 | });
53 | }
54 |
55 | /**
56 | * Function when socket disconnects
57 | */
58 | disconnected() {
59 | this.setState({
60 | connected: false,
61 | reconnecting: false
62 | });
63 | }
64 |
65 | /**
66 | * Function when the socket is reconnecting
67 | */
68 | reconnecting() {
69 | this.setState({
70 | reconnecting: true
71 | });
72 | }
73 |
74 | /**
75 | * Catches the router events
76 | *
77 | * @param e
78 | */
79 | routerUpdate(e) {
80 | window.events.emit("router", {
81 | route: e.url
82 | });
83 | }
84 |
85 | /**
86 | * Preact render function
87 | *
88 | * @returns {*}
89 | */
90 | render() {
91 | return (
92 |
103 | );
104 | }
105 |
106 | /**
107 | * Renders the main app when connection is ready
108 | *
109 | * @return {*}
110 | */
111 | mainRender() {
112 | return (
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | )
125 | }
126 |
127 | /**
128 | * Renders the error message
129 | *
130 | * @return {*}
131 | */
132 | errorRender() {
133 | return (
134 |
135 |
{this.props.lang.general.connection.error.title}
136 |
137 | {this.props.lang.general.connection.error.line1}
138 | {this.props.lang.general.connection.error.line2}
139 | {this.props.lang.general.connection.error.line3}
140 |
141 |
142 | )
143 | }
144 |
145 | /**
146 | * Renders the reconnecting message
147 | *
148 | * @return {*}
149 | */
150 | reconnectRender() {
151 | return (
152 |
153 |
{this.props.lang.general.connection.reconnecting.title}
154 |
155 | {this.props.lang.general.connection.reconnecting.line1}
156 | {this.props.lang.general.connection.reconnecting.line2}
157 | {this.props.lang.general.connection.reconnecting.line3}
158 |
159 |
160 | )
161 | }
162 | }
163 |
164 | const DataApp = connect('lang')(App);
165 | render(, document.body);
166 | require('preact/debug');
167 |
--------------------------------------------------------------------------------
/frontend/modules/language.js:
--------------------------------------------------------------------------------
1 | import langFiles from "../lang";
2 | import store from './store';
3 | import storage from './storage';
4 |
5 | export default new class language {
6 | /**
7 | * Constructor
8 | */
9 | constructor() {
10 | window.langSwitch = this.set;
11 | }
12 |
13 | /**
14 | * Checks if the initial lang has been set and loads the lang set
15 | */
16 | init() {
17 | const currentLang = storage.get("lang");
18 | if(currentLang === null) {
19 | storage.set("lang", "en");
20 | this.set("en");
21 |
22 | return;
23 | }
24 |
25 | this.set(currentLang);
26 | }
27 |
28 | /**
29 | * Sets the new language
30 | *
31 | * @param lang
32 | */
33 | set(lang) {
34 | if(typeof langFiles[lang] !== "undefined") {
35 | storage.set("lang", lang);
36 |
37 | store.setState({
38 | lang: langFiles[lang]
39 | });
40 | } else {
41 | console.warn("Language not found!")
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/frontend/modules/socket.js:
--------------------------------------------------------------------------------
1 | import Sockette from 'sockette';
2 | import store from './store';
3 | import systemNotification from './systemNotification';
4 |
5 | export default new class Socket {
6 | /**
7 | * Function to setup the socket connection
8 | *
9 | * @param url
10 | * @param connectedCallback
11 | * @param disconnectedCallback
12 | * @param reconnectingCallback
13 | */
14 | initialize(url, connectedCallback, disconnectedCallback, reconnectingCallback) {
15 | this.config = {
16 | url: `ws://${url}/`
17 | };
18 | this.ws = null;
19 | this.id = "";
20 | this.initialConnect = true;
21 | this.connectedCallback = connectedCallback;
22 | this.disconnectedCallback = disconnectedCallback;
23 | this.reconnectingCallback = reconnectingCallback;
24 |
25 | this.setup();
26 | }
27 |
28 | /**
29 | * Create socket connection with ws
30 | */
31 | setup() {
32 | this.ws = new Sockette(this.config.url, {
33 | timeout: 5e3,
34 | maxAttempts: 10,
35 | onopen: () => {
36 | if(this.initialConnect) {
37 | this.initialConnect = false;
38 | } else {
39 | this.send("general_wants_update", {})
40 | }
41 |
42 | console.log('[SOCKET] Connected!');
43 | },
44 | onmessage: (e) => this.message(e.data),
45 | onreconnect: () => {
46 | console.warn('[SOCKET] Reconnecting...');
47 | this.reconnectingCallback();
48 | },
49 | onclose: () => console.warn('[SOCKET] Closed!'),
50 | onerror: e => console.error('[SOCKET] Error:', e),
51 | onmaximum: () => {
52 | console.warn('[SOCKET] Failed to reconnect!');
53 | this.ws.close();
54 | this.disconnectedCallback();
55 | }
56 | });
57 | }
58 |
59 | /**
60 | * Function to handle all incoming messages
61 | *
62 | * @param data
63 | */
64 | message(data) {
65 | const decodedMessage = atob(data);
66 | const message = JSON.parse(decodedMessage);
67 |
68 | if(message.instruction === "init") {
69 | console.log('[SOCKET] Init', message.data);
70 |
71 | store.setState(message.data);
72 |
73 | this.connectedCallback();
74 | }
75 |
76 | if(message.instruction === "update") {
77 | console.log('[SOCKET] Update', message.data);
78 |
79 | store.setState(message.data);
80 | }
81 |
82 | if(message.instruction === "log") {
83 | store.setState({
84 | logs: [message.data].concat(store.getState().logs)
85 | });
86 | }
87 |
88 | if(message.instruction === "notification") {
89 | console.log('[SOCKET] Notification', message.data);
90 |
91 | if(message.data.system) {
92 | systemNotification.sendNotification(message.data.message);
93 | } else {
94 | window.events.emit("notification", {
95 | title: message.data.message,
96 | color: message.data.color
97 | });
98 | }
99 | }
100 | }
101 |
102 | /**
103 | * Send a message to the server
104 | *
105 | * @param instruction
106 | * @param data
107 | */
108 | send(instruction, data) {
109 | this.ws.send(this.encrypt({
110 | instruction,
111 | data
112 | }));
113 | }
114 |
115 | /**
116 | * Encrypt a message
117 | *
118 | * @param data
119 | * @return {string}
120 | */
121 | encrypt(data) {
122 | const string = JSON.stringify(data);
123 | return btoa(string);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/frontend/modules/storage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Get an item in the storage
3 | *
4 | * @param {string} key
5 | * @return {null|{}}
6 | */
7 | const get = (key) => {
8 | const state = localStorage.getItem(key);
9 | if (state === null) return null;
10 | return JSON.parse(state);
11 | };
12 |
13 | /**
14 | * Set an item in the storage
15 | *
16 | * @param {string} key
17 | * @param {*} state
18 | */
19 | const set = (key, state) => {
20 | localStorage.setItem(key, JSON.stringify(state));
21 | };
22 |
23 | /**
24 | * Remove an item from the storage
25 | *
26 | * @param {string} key
27 | */
28 | const remove = (key) => {
29 | localStorage.removeItem(key);
30 | };
31 |
32 | export default { get, set, remove };
33 |
--------------------------------------------------------------------------------
/frontend/modules/store.js:
--------------------------------------------------------------------------------
1 | import createUnistore from 'unistore';
2 | import devtools from 'unistore/devtools';
3 |
4 | /**
5 | * Exports the store with the default state
6 | *
7 | * @return {any}
8 | */
9 | const createStore = () => {
10 | const initialState = {
11 | servers: [],
12 | groups: [],
13 | matches: [],
14 | maps: [],
15 | configs: {},
16 | challonge: [],
17 | logs: []
18 | };
19 |
20 | return process.env.NODE_ENV === 'production' ? createUnistore(initialState) : devtools(createUnistore(initialState));
21 | };
22 |
23 | /**
24 | * All action for mutating the store
25 | *
26 | * @return {*}
27 | */
28 | const actions = () => {
29 | return {
30 | setSocketData(state, payload) {
31 | return {
32 | servers: payload.servers,
33 | groups: payload.groups,
34 | matches: payload.matches,
35 | maps: payload.maps,
36 | configs: payload.configs,
37 | challonge: payload.challonge
38 | };
39 | },
40 | setLogData(state, payload) {
41 | return {
42 | logs: payload.logs
43 | };
44 | }
45 | };
46 | };
47 |
48 | export { actions };
49 | export default createStore();
50 |
--------------------------------------------------------------------------------
/frontend/modules/systemNotification.js:
--------------------------------------------------------------------------------
1 | import storage from './storage';
2 |
3 | export default new class systemNotification {
4 | /**
5 | * Constructor
6 | */
7 | constructor() {
8 | this.enabled = false;
9 | }
10 |
11 | /**
12 | * Requests the user for notification permissions
13 | *
14 | * @param callback
15 | */
16 | requestPermission(callback) {
17 | if (window.Notification && Notification.permission !== "granted") {
18 | Notification.requestPermission((status) => {
19 | if (Notification.permission !== status) {
20 | Notification.permission = status;
21 | }
22 |
23 | callback(Notification.permission);
24 | });
25 | }
26 | }
27 |
28 | /**
29 | * Toggles the notifications on and off
30 | *
31 | * @param callback
32 | */
33 | toggleNotifications(callback) {
34 | if(Notification.permission !== "granted") {
35 | this.requestPermission((status) => {
36 | if(status === "granted") {
37 | storage.set("notificationsEnabled", true);
38 | callback(true);
39 | } else {
40 | storage.set("notificationsEnabled", false);
41 | callback(false);
42 | }
43 | });
44 | } else {
45 | if(storage.get("notificationsEnabled")) {
46 | storage.set("notificationsEnabled", false);
47 | callback(false);
48 | } else {
49 | storage.set("notificationsEnabled", true);
50 | callback(true);
51 | }
52 | }
53 | }
54 |
55 | /**
56 | * Sends a notification
57 | *
58 | * @param text
59 | */
60 | sendNotification(text) {
61 | if (window.Notification && Notification.permission === "granted" && storage.get("notificationsEnabled")) {
62 | new Notification("CSGO Remote", {body: text});
63 | }
64 | }
65 |
66 | /**
67 | * Returns the current permission status
68 | *
69 | * @return {*}
70 | */
71 | currentPermissionStatus() {
72 | if(storage.get("notificationsEnabled") && Notification.permission === "granted") return true;
73 | return false;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/frontend/scss/components/create.scss:
--------------------------------------------------------------------------------
1 | .error {
2 | border: red 1px solid;
3 | }
4 |
--------------------------------------------------------------------------------
/frontend/scss/components/detail.scss:
--------------------------------------------------------------------------------
1 | .btn-detail {
2 | margin-bottom: 5px;
3 | }
4 |
5 | #map {
6 | width: 200px;
7 | display: inline;
8 | margin-bottom: 5px;
9 | }
10 |
11 | #message {
12 | width: 200px;
13 | display: inline;
14 | margin-bottom: 5px;
15 | }
16 |
17 | .status-error {
18 | color: red;
19 | margin-bottom: 10px;
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/scss/components/home.scss:
--------------------------------------------------------------------------------
1 | .filters {
2 | display: flex;
3 | float: right;
4 | flex-wrap: wrap;
5 |
6 | span {
7 | margin-right: 10px;
8 | }
9 |
10 | .custom-control {
11 | margin-right: 10px;
12 | margin-bottom: 10px;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/frontend/scss/components/icons/add.scss:
--------------------------------------------------------------------------------
1 | .add-icon {
2 | a {
3 | path {
4 | fill: rgba(255, 255, 255, 0.5);
5 | }
6 |
7 | &.active {
8 | path {
9 | fill: white;
10 | }
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/scss/components/icons/close.scss:
--------------------------------------------------------------------------------
1 | .close-icon {
2 | a {
3 | path {
4 | fill: rgba(255, 255, 255, 0.5);
5 | }
6 |
7 | &.active {
8 | path {
9 | fill: white;
10 | }
11 | }
12 | }
13 | }
14 |
15 | .close {
16 | font-size: inherit;
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/scss/components/icons/details.scss:
--------------------------------------------------------------------------------
1 | .details-icon {
2 | a {
3 | path {
4 | fill: rgba(255, 255, 255, 0.5);
5 | }
6 |
7 | &.active {
8 | path {
9 | fill: white;
10 | }
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/scss/components/icons/edit.scss:
--------------------------------------------------------------------------------
1 | .edit-icon {
2 | a {
3 | path {
4 | fill: rgba(255, 255, 255, 0.5);
5 | }
6 |
7 | &.active {
8 | path {
9 | fill: white;
10 | }
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/scss/components/icons/home.scss:
--------------------------------------------------------------------------------
1 | .home-icon {
2 | a {
3 | path {
4 | fill: rgba(255, 255, 255, 0.5);
5 | }
6 |
7 | &.active {
8 | path {
9 | fill: white;
10 | }
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/scss/components/icons/language.scss:
--------------------------------------------------------------------------------
1 | .language-icon {
2 | path {
3 | fill: rgba(255, 255, 255, 1);
4 | }
5 |
6 | &.active {
7 | path {
8 | fill: white;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/scss/components/icons/notification.scss:
--------------------------------------------------------------------------------
1 | .notification-icon {
2 | div {
3 | cursor: pointer;
4 |
5 | path {
6 | fill: rgba(255, 255, 255, 0.5);
7 | }
8 |
9 | &.active {
10 | path {
11 | fill: white;
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/scss/components/icons/servers.scss:
--------------------------------------------------------------------------------
1 | .servers-icon {
2 | a {
3 | path {
4 | fill: rgba(255, 255, 255, 0.5);
5 | }
6 |
7 | &.active {
8 | path {
9 | fill: white;
10 | }
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/scss/components/icons/settings.scss:
--------------------------------------------------------------------------------
1 | .settings-icon {
2 | margin-left: auto;
3 |
4 | a {
5 | path {
6 | fill: rgba(255, 255, 255, 0.5);
7 | }
8 |
9 | &.active {
10 | path {
11 | fill: white;
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/scss/components/partials/alert.scss:
--------------------------------------------------------------------------------
1 | #overlay {
2 | position: fixed;
3 | display: block;
4 | width: 100%;
5 | height: 100%;
6 | top: 0;
7 | left: 0;
8 | right: 0;
9 | bottom: 0;
10 | background-color: rgba(0, 0, 0, 0.5);
11 | z-index: 0;
12 | }
13 |
--------------------------------------------------------------------------------
/frontend/scss/components/partials/breadcrumbs.scss:
--------------------------------------------------------------------------------
1 | .breadcrumb-item + .breadcrumb-item::before {
2 | content: ">";
3 | }
4 |
5 | .breadcrumb-item {
6 | &.active {
7 | color: black;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/frontend/scss/components/partials/footer.scss:
--------------------------------------------------------------------------------
1 | .footer {
2 | margin-top: 20px;
3 | bottom: 0;
4 | width: 100%;
5 | height: 60px;
6 | line-height: 60px;
7 | background-color: #f5f5f5;
8 | }
9 |
10 | body > .container {
11 | padding: 60px 15px 0;
12 | }
13 |
14 | .footer > .container {
15 | padding-right: 15px;
16 | padding-left: 15px;
17 |
18 | display: flex;
19 | align-items: center;
20 | }
21 |
22 | .footer .dropup {
23 | margin-left: auto;
24 | }
25 |
26 | .footer .dropdown-item {
27 | cursor: pointer;
28 | }
29 |
--------------------------------------------------------------------------------
/frontend/scss/components/partials/header.scss:
--------------------------------------------------------------------------------
1 | .navbar {
2 | background: #222222;
3 | margin-bottom: 20px;
4 | min-height: 74px;
5 | }
6 |
7 | .nav-item::after {
8 | content: '';
9 | display: flex;
10 | width: 0px;
11 | height: 2px;
12 | background: #fec400;
13 | transition: 0.2s;
14 | }
15 |
16 | .nav-item:hover::after {
17 | width: 100%;
18 | }
19 |
20 | .nav-link {
21 | padding: 15px 5px;
22 | transition: 0.2s;
23 | }
24 |
25 | .dropdown-item.active, .dropdown-item:active {
26 | color: #212529;
27 | }
28 |
29 | .dropdown-item:focus, .dropdown-item:hover {
30 | background: #fec400;
31 | }
32 |
33 | .navbar-nav{
34 | flex-grow: 2;
35 | }
36 |
--------------------------------------------------------------------------------
/frontend/scss/components/partials/notification.scss:
--------------------------------------------------------------------------------
1 | #notifications {
2 | position: fixed;
3 | top: 0;
4 | left: 50%;
5 | width: 80%;
6 | margin-top: 10px;
7 | z-index: 999;
8 | transform: translate(-50%);
9 | }
10 |
--------------------------------------------------------------------------------
/frontend/scss/components/settings/log.scss:
--------------------------------------------------------------------------------
1 | .logs {
2 | background-color: #e9ecef;
3 | border-radius: 0.25rem;
4 | height: 400px;
5 | overflow: scroll;
6 | font-family: "Courier New", Arial;
7 | font-size: 12px;
8 | }
9 |
--------------------------------------------------------------------------------
/frontend/scss/global/base.scss:
--------------------------------------------------------------------------------
1 | // base styling
2 | *,
3 | *:before,
4 | *:after {
5 | margin: 0;
6 | padding: 0;
7 | box-sizing: inherit;
8 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
9 | }
10 |
11 | html,
12 | body {
13 | width: 100%;
14 | height: 100%;
15 | box-sizing: border-box;
16 | }
17 |
18 | #root {
19 | display: flex;
20 | flex-direction: column;
21 | height: 100%;
22 | }
23 |
24 | .container {
25 | flex-grow: 1;
26 | }
27 |
28 | .dropdown-menu {
29 | min-width: 0;
30 | padding: 0;
31 | }
32 |
33 | .dropdown-item {
34 | padding: 0px 16px;
35 | }
36 |
37 | a {
38 | color: #005B94;
39 | }
40 |
--------------------------------------------------------------------------------
/frontend/scss/global/normalize.scss:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS text size adjust after orientation change, without disabling
6 | * user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability when focused and also mouse hovered in all browsers.
95 | */
96 |
97 | a:active,
98 | a:hover {
99 | outline: 0;
100 | }
101 |
102 | /* Text-level semantics
103 | ========================================================================== */
104 |
105 | /**
106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
107 | */
108 |
109 | abbr[title] {
110 | border-bottom: 1px dotted;
111 | }
112 |
113 | /**
114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
115 | */
116 |
117 | b,
118 | strong {
119 | font-weight: bold;
120 | }
121 |
122 | /**
123 | * Address styling not present in Safari and Chrome.
124 | */
125 |
126 | dfn {
127 | font-style: italic;
128 | }
129 |
130 | /**
131 | * Address variable `h1` font-size and margin within `section` and `article`
132 | * contexts in Firefox 4+, Safari, and Chrome.
133 | */
134 |
135 | h1 {
136 | font-size: 4rem;
137 | margin: 0.67rem 0;
138 |
139 | }
140 |
141 | /**
142 | * Address styling not present in IE 8/9.
143 | */
144 |
145 | mark {
146 | background: #ff0;
147 | color: #000;
148 | }
149 |
150 | /**
151 | * Address inconsistent and variable font size in all browsers.
152 | */
153 |
154 | small {
155 | font-size: 80%;
156 | }
157 |
158 | /**
159 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
160 | */
161 |
162 | sub,
163 | sup {
164 | font-size: 75%;
165 | line-height: 0;
166 | position: relative;
167 | vertical-align: baseline;
168 | }
169 |
170 | sup {
171 | top: -0.5rem;
172 | }
173 |
174 | sub {
175 | bottom: -0.25rem;
176 | }
177 |
178 | /* Embedded content
179 | ========================================================================== */
180 |
181 | /**
182 | * Remove border when inside `a` element in IE 8/9/10.
183 | */
184 |
185 | img {
186 | border: 0;
187 | }
188 |
189 | /**
190 | * Correct overflow not hidden in IE 9/10/11.
191 | */
192 |
193 | svg:not(:root) {
194 | overflow: hidden;
195 | }
196 |
197 | /* Grouping content
198 | ========================================================================== */
199 |
200 | /**
201 | * Address margin not present in IE 8/9 and Safari.
202 | */
203 |
204 | figure {
205 | //margin: 1rem 40px;
206 | }
207 |
208 | /**
209 | * Address differences between Firefox and other browsers.
210 | */
211 |
212 | hr {
213 | -moz-box-sizing: content-box;
214 | box-sizing: content-box;
215 | height: 0;
216 | }
217 |
218 | /**
219 | * Contain overflow in all browsers.
220 | */
221 |
222 | pre {
223 | overflow: auto;
224 | }
225 |
226 | /**
227 | * Address odd `em`-unit font size rendering in all browsers.
228 | */
229 |
230 | code,
231 | kbd,
232 | pre,
233 | samp {
234 | font-family: monospace, monospace;
235 | font-size: 1rem;
236 | }
237 |
238 | /* Forms
239 | ========================================================================== */
240 |
241 | /**
242 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
243 | * styling of `select`, unless a `border` property is set.
244 | */
245 |
246 | /**
247 | * 1. Correct color not being inherited.
248 | * Known issue: affects color of disabled elements.
249 | * 2. Correct font properties not being inherited.
250 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
251 | */
252 |
253 | button,
254 | input,
255 | optgroup,
256 | select,
257 | textarea {
258 | color: inherit; /* 1 */
259 | font: inherit; /* 2 */
260 | margin: 0; /* 3 */
261 | }
262 |
263 | /**
264 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
265 | */
266 |
267 | button {
268 | overflow: visible;
269 | }
270 |
271 | /**
272 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
273 | * All other form control elements do not inherit `text-transform` values.
274 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
275 | * Correct `select` style inheritance in Firefox.
276 | */
277 |
278 | button,
279 | select {
280 | text-transform: none;
281 | }
282 |
283 | /**
284 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
285 | * and `video` controls.
286 | * 2. Correct inability to style clickable `input` types in iOS.
287 | * 3. Improve usability and consistency of cursor style between image-type
288 | * `input` and others.
289 | */
290 |
291 | button,
292 | html input[type="button"], /* 1 */
293 | input[type="reset"],
294 | input[type="submit"] {
295 | -webkit-appearance: button; /* 2 */
296 | cursor: pointer; /* 3 */
297 | }
298 |
299 | /**
300 | * Re-set default cursor for disabled elements.
301 | */
302 |
303 | button[disabled],
304 | html input[disabled] {
305 | cursor: default;
306 | }
307 |
308 | /**
309 | * Remove inner padding and border in Firefox 4+.
310 | */
311 |
312 | button::-moz-focus-inner,
313 | input::-moz-focus-inner {
314 | border: 0;
315 | padding: 0;
316 | }
317 |
318 | /**
319 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
320 | * the UA stylesheet.
321 | */
322 |
323 | input {
324 | line-height: normal;
325 | }
326 |
327 | /**
328 | * It's recommended that you don't attempt to style these elements.
329 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
330 | *
331 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
332 | * 2. Remove excess padding in IE 8/9/10.
333 | */
334 |
335 | input[type="checkbox"],
336 | input[type="radio"] {
337 | box-sizing: border-box; /* 1 */
338 | padding: 0; /* 2 */
339 | }
340 |
341 | /**
342 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
343 | * `font-size` values of the `input`, it causes the cursor style of the
344 | * decrement button to change from `default` to `text`.
345 | */
346 |
347 | input[type="number"]::-webkit-inner-spin-button,
348 | input[type="number"]::-webkit-outer-spin-button {
349 | height: auto;
350 | }
351 |
352 | /**
353 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
354 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
355 | * (include `-moz` to future-proof).
356 | */
357 |
358 | input[type="search"] {
359 | -webkit-appearance: textfield; /* 1 */
360 | -moz-box-sizing: content-box;
361 | -webkit-box-sizing: content-box; /* 2 */
362 | box-sizing: content-box;
363 | }
364 |
365 | /**
366 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
367 | * Safari (but not Chrome) clips the cancel button when the search input has
368 | * padding (and `textfield` appearance).
369 | */
370 |
371 | input[type="search"]::-webkit-search-cancel-button,
372 | input[type="search"]::-webkit-search-decoration {
373 | -webkit-appearance: none;
374 | }
375 |
376 | /**
377 | * Define consistent border, margin, and padding.
378 | */
379 |
380 | fieldset {
381 | border: 1px solid #c0c0c0;
382 | margin: 0 2px;
383 | padding: 0.35rem 0.625rem 0.75rem;
384 | }
385 |
386 | /**
387 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
388 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
389 | */
390 |
391 | legend {
392 | border: 0; /* 1 */
393 | padding: 0; /* 2 */
394 | }
395 |
396 | /**
397 | * Remove default vertical scrollbar in IE 8/9/10/11.
398 | */
399 |
400 | textarea {
401 | overflow: auto;
402 | }
403 |
404 | /**
405 | * Don't inherit the `font-weight` (applied by a rule above).
406 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
407 | */
408 |
409 | optgroup {
410 | font-weight: bold;
411 | }
412 |
413 | /* Tables
414 | ========================================================================== */
415 |
416 | /**
417 | * Remove most spacing between table cells.
418 | */
419 |
420 | table {
421 | border-collapse: collapse;
422 | border-spacing: 0;
423 | }
424 |
425 | td,
426 | th {
427 | padding: 0;
428 | }
429 |
--------------------------------------------------------------------------------
/frontend/scss/global/utils.scss:
--------------------------------------------------------------------------------
1 | // Visibility
2 | .hidden {
3 | visibility: hidden;
4 | }
5 |
6 | .visible {
7 | visibility: visible;
8 | }
9 |
10 | .flex {
11 | display: flex;
12 | align-items: center;
13 | justify-content: center;
14 | }
15 |
16 | .vertical {
17 | flex-direction: column;
18 | }
19 |
20 | .horizontal {
21 | flex-direction: row;
22 | }
23 |
24 | .text-center {
25 | text-align: center;
26 | }
27 |
28 | .text-right {
29 | text-align: right;
30 | }
31 |
--------------------------------------------------------------------------------
/frontend/scss/mixins/helpers.scss:
--------------------------------------------------------------------------------
1 | // helpers
2 |
3 | // visually hide copy
4 | @mixin hideType {
5 | font-size: 0;
6 | line-height: 0;
7 | text-indent: -9999999px;
8 | overflow: hidden;
9 | }
10 |
11 | // self clear children
12 | @mixin clearfix {
13 | &:after {
14 | content: "";
15 | display: table;
16 | clear: both;
17 | }
18 | }
19 |
20 | // fallback for IE (no object-fit support)
21 | // parent needs to have overflow hidden
22 | @mixin objectfitIE {
23 | position: absolute;
24 | top: 50%;
25 | left: 50%;
26 | z-index: 1;
27 | min-width: 100%;
28 | min-height: 100%;
29 | width: auto;
30 | height: auto;
31 | transform: translate(-50%, -50%);
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/scss/mixins/mediaqueries.scss:
--------------------------------------------------------------------------------
1 | @mixin breakpoint($map) {
2 | $query: "";
3 | @if map-has-key($map, min) { $query: append($query, "(min-width: #{map-get($map, min)})") }
4 | @if map-has-key($map, min) and map-has-key($map, max) { $query: append($query, "and") }
5 | @if map-has-key($map, max) { $query: append($query, "(max-width: #{map-get($map, max)})") }
6 | @media screen and #{$query} { @content; }
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/scss/mixins/typography.scss:
--------------------------------------------------------------------------------
1 | // font-smoothing
2 |
3 | @mixin font-smoothing($light-text: true) {
4 | @if $light-text == true {
5 | -webkit-font-smoothing: antialiased;
6 | -moz-osx-font-smoothing: grayscale;
7 | }
8 | @else {
9 | -webkit-font-smoothing: subpixel-antialiased;
10 | -moz-osx-font-smoothing: auto;
11 | }
12 | }
13 |
14 | @mixin font($type: main, $fontsmoothing: false) {
15 | @if $type == main {
16 | font-family: $fontTypeBase;
17 | font-weight: 400;
18 | font-style: normal;
19 | }
20 | @if $type == main-bold {
21 | font-family: $fontTypeBaseBold;
22 | font-weight: 500;
23 | font-style: normal;
24 | }
25 | @if $type == main-italic {
26 | font-family: $fontTypeBaseItalic;
27 | font-weight: 400;
28 | font-style: italic;
29 | }
30 | @if $fontsmoothing == true {
31 | @include font-smoothing(true);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/scss/style.scss:
--------------------------------------------------------------------------------
1 | // Bootstrap
2 | @import "~bootstrap/scss/bootstrap";
3 |
4 | // imports
5 | @import
6 |
7 | // variables
8 | 'variables/breakpoints',
9 | 'variables/fonts',
10 | 'variables/colors',
11 | 'variables/animations',
12 |
13 | // mixins
14 | 'mixins/mediaqueries',
15 | 'mixins/typography',
16 | 'mixins/helpers',
17 |
18 | // global
19 | 'global/normalize',
20 | 'global/base',
21 | 'global/utils',
22 |
23 | // components
24 | 'components/icons/add',
25 | 'components/icons/servers',
26 | 'components/icons/notification',
27 | 'components/icons/language',
28 | 'components/icons/close',
29 | 'components/icons/edit',
30 | 'components/icons/home',
31 | 'components/icons/details',
32 | 'components/icons/settings',
33 | 'components/partials/header',
34 | 'components/partials/footer',
35 | 'components/partials/breadcrumbs',
36 | 'components/create',
37 | 'components/partials/alert',
38 | 'components/detail',
39 | 'components/home',
40 | 'components/partials/notification',
41 | 'components/settings/log';
42 |
43 | // views
44 |
--------------------------------------------------------------------------------
/frontend/scss/variables/animations.scss:
--------------------------------------------------------------------------------
1 | // animations
2 |
3 | // easings
4 | $global-easing: cubic-bezier(0.850, 0.210, 0.165, 0.780);
5 | $elastic-easing: cubic-bezier(0.785, -0.565, 0.245, 1.640);
6 | $elastic-out: cubic-bezier(0.240, 1.650, 0.425, 0.920);
7 | $elastic-in: cubic-bezier(0.375, 0.110, 0.725, -0.600);
8 |
--------------------------------------------------------------------------------
/frontend/scss/variables/breakpoints.scss:
--------------------------------------------------------------------------------
1 | // Breakpoints
2 |
3 | $small: 400px;
4 | $medium: 600px;
5 | $large: 900px;
6 | $xlarge: 1200px;
7 |
8 | // Ranges starting form a breakpoint
9 | $small-up: ( min: $small );
10 | $medium-up: ( min: $medium );
11 | $large-up: ( min: $large );
12 | $xlarge-up: ( min: $xlarge );
13 |
14 | // Ranges between breakpoints
15 | $small-to-medium: ( min: $small, max: #{$medium - 1} );
16 | $medium-to-large: ( min: $medium, max: #{$large - 1} );
17 |
18 | // Ranges up to a breakpoint
19 | $small-down: ( max: #{$small - 1} );
20 | $medium-down: ( max: #{$medium - 1} );
21 |
--------------------------------------------------------------------------------
/frontend/scss/variables/colors.scss:
--------------------------------------------------------------------------------
1 | // colors
2 |
--------------------------------------------------------------------------------
/frontend/scss/variables/fonts.scss:
--------------------------------------------------------------------------------
1 | // 1. loading fonts
2 | // 2. defining global font-sizes
3 | // 3. defining global font-name variables
4 |
5 | // paths
6 | $path-to-font: '../fonts';
7 |
8 | // todo add custom fonts
9 |
10 | // Font sizes
11 | $sizeFontSmall: 14px;
12 | $sizeFontTitleLarge: 30px;
13 | $sizeFontSubTitle: 16px;
14 | $sizeFontHighlight: 20px;
15 |
16 | // Fonts
17 | $fontTypeBase: Calibri, sans-serif;
18 | $fontTypeBaseItalic: Calibri, sans-serif;
19 | $fontTypeBaseBold: Calibri, sans-serif;
20 | $fontTypeSemiBold: Calibri, sans-serif;
21 | $fontTypeHeader: Calibri, sans-serif;
22 |
--------------------------------------------------------------------------------
/frontend/test/lang.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import test suite
3 | */
4 | const should = require('should');
5 | const request = require('request');
6 |
7 | /**
8 | * Import packages needed for tests
9 | */
10 | const lang = require("../lang").default;
11 |
12 | /**
13 | * Launch test
14 | */
15 | describe("FRONTEND - Lang", () => {
16 | it('Lang.js should be defined', (done) => {
17 | lang.should.be.an.Object();
18 | done();
19 | });
20 |
21 | it('Lang.js should contain at least the EN lang', (done) => {
22 | lang.en.should.be.an.Object();
23 | done();
24 | });
25 |
26 | it('Lang.js all lang files should have same categories as EN lang', (done) => {
27 | const langs = Object.keys(lang);
28 | const categories = Object.keys(lang.en);
29 |
30 | // Remove EN from langs
31 | const item = langs.indexOf("en");
32 | if(item !== -1) {
33 | langs.splice(item, 1);
34 | }
35 |
36 | for(let i = 0; i < langs.length; i++) {
37 | const keys = Object.keys(lang[langs[i]]);
38 | keys.should.containDeep(categories);
39 | }
40 |
41 | done();
42 | });
43 |
44 | it('Lang.js all categories in lang files should have same elements as EN lang', (done) => {
45 | const langs = Object.keys(lang);
46 | const categorys = Object.keys(lang.en);
47 |
48 | // Remove EN from langs
49 | const item = langs.indexOf("en");
50 | if(item !== -1) {
51 | langs.splice(item, 1);
52 | }
53 |
54 | for(let lan = 0; lan < langs.length; lan++) {
55 | for(let cat = 0; cat < categorys.length; cat++) {
56 | const enElements = Object.keys(lang.en[categorys[cat]]);
57 | const otherElements = Object.keys(lang[langs[lan]][categorys[cat]]);
58 |
59 | otherElements.should.containDeep(enElements);
60 | }
61 | }
62 |
63 | done();
64 | });
65 | });
66 |
--------------------------------------------------------------------------------
/frontend/utils/Arrays.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Find an object in an array by id
3 | *
4 | * @param array
5 | * @param id
6 | * @return {*}
7 | */
8 | function findByIdInObjectArray(array, id) {
9 | for(let item = 0; item < array.length; item++) {
10 | if(typeof array[item].id !== "undefined" && array[item].id === id) {
11 | array[item].index = item;
12 | return array[item];
13 | }
14 | }
15 |
16 | return false;
17 | }
18 |
19 | /**
20 | * Checks if a server is in use by a match
21 | *
22 | * @param server
23 | * @param matches
24 | * @return {*}
25 | */
26 | function checkServerAvailability(server, matches) {
27 | for(let item = 0; item < matches.length; item++) {
28 | const match = matches[item];
29 |
30 | if(match.status > 0 && match.status < 99 && match.server === server) {
31 | return match;
32 | }
33 | }
34 |
35 | return false;
36 | }
37 |
38 | /**
39 | * Checks if a server is in use by a match
40 | *
41 | * @param id
42 | * @param server
43 | * @param matches
44 | * @return {*}
45 | */
46 | function checkServerAvailabilityForMatch(id, server, matches) {
47 | for(let item = 0; item < matches.length; item++) {
48 | const match = matches[item];
49 |
50 | if(match.id !== id && match.status > 0 && match.status < 99 && match.server === server) {
51 | return match;
52 | }
53 | }
54 |
55 | return false;
56 | }
57 |
58 | export {findByIdInObjectArray, checkServerAvailability, checkServerAvailabilityForMatch};
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "csgo-rcon-nodejs",
3 | "version": "0.0.0",
4 | "description": "NPM packages for csgo-rcon-nodejs",
5 | "private": true,
6 | "scripts": {
7 | "debug": "cd ./app/src && node --inspect-brk=0.0.0.0:5858 ./server.js",
8 | "start": "cd ./app/src && node ./server.js",
9 | "dev": "cd ./app/src && nodemon -L --ignore csgo-rcon.json --watch ./ ./server.js",
10 | "server:build": "pkg ./package.json --targets latest-linux-x64,latest-macos-x64,latest-win-x64 --out-path ./app/dist",
11 | "prewebpack": "rimraf ./public/dist",
12 | "webpack": "webpack --watch --watch-poll --mode development --config ./_scripts/webpack/webpack.config.js",
13 | "prewebpack:production": "rimraf ./public/dist",
14 | "webpack:production": "cross-env NODE_ENV=production webpack --mode production --config ./_scripts/webpack/webpack.config.js",
15 | "prebuild": "git describe --tags `git rev-list --tags --max-count=1` > ./app/src/config/version/version.txt",
16 | "prebuild:alpha": "git rev-parse HEAD > ./app/src/config/version/version.txt",
17 | "build": "npm run webpack:production && npm run server:build && rm ./app/src/config/version/version.txt",
18 | "build:alpha": "npm run webpack:production && npm run server:build && rm ./app/src/config/version/version.txt",
19 | "lint": "eslint -c ./package.json ./",
20 | "madge": "madge ./app/src && madge ./app/src --circular",
21 | "test": "npm run lint && npm run madge && npm run test:all",
22 | "test:all": "nyc --reporter=html --reporter=text mocha -R spec --recursive --require babel-core/register './{,!(node_modules)/**}/*.test.js'",
23 | "test:watch": "mocha -R spec --recursive --watch --require babel-core/register './{,!(node_modules)/**}/*.test.js'",
24 | "test:cleanup": "rimraf ./coverage ./.nyc_output"
25 | },
26 | "babel": {
27 | "presets": [
28 | "env"
29 | ]
30 | },
31 | "pkg": {
32 | "assets": [
33 | "_scripts/config/*",
34 | "_scripts/csgo-configs/**/*",
35 | "app/src/config/version/version.txt",
36 | "app/src/**/*.js",
37 | "app/src/**/*.ejs",
38 | "public/**/*",
39 | "LICENCE",
40 | "README.md"
41 | ]
42 | },
43 | "bin": "app/src/bundle.js",
44 | "author": "Glenn de Haan",
45 | "license": "MIT",
46 | "engines": {
47 | "node": ">=8.0.0"
48 | },
49 | "eslintConfig": {
50 | "parser": "babel-eslint",
51 | "parserOptions": {
52 | "ecmaVersion": 2018,
53 | "sourceType": "module",
54 | "ecmaFeatures": {
55 | "jsx": true
56 | }
57 | },
58 | "env": {
59 | "browser": true,
60 | "node": true
61 | },
62 | "rules": {
63 | "no-console": 0,
64 | "react/prop-types": 0,
65 | "comma-dangle": [
66 | "error",
67 | "never"
68 | ],
69 | "indent": [
70 | "error",
71 | 4
72 | ]
73 | },
74 | "extends": [
75 | "eslint:recommended",
76 | "plugin:react/recommended"
77 | ],
78 | "settings": {
79 | "react": {
80 | "pragma": "h",
81 | "version": "16.0"
82 | }
83 | }
84 | },
85 | "eslintIgnore": [
86 | "public/dist",
87 | "*.test.js"
88 | ],
89 | "dependencies": {
90 | "atob": "^2.1.2",
91 | "babel-core": "^6.26.3",
92 | "babel-eslint": "^10.0.1",
93 | "babel-loader": "^7.1.5",
94 | "babel-plugin-transform-react-jsx": "^6.24.1",
95 | "babel-preset-env": "^1.7.0",
96 | "babel-preset-react": "^6.24.1",
97 | "body-parser": "^1.18.3",
98 | "bootstrap": "^4.1.3",
99 | "btoa": "^1.2.1",
100 | "compression": "^1.7.3",
101 | "cross-env": "^5.2.0",
102 | "css-loader": "^0.28.11",
103 | "csvtojson": "^2.0.8",
104 | "deepmerge": "^2.2.1",
105 | "ejs": "^2.6.1",
106 | "express": "^4.16.4",
107 | "express-basic-auth": "^1.1.6",
108 | "express-ws": "^4.0.0",
109 | "mini-css-extract-plugin": "^0.4.4",
110 | "mitt": "^1.1.3",
111 | "mocha": "^5.2.0",
112 | "node-fetch": "^2.2.1",
113 | "node-json-db": "^0.9.1",
114 | "node-sass": "^4.10.0",
115 | "nyc": "^13.1.0",
116 | "optimize-css-assets-webpack-plugin": "^5.0.1",
117 | "pkg": "^4.3.4",
118 | "preact": "^8.3.1",
119 | "preact-router": "^2.6.1",
120 | "rimraf": "^2.6.2",
121 | "sass-loader": "^7.0.3",
122 | "should": "^13.2.3",
123 | "simple-node-logger": "^0.93.40",
124 | "size-plugin": "^1.0.1",
125 | "sockette": "^2.0.1",
126 | "srcds-rcon": "github:randunel/node-srcds-rcon",
127 | "uglify-es": "^3.3.10",
128 | "uglifyjs-webpack-plugin": "^2.0.1",
129 | "unfetch": "^4.0.1",
130 | "unistore": "^3.1.0",
131 | "uuid": "^3.3.2",
132 | "webpack": "^4.25.1",
133 | "webpack-cli": "^3.1.2",
134 | "webpack-manifest-plugin": "^2.0.3"
135 | },
136 | "devDependencies": {
137 | "eslint": "^5.8.0",
138 | "eslint-loader": "^2.0.0",
139 | "eslint-plugin-react": "^7.10.0",
140 | "madge": "^3.3.0",
141 | "nodemon": "^1.18.6"
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/public/assets/csgo-remote_matches.csv:
--------------------------------------------------------------------------------
1 | team_1_name,team_2_name
2 | Cloud9,Fnatic
3 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "CSGO Remote",
3 | "short_name": "CSGO Remote",
4 | "icons": [
5 | {
6 | "src": "/images/icons/144.png",
7 | "type": "image/png",
8 | "sizes": "144x144"
9 | },
10 | {
11 | "src": "/images/icons/192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "/images/icons/512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": "/",
22 | "scope": "/",
23 | "display": "standalone",
24 | "orientation": "portrait",
25 | "background_color": "#000000",
26 | "theme_color": "#000000"
27 | }
28 |
--------------------------------------------------------------------------------