Init cs2 rcon panel
This commit is contained in:
commit
78d9792a03
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
*.db
|
44
app.js
Normal file
44
app.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const session = require('express-session');
|
||||||
|
const ejs = require('ejs');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
|
app.use(session({
|
||||||
|
secret: 'keyboard cat',
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
const gameModule = require('./routes/game');
|
||||||
|
const gameRoutes = gameModule.router;
|
||||||
|
|
||||||
|
const serverRoutes = require('./routes/server');
|
||||||
|
const authRoutes = require('./routes/auth');
|
||||||
|
|
||||||
|
const port = 3000;
|
||||||
|
|
||||||
|
// Serve static files from the 'public' directory
|
||||||
|
app.use(express.static('public'));
|
||||||
|
|
||||||
|
// Set the view engine to EJS
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
|
|
||||||
|
app.use('/', gameRoutes);
|
||||||
|
app.use('/', serverRoutes);
|
||||||
|
app.use('/', authRoutes)
|
||||||
|
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
if (req.session.user) {
|
||||||
|
res.redirect('/servers');
|
||||||
|
} else {
|
||||||
|
res.render('login');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Server is running on port ${port}`);
|
||||||
|
});
|
17
cfg/knife.cfg
Normal file
17
cfg/knife.cfg
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
mp_ct_default_secondary ""
|
||||||
|
mp_free_armor 1
|
||||||
|
mp_freezetime 10
|
||||||
|
mp_give_player_c4 0
|
||||||
|
mp_maxmoney 0
|
||||||
|
mp_respawn_immunitytime 0
|
||||||
|
mp_respawn_on_death_ct 0
|
||||||
|
mp_respawn_on_death_t 0
|
||||||
|
mp_roundtime 1.92
|
||||||
|
mp_roundtime_defuse 1.92
|
||||||
|
mp_roundtime_hostage 1.92
|
||||||
|
mp_t_default_secondary ""
|
||||||
|
mp_round_restart_delay 3
|
||||||
|
|
||||||
|
say Knife!
|
||||||
|
say Knife!
|
||||||
|
say Knife!
|
114
cfg/live.cfg
Normal file
114
cfg/live.cfg
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
ammo_grenade_limit_default 1
|
||||||
|
ammo_grenade_limit_flashbang 2
|
||||||
|
ammo_grenade_limit_total 4
|
||||||
|
bot_quota 0
|
||||||
|
cash_player_bomb_defused 300
|
||||||
|
cash_player_bomb_planted 300
|
||||||
|
cash_player_damage_hostage -30
|
||||||
|
cash_player_interact_with_hostage 300
|
||||||
|
cash_player_killed_enemy_default 300
|
||||||
|
cash_player_killed_enemy_factor 1
|
||||||
|
cash_player_killed_hostage -1000
|
||||||
|
cash_player_killed_teammate -300
|
||||||
|
cash_player_rescued_hostage 1000
|
||||||
|
cash_team_bonus_shorthanded 1000
|
||||||
|
cash_team_elimination_bomb_map 3250
|
||||||
|
cash_team_elimination_hostage_map_ct 3000
|
||||||
|
cash_team_elimination_hostage_map_t 3000
|
||||||
|
cash_team_hostage_alive 0
|
||||||
|
cash_team_hostage_interaction 600
|
||||||
|
cash_team_loser_bonus 1400
|
||||||
|
cash_team_loser_bonus_consecutive_rounds 500
|
||||||
|
cash_team_planted_bomb_but_defused 800
|
||||||
|
cash_team_rescued_hostage 600
|
||||||
|
cash_team_terrorist_win_bomb 3500
|
||||||
|
cash_team_win_by_defusing_bomb 3500
|
||||||
|
cash_team_win_by_hostage_rescue 2900
|
||||||
|
cash_team_win_by_time_running_out_bomb 3250
|
||||||
|
cash_team_win_by_time_running_out_hostage 3250
|
||||||
|
ff_damage_reduction_bullets 0.33
|
||||||
|
ff_damage_reduction_grenade 0.85
|
||||||
|
ff_damage_reduction_grenade_self 1
|
||||||
|
ff_damage_reduction_other 0.4
|
||||||
|
mp_afterroundmoney 0
|
||||||
|
mp_autokick 0
|
||||||
|
mp_autoteambalance 0
|
||||||
|
mp_backup_restore_load_autopause 1
|
||||||
|
mp_backup_round_auto 1
|
||||||
|
mp_buy_anywhere 0
|
||||||
|
mp_buy_during_immunity 0
|
||||||
|
mp_buytime 20
|
||||||
|
mp_c4timer 40
|
||||||
|
mp_ct_default_melee weapon_knife
|
||||||
|
mp_ct_default_primary ""
|
||||||
|
mp_ct_default_secondary weapon_hkp2000
|
||||||
|
mp_death_drop_defuser 1
|
||||||
|
mp_death_drop_grenade 2
|
||||||
|
mp_death_drop_gun 1
|
||||||
|
mp_defuser_allocation 0
|
||||||
|
mp_display_kill_assists 1
|
||||||
|
mp_endmatch_votenextmap 0
|
||||||
|
mp_forcecamera 1
|
||||||
|
mp_free_armor 0
|
||||||
|
mp_freezetime 15
|
||||||
|
mp_friendlyfire 1
|
||||||
|
mp_give_player_c4 1
|
||||||
|
mp_halftime 1
|
||||||
|
mp_halftime_duration 15
|
||||||
|
mp_halftime_pausetimer 0
|
||||||
|
mp_ignore_round_win_conditions 0
|
||||||
|
mp_limitteams 0
|
||||||
|
mp_match_can_clinch 1
|
||||||
|
mp_match_end_restart 1
|
||||||
|
mp_maxmoney 16000
|
||||||
|
mp_maxrounds 24
|
||||||
|
mp_molotovusedelay 0
|
||||||
|
mp_overtime_enable 1
|
||||||
|
mp_overtime_halftime_pausetimer 0
|
||||||
|
mp_overtime_maxrounds 6
|
||||||
|
mp_overtime_startmoney 10000
|
||||||
|
mp_playercashawards 1
|
||||||
|
mp_randomspawn 0
|
||||||
|
mp_respawn_immunitytime 0
|
||||||
|
mp_respawn_on_death_ct 0
|
||||||
|
mp_respawn_on_death_t 0
|
||||||
|
mp_round_restart_delay 7
|
||||||
|
mp_roundtime 1.92
|
||||||
|
mp_roundtime_defuse 1.92
|
||||||
|
mp_roundtime_hostage 1.92
|
||||||
|
mp_solid_teammates 1
|
||||||
|
mp_starting_losses 1
|
||||||
|
mp_startmoney 800
|
||||||
|
mp_t_default_melee weapon_knife
|
||||||
|
mp_t_default_primary ""
|
||||||
|
mp_t_default_secondary weapon_glock
|
||||||
|
mp_teamcashawards 1
|
||||||
|
mp_timelimit 0
|
||||||
|
mp_weapons_allow_map_placed 1
|
||||||
|
mp_weapons_allow_zeus 1
|
||||||
|
mp_weapons_glow_on_ground 0
|
||||||
|
mp_win_panel_display_time 3
|
||||||
|
occlusion_test_async 0
|
||||||
|
spec_freeze_deathanim_time 0
|
||||||
|
spec_freeze_panel_extended_time 0
|
||||||
|
spec_freeze_time 2
|
||||||
|
spec_freeze_time_lock 2
|
||||||
|
spec_replay_enable 0
|
||||||
|
sv_allow_votes 0
|
||||||
|
sv_auto_full_alltalk_during_warmup_half_end 0
|
||||||
|
sv_coaching_enabled 1
|
||||||
|
sv_competitive_official_5v5 1
|
||||||
|
sv_damage_print_enable 0
|
||||||
|
sv_deadtalk 1
|
||||||
|
sv_hibernate_postgame_delay 300
|
||||||
|
sv_holiday_mode 0
|
||||||
|
sv_ignoregrenaderadio 0
|
||||||
|
sv_infinite_ammo 0
|
||||||
|
sv_occlude_players 1
|
||||||
|
sv_talk_enemy_dead 0
|
||||||
|
sv_talk_enemy_living 0
|
||||||
|
sv_voiceenable 1
|
||||||
|
tv_relayvoice 0
|
||||||
|
say Match is live!
|
||||||
|
say Match is live!
|
||||||
|
say Match is live!
|
70
cfg/live_kancha.cfg
Normal file
70
cfg/live_kancha.cfg
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
ammo_grenade_limit_default 1
|
||||||
|
ammo_grenade_limit_flashbang 2
|
||||||
|
ammo_grenade_limit_total 4
|
||||||
|
bot_kick
|
||||||
|
bot_quota 0
|
||||||
|
mp_afterroundmoney 0
|
||||||
|
mp_autokick 0
|
||||||
|
mp_autoteambalance 0
|
||||||
|
mp_backup_restore_load_autopause 0
|
||||||
|
mp_backup_round_auto 1
|
||||||
|
mp_buytime 20
|
||||||
|
mp_c4timer 40
|
||||||
|
mp_ct_default_secondary "weapon_hkp2000"
|
||||||
|
mp_death_drop_gun 1
|
||||||
|
mp_endmatch_votenextmap 0
|
||||||
|
mp_free_armor 0
|
||||||
|
mp_freezetime 17
|
||||||
|
mp_give_player_c4 1
|
||||||
|
mp_halftime_duration 15
|
||||||
|
mp_halftime_pausetimer 0
|
||||||
|
mp_ignore_round_win_conditions 0
|
||||||
|
mp_limitteams 0
|
||||||
|
mp_match_end_restart 1
|
||||||
|
mp_maxmoney 16000
|
||||||
|
mp_maxrounds 24
|
||||||
|
mp_overtime_enable 1
|
||||||
|
mp_overtime_halftime_pausetimer 0
|
||||||
|
mp_overtime_maxrounds 6
|
||||||
|
mp_overtime_startmoney 10000
|
||||||
|
mp_respawn_immunitytime 0
|
||||||
|
mp_respawn_on_death_ct 0
|
||||||
|
mp_respawn_on_death_t 0
|
||||||
|
mp_round_restart_delay 5
|
||||||
|
mp_roundtime 1.92
|
||||||
|
mp_roundtime_defuse 1.92
|
||||||
|
mp_roundtime_hostage 1.92
|
||||||
|
mp_solid_teammates 1
|
||||||
|
mp_starting_losses 1
|
||||||
|
mp_startmoney 800
|
||||||
|
mp_t_default_secondary "weapon_glock"
|
||||||
|
mp_timelimit 0
|
||||||
|
spec_freeze_deathanim_time 0
|
||||||
|
spec_freeze_panel_extended_time 0
|
||||||
|
spec_freeze_time 2
|
||||||
|
spec_freeze_time_lock 2
|
||||||
|
mp_weapons_allow_typecount 5
|
||||||
|
cash_team_bonus_shorthanded 0
|
||||||
|
cash_team_loser_bonus_shorthanded 0
|
||||||
|
sv_allow_votes 0
|
||||||
|
sv_auto_full_alltalk_during_warmup_half_end 0
|
||||||
|
sv_coaching_enabled 1
|
||||||
|
sv_competitive_official_5v5 1
|
||||||
|
sv_damage_print_enable 0
|
||||||
|
sv_deadtalk 1
|
||||||
|
sv_hibernate_postgame_delay 300
|
||||||
|
sv_holiday_mode 0
|
||||||
|
sv_talk_enemy_dead 0
|
||||||
|
sv_talk_enemy_living 0
|
||||||
|
sv_voiceenable 1
|
||||||
|
tv_relayvoice 1
|
||||||
|
mp_friendlyfire 1
|
||||||
|
sv_coach_comm_unrestricted 1
|
||||||
|
|
||||||
|
say Match is live!!
|
||||||
|
say Match is live!!
|
||||||
|
say Match is live!!
|
||||||
|
say Match is live!!
|
||||||
|
say Match is live!!
|
||||||
|
say Match is live!!
|
||||||
|
say Match is live!!
|
114
cfg/live_wingman.cfg
Normal file
114
cfg/live_wingman.cfg
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
ammo_grenade_limit_default 1
|
||||||
|
ammo_grenade_limit_flashbang 2
|
||||||
|
ammo_grenade_limit_total 4
|
||||||
|
bot_quota 0
|
||||||
|
cash_player_bomb_defused 300
|
||||||
|
cash_player_bomb_planted 300
|
||||||
|
cash_player_damage_hostage -30
|
||||||
|
cash_player_interact_with_hostage 300
|
||||||
|
cash_player_killed_enemy_default 300
|
||||||
|
cash_player_killed_enemy_factor 1
|
||||||
|
cash_player_killed_hostage -1000
|
||||||
|
cash_player_killed_teammate -300
|
||||||
|
cash_player_rescued_hostage 1000
|
||||||
|
cash_team_bonus_shorthanded 1000
|
||||||
|
cash_team_elimination_bomb_map 2750
|
||||||
|
cash_team_elimination_hostage_map_ct 2500
|
||||||
|
cash_team_elimination_hostage_map_t 2500
|
||||||
|
cash_team_hostage_alive 0
|
||||||
|
cash_team_hostage_interaction 600
|
||||||
|
cash_team_loser_bonus 2000
|
||||||
|
cash_team_loser_bonus_consecutive_rounds 300
|
||||||
|
cash_team_planted_bomb_but_defused 800
|
||||||
|
cash_team_rescued_hostage 600
|
||||||
|
cash_team_terrorist_win_bomb 3000
|
||||||
|
cash_team_win_by_defusing_bomb 3000
|
||||||
|
cash_team_win_by_hostage_rescue 2900
|
||||||
|
cash_team_win_by_time_running_out_bomb 2750
|
||||||
|
cash_team_win_by_time_running_out_hostage 2750
|
||||||
|
ff_damage_reduction_bullets 0.33
|
||||||
|
ff_damage_reduction_grenade 0.85
|
||||||
|
ff_damage_reduction_grenade_self 1
|
||||||
|
ff_damage_reduction_other 0.4
|
||||||
|
mp_afterroundmoney 0
|
||||||
|
mp_autokick 0
|
||||||
|
mp_autoteambalance 0
|
||||||
|
mp_backup_restore_load_autopause 1
|
||||||
|
mp_backup_round_auto 1
|
||||||
|
mp_buy_anywhere 0
|
||||||
|
mp_buy_during_immunity 0
|
||||||
|
mp_buytime 20
|
||||||
|
mp_c4timer 40
|
||||||
|
mp_ct_default_melee weapon_knife
|
||||||
|
mp_ct_default_primary ""
|
||||||
|
mp_ct_default_secondary weapon_hkp2000
|
||||||
|
mp_death_drop_defuser 1
|
||||||
|
mp_death_drop_grenade 2
|
||||||
|
mp_death_drop_gun 1
|
||||||
|
mp_defuser_allocation 0
|
||||||
|
mp_display_kill_assists 1
|
||||||
|
mp_endmatch_votenextmap 0
|
||||||
|
mp_forcecamera 1
|
||||||
|
mp_free_armor 0
|
||||||
|
mp_freezetime 10
|
||||||
|
mp_friendlyfire 1
|
||||||
|
mp_give_player_c4 1
|
||||||
|
mp_halftime 1
|
||||||
|
mp_halftime_duration 15
|
||||||
|
mp_halftime_pausetimer 0
|
||||||
|
mp_ignore_round_win_conditions 0
|
||||||
|
mp_limitteams 0
|
||||||
|
mp_match_can_clinch 1
|
||||||
|
mp_match_end_restart 1
|
||||||
|
mp_maxmoney 8000
|
||||||
|
mp_maxrounds 16
|
||||||
|
mp_molotovusedelay 0
|
||||||
|
mp_overtime_enable 1
|
||||||
|
mp_overtime_halftime_pausetimer 0
|
||||||
|
mp_overtime_maxrounds 4
|
||||||
|
mp_overtime_startmoney 8000
|
||||||
|
mp_playercashawards 1
|
||||||
|
mp_randomspawn 0
|
||||||
|
mp_respawn_immunitytime 0
|
||||||
|
mp_respawn_on_death_ct 0
|
||||||
|
mp_respawn_on_death_t 0
|
||||||
|
mp_round_restart_delay 7
|
||||||
|
mp_roundtime 1.5
|
||||||
|
mp_roundtime_defuse 1.5
|
||||||
|
mp_roundtime_hostage 1.5
|
||||||
|
mp_solid_teammates 1
|
||||||
|
mp_starting_losses 1
|
||||||
|
mp_startmoney 800
|
||||||
|
mp_t_default_melee weapon_knife
|
||||||
|
mp_t_default_primary ""
|
||||||
|
mp_t_default_secondary weapon_glock
|
||||||
|
mp_teamcashawards 1
|
||||||
|
mp_timelimit 0
|
||||||
|
mp_weapons_allow_map_placed 1
|
||||||
|
mp_weapons_allow_zeus 1
|
||||||
|
mp_weapons_glow_on_ground 0
|
||||||
|
mp_win_panel_display_time 3
|
||||||
|
occlusion_test_async 0
|
||||||
|
spec_freeze_deathanim_time 0
|
||||||
|
spec_freeze_panel_extended_time 0
|
||||||
|
spec_freeze_time 2
|
||||||
|
spec_freeze_time_lock 2
|
||||||
|
spec_replay_enable 0
|
||||||
|
sv_allow_votes 0
|
||||||
|
sv_auto_full_alltalk_during_warmup_half_end 0
|
||||||
|
sv_coaching_enabled 1
|
||||||
|
sv_competitive_official_5v5 1
|
||||||
|
sv_damage_print_enable 0
|
||||||
|
sv_deadtalk 1
|
||||||
|
sv_hibernate_postgame_delay 300
|
||||||
|
sv_holiday_mode 0
|
||||||
|
sv_ignoregrenaderadio 0
|
||||||
|
sv_infinite_ammo 0
|
||||||
|
sv_occlude_players 1
|
||||||
|
sv_talk_enemy_dead 0
|
||||||
|
sv_talk_enemy_living 0
|
||||||
|
sv_voiceenable 1
|
||||||
|
tv_relayvoice 0
|
||||||
|
say Match is live!
|
||||||
|
say Match is live!
|
||||||
|
say Match is live!
|
46
cfg/warmup.cfg
Normal file
46
cfg/warmup.cfg
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
bot_kick
|
||||||
|
bot_quota 0
|
||||||
|
mp_autokick 0
|
||||||
|
mp_autoteambalance 0
|
||||||
|
mp_buy_anywhere 0
|
||||||
|
mp_buytime 15
|
||||||
|
mp_death_drop_gun 0
|
||||||
|
mp_free_armor 0
|
||||||
|
mp_ignore_round_win_conditions 0
|
||||||
|
mp_limitteams 0
|
||||||
|
mp_radar_showall 0
|
||||||
|
mp_respawn_on_death_ct 0
|
||||||
|
mp_respawn_on_death_t 0
|
||||||
|
mp_solid_teammates 0
|
||||||
|
mp_spectators_max 20
|
||||||
|
mp_maxmoney 16000
|
||||||
|
mp_startmoney 16000
|
||||||
|
mp_timelimit 0
|
||||||
|
sv_alltalk 1
|
||||||
|
sv_auto_full_alltalk_during_warmup_half_end 0
|
||||||
|
sv_coaching_enabled 1
|
||||||
|
sv_competitive_official_5v5 1
|
||||||
|
sv_deadtalk 1
|
||||||
|
sv_full_alltalk 1
|
||||||
|
sv_grenade_trajectory 0
|
||||||
|
sv_hibernate_when_empty 0
|
||||||
|
mp_weapons_allow_typecount -1
|
||||||
|
sv_infinite_ammo 0
|
||||||
|
sv_showimpacts 0
|
||||||
|
sv_voiceenable 1
|
||||||
|
sm_cvar sv_mute_players_with_social_penalties 0
|
||||||
|
sv_mute_players_with_social_penalties 0
|
||||||
|
tv_relayvoice 1
|
||||||
|
sv_cheats 0
|
||||||
|
mp_ct_default_melee weapon_knife
|
||||||
|
mp_ct_default_secondary weapon_hkp2000
|
||||||
|
mp_ct_default_primary ""
|
||||||
|
mp_t_default_melee weapon_knife
|
||||||
|
mp_t_default_secondary weapon_glock
|
||||||
|
mp_t_default_primary
|
||||||
|
mp_warmup_start
|
||||||
|
mp_warmup_pausetimer 1
|
||||||
|
mp_warmuptime 9999
|
||||||
|
say Warmup!
|
||||||
|
say Warmup!
|
||||||
|
say Warmup!
|
47
db.js
Normal file
47
db.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const Database = require('better-sqlite3');
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
|
||||||
|
const better_sqlite_client = new Database('cspanel.db');
|
||||||
|
|
||||||
|
better_sqlite_client.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS servers (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
serverIP TEXT NOT NULL,
|
||||||
|
serverPort INTEGER NOT NULL,
|
||||||
|
rconPassword TEXT NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
better_sqlite_client.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
password TEXT NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
const default_username = 'cspanel';
|
||||||
|
const default_password = 'v67ic55x4ghvjfj';
|
||||||
|
// Hash the default password
|
||||||
|
const hashed_password = bcrypt.hashSync(default_password, 10);
|
||||||
|
|
||||||
|
// Check if the default user already exists
|
||||||
|
const existing_user = better_sqlite_client.prepare(`
|
||||||
|
SELECT * FROM users WHERE username = ?
|
||||||
|
`).get(default_username);
|
||||||
|
|
||||||
|
if (existing_user) {
|
||||||
|
console.log('Default user already exists');
|
||||||
|
} else {
|
||||||
|
// Insert the default user into the users table
|
||||||
|
const insert_query = better_sqlite_client.prepare(`
|
||||||
|
INSERT INTO users (username, password)
|
||||||
|
VALUES (?, ?)
|
||||||
|
`);
|
||||||
|
|
||||||
|
insert_query.run(default_username, hashed_password);
|
||||||
|
console.log('Default user created successfully.');
|
||||||
|
}
|
||||||
|
module.exports = {
|
||||||
|
better_sqlite_client
|
||||||
|
};
|
14
modules/middleware.js
Normal file
14
modules/middleware.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
function is_authenticated(req, res, next) {
|
||||||
|
if (req.session.user) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
const accept_header = req.headers['accept'];
|
||||||
|
if (accept_header && accept_header.includes('text/html')) {
|
||||||
|
res.redirect('/');
|
||||||
|
} else {
|
||||||
|
res.status(401).json({ status: 401, message: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = is_authenticated;
|
90
modules/rcon.js
Normal file
90
modules/rcon.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
const Rcon = require('rcon-srcds').default;
|
||||||
|
const { better_sqlite_client } = require('../db');
|
||||||
|
|
||||||
|
|
||||||
|
class RconManager {
|
||||||
|
constructor() {
|
||||||
|
this.rcons = {};
|
||||||
|
this.details = {};
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
const servers_query = better_sqlite_client.prepare(`
|
||||||
|
SELECT * FROM servers
|
||||||
|
`);
|
||||||
|
const servers = servers_query.all();
|
||||||
|
console.log('All servers in DB:', servers);
|
||||||
|
for (const server of servers) {
|
||||||
|
const server_id = server.id.toString();
|
||||||
|
if (server_id in this.rcons) continue;
|
||||||
|
await this.connect(server_id, server)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error connecting to MongoDB:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(server_id, server) {
|
||||||
|
let rcon_connection = null;
|
||||||
|
rcon_connection = new Rcon({ host: server.serverIP, port: server.serverPort, timeout: 5000 });
|
||||||
|
console.log("CONNECTING RCON", server_id, server.serverIP, server.serverPort);
|
||||||
|
|
||||||
|
// Set a timeout for the authentication process
|
||||||
|
const authenticationTimeout = setTimeout(async () => {
|
||||||
|
console.error('RCON Authentication timed out', server_id);
|
||||||
|
try {
|
||||||
|
await this.disconnect_rcon(server_id); // Disconnect the RCON connection
|
||||||
|
console.log('Timed out, disconnected RCON', server_id);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error disconnecting RCON', server_id, error);
|
||||||
|
}
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await rcon_connection.authenticate(server.rconPassword);
|
||||||
|
clearTimeout(authenticationTimeout);
|
||||||
|
console.log('RCON Authenticated', server_id, server.serverIP, server.serverPort);
|
||||||
|
} catch (error) {
|
||||||
|
clearTimeout(authenticationTimeout);
|
||||||
|
console.error('RCON Authentication failed', server_id, error);
|
||||||
|
// Handle the authentication error here as needed.
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rcons[server_id] = rcon_connection;
|
||||||
|
this.details[server_id] = {
|
||||||
|
host: server.serverIP,
|
||||||
|
port: server.serverPort,
|
||||||
|
rcon_password: server.rconPassword,
|
||||||
|
connected: rcon_connection.isConnected(),
|
||||||
|
authenticated: rcon_connection.isAuthenticated()
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async disconnect_rcon(server_id) {
|
||||||
|
console.log('starting disconnect', server_id)
|
||||||
|
if (!this.rcons[server_id].connected) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rcons[server_id].authenticated = false;
|
||||||
|
this.rcons[server_id].connected = false;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.rcons[server_id].connection.once('close', () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rcons[server_id].connection.once('error', (e) => {
|
||||||
|
console.error('Socket error during disconnect:', e);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rcons[server_id].connection.end(); // Close the socket gracefully
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new RconManager();
|
2409
package-lock.json
generated
Normal file
2409
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
package.json
Normal file
24
package.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "cs2panel",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "RCON Web Panel to control CS2 Servers.",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "nodemon app.js",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
|
"better-sqlite3": "^8.6.0",
|
||||||
|
"ejs": "^3.1.9",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"express-session": "^1.17.3",
|
||||||
|
"mongodb": "^6.1.0",
|
||||||
|
"rcon-srcds": "^2.0.2",
|
||||||
|
"sqlite3": "^5.1.6",
|
||||||
|
"srcds-rcon": "^2.2.1"
|
||||||
|
}
|
||||||
|
}
|
26
public/css/navbar.css
Normal file
26
public/css/navbar.css
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
.custom-navbar {
|
||||||
|
background: linear-gradient(to bottom right, #3494E6, #EC6EAD, #3494E6);
|
||||||
|
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-navbar .navbar-brand {
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-navbar .navbar-toggler-icon {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-navbar .navbar-nav .nav-link {
|
||||||
|
color: #fff;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-navbar .navbar-nav .nav-link:hover {
|
||||||
|
color: #f39c12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
188
public/js/console.js
Normal file
188
public/js/console.js
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
$(document).ready(function () {
|
||||||
|
const current_path = window.location.pathname;
|
||||||
|
|
||||||
|
function fetch_servers() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/servers',
|
||||||
|
type: 'GET',
|
||||||
|
success: function (data) {
|
||||||
|
$('#serverList').empty();
|
||||||
|
|
||||||
|
data.servers.forEach(function (server) {
|
||||||
|
const card = `
|
||||||
|
<div class="card server-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">${server.hostname} (${server.serverIP}:${server.serverPort})</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
RCON Password: <input type="password" id="rconPassword" class="rcon-password-${server.id}" value="${server.rconPassword}" class="password-mask" disabled>
|
||||||
|
<button class="btn btn-sm btn-secondary toggle-password" server-id="${server.id}" class="hide-unhide-rcon">
|
||||||
|
<i class="fa fa-eye" server-id="${server.id}" id="toggleEyeIcon-${server.id}"></i>
|
||||||
|
</button>
|
||||||
|
<p class="status connected-status ${server.connected ? 'connected' : 'disconnected'}">
|
||||||
|
RCON Connected: ${server.connected ? 'Yes' : 'No'}
|
||||||
|
</p>
|
||||||
|
<p class="status authenticated-status ${server.authenticated ? 'authenticated' : 'not-authenticated'}">
|
||||||
|
RCON Authenticated: ${server.authenticated ? 'Yes' : 'No'}
|
||||||
|
</p>
|
||||||
|
${(!server.connected || !server.authenticated) ? '<button class="btn btn-success" server-id="' + server.id + '" id="reconnect_server">Reconnect</button>' : ''}
|
||||||
|
<a href="/manage/${server.id}" class="btn btn-primary">Manage</a>
|
||||||
|
<button class="btn btn-danger" server-id='${server.id}' id="delete_server">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
$('#serverList').append(card);
|
||||||
|
});
|
||||||
|
$(".toggle-password").click((event) => {
|
||||||
|
let server_id = $(event.target).attr("server-id");
|
||||||
|
toggle_password_visibility(server_id)
|
||||||
|
});
|
||||||
|
$("#reconnect_server").click(async (element) => {
|
||||||
|
try {
|
||||||
|
const server_id = $(element.target).attr("server-id");
|
||||||
|
const response = await $.ajax({
|
||||||
|
url: '/api/reconnect-server',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify({ server_id: server_id }),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (response.status === 200) {
|
||||||
|
fetch_servers();
|
||||||
|
} else {
|
||||||
|
console.error('Server responded with a non-200 status code:', response.status);
|
||||||
|
alert('An error occurred while reconnecting to the server.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
alert('An error occurred while reconnecting to the server.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#delete_server").click(async (element) => {
|
||||||
|
const confirmed = confirm("Are you sure you want to delete this server?");
|
||||||
|
if (confirmed) {
|
||||||
|
window.server_id = $(element.target).attr("server-id");
|
||||||
|
await send_post_request("/api/delete-server");
|
||||||
|
fetch_servers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function (error) {
|
||||||
|
console.error(error);
|
||||||
|
alert('An error occurred while fetching servers.');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (current_path == "/servers") {
|
||||||
|
fetch_servers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle_password_visibility(server_id) {
|
||||||
|
const password_field = document.getElementsByClassName("rcon-password-" + server_id)[0]
|
||||||
|
const eye_icon = document.getElementById(`toggleEyeIcon-${server_id}`);
|
||||||
|
|
||||||
|
if (password_field.type === 'password') {
|
||||||
|
password_field.type = 'text';
|
||||||
|
eye_icon.classList.remove('fa-eye');
|
||||||
|
eye_icon.classList.add('fa-eye-slash');
|
||||||
|
} else {
|
||||||
|
password_field.type = 'password';
|
||||||
|
eye_icon.classList.remove('fa-eye-slash');
|
||||||
|
eye_icon.classList.add('fa-eye');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function send_post_request(apiEndpoint, data = {}) {
|
||||||
|
try {
|
||||||
|
data.server_id = window.server_id
|
||||||
|
const response = await fetch(apiEndpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data.message)
|
||||||
|
alert(data.message);
|
||||||
|
} else {
|
||||||
|
alert('Failed to perform the action');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('An error occurred');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#pause_game').on('click', function () {
|
||||||
|
send_post_request('/api/pause');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#unpause_game').on('click', function () {
|
||||||
|
send_post_request('/api/unpause');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#restart_game').on('click', function () {
|
||||||
|
send_post_request('/api/restart');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#start_warmup').on('click', function () {
|
||||||
|
send_post_request('/api/start-warmup');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#knife_start').on('click', function () {
|
||||||
|
send_post_request('/api/start-knife');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#swap_team').on('click', function () {
|
||||||
|
send_post_request('/api/swap-team');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#go_live').on('click', function () {
|
||||||
|
send_post_request('/api/go-live');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#rconInputBtn').on('click', function () {
|
||||||
|
let data = {
|
||||||
|
command: $('#rconInput').val()
|
||||||
|
};
|
||||||
|
send_post_request('/api/rcon', data);
|
||||||
|
$('#rconInput').val('');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#list_backups').on('click', function () {
|
||||||
|
send_post_request('/api/list-backups');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#restore_latest_backup').on('click', function () {
|
||||||
|
send_post_request('/api/restore-latest-backup');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#restore_backup').on('click', function () {
|
||||||
|
const round_number = prompt('Enter round number to restore:');
|
||||||
|
if (round_number !== null && round_number.trim() !== '') {
|
||||||
|
const round_number_value = parseInt(round_number);
|
||||||
|
if (!isNaN(round_number_value)) {
|
||||||
|
send_post_request('/api/restore-round', { round_number: round_number_value });
|
||||||
|
} else {
|
||||||
|
alert('Invalid round number. Please enter a valid number.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert('Round number cannot be empty. Please enter a valid number.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#server_setup_form').on('submit', async function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const data = {
|
||||||
|
team1: $('#team1').val(),
|
||||||
|
team2: $('#team2').val(),
|
||||||
|
selectedMap: $('#selectedMap').val(),
|
||||||
|
game_mode: $('#game_mode').val(),
|
||||||
|
server_id: window.server_id
|
||||||
|
};
|
||||||
|
send_post_request('/api/setup-game', data);
|
||||||
|
});
|
||||||
|
});
|
38
routes/auth.js
Normal file
38
routes/auth.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
|
||||||
|
const { better_sqlite_client } = require('../db');
|
||||||
|
|
||||||
|
router.post('/auth/login', (req, res) => {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
const query = better_sqlite_client.prepare('SELECT * FROM users WHERE username = ?');
|
||||||
|
const user = query.get(username);
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
bcrypt.compare(password, user.password, (err, result) => {
|
||||||
|
if (result) {
|
||||||
|
req.session.user = user;
|
||||||
|
res.status(200).json({ status: 200, message: 'Login successful' });
|
||||||
|
} else {
|
||||||
|
res.status(200).json({ status: 401, message: 'Invalid credentials' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(401).json({ status: 401, message: 'Invalid credentials' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/auth/logout', (req, res) => {
|
||||||
|
req.session.destroy((err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).json({ status: 500, message: 'Logout failed' });
|
||||||
|
} else {
|
||||||
|
res.redirect('/');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
259
routes/game.js
Normal file
259
routes/game.js
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const readline = require('readline');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const rcon = require("../modules/rcon");
|
||||||
|
const is_authenticated = require("../modules/middleware");
|
||||||
|
|
||||||
|
const ALLOWED_STEAM_IDS = ['76561198154367261']
|
||||||
|
|
||||||
|
router.post('/api/setup-game', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
const team1 = req.body.team1;
|
||||||
|
const team2 = req.body.team2;
|
||||||
|
const selected_map = req.body.selectedMap;
|
||||||
|
const game_mode = req.body.game_mode.toString();
|
||||||
|
rcon.rcons[server_id].execute(`mp_teamname_1 "${team1}"`);
|
||||||
|
rcon.rcons[server_id].execute(`mp_teamname_2 "${team2}"`);
|
||||||
|
rcon.rcons[server_id].execute(`game_mode ${game_mode}`);
|
||||||
|
if (game_mode == "1") {
|
||||||
|
execute_cfg_on_server(server_id, './cfg/live.cfg');
|
||||||
|
} else if (game_mode == "2") {
|
||||||
|
execute_cfg_on_server(server_id, './cfg/live_wingman.cfg');
|
||||||
|
}
|
||||||
|
rcon.rcons[server_id].execute(`mp_warmup_pausetimer 1`);
|
||||||
|
rcon.rcons[server_id].execute(`map ${selected_map}`);
|
||||||
|
|
||||||
|
// Adding 1 second delay in executing warmup.cfg to make it effective after map has been changed.
|
||||||
|
setTimeout(() => {
|
||||||
|
execute_cfg_on_server(server_id, './cfg/warmup.cfg');
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
return res.status(200).json({ message: 'Game Created!' });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/api/restart', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
rcon.rcons[server_id].execute('mp_restartgame 1');
|
||||||
|
return res.status(200).json({ message: 'Game restarted' });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/api/start-warmup', is_authenticated, (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
rcon.rcons[server_id].execute('mp_restartgame 1');
|
||||||
|
execute_cfg_on_server(server_id, './cfg/warmup.cfg');
|
||||||
|
|
||||||
|
return res.status(200).json({ message: 'Warmup started!' });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/api/start-knife', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
rcon.rcons[server_id].execute('mp_warmup_end');
|
||||||
|
rcon.rcons[server_id].execute('mp_restartgame 1');
|
||||||
|
execute_cfg_on_server(server_id, './cfg/knife.cfg');
|
||||||
|
|
||||||
|
return res.status(200).json({ message: 'Knife started!' });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/api/swap-team', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
rcon.rcons[server_id].execute('mp_swapteams');
|
||||||
|
return res.status(200).json({ message: 'Teams Swapped!' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/api/go-live', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
rcon.rcons[server_id].execute('mp_warmup_end');
|
||||||
|
const response = await rcon.rcons[server_id].execute('game_mode');
|
||||||
|
const game_mode = response.split("=")[1].trim().toString();
|
||||||
|
if (game_mode == "1") {
|
||||||
|
console.log("Executing live.cfg")
|
||||||
|
execute_cfg_on_server(server_id, './cfg/live.cfg');
|
||||||
|
} else if (game_mode == "2") {
|
||||||
|
console.log("Executing live_wingman.cfg")
|
||||||
|
execute_cfg_on_server(server_id, './cfg/live_wingman.cfg');
|
||||||
|
}
|
||||||
|
rcon.rcons[server_id].execute('mp_restartgame 1');
|
||||||
|
|
||||||
|
return res.status(200).json({ message: 'Match is live!!' });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// List Round Backups API
|
||||||
|
router.post('/api/list-backups', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
const response = await rcon.rcons[server_id].execute('mp_backup_restore_list_files');
|
||||||
|
console.log('Server response:', response);
|
||||||
|
return res.status(200).json({ message: response });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Restore Round API
|
||||||
|
router.post('/api/restore-round', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
let round_number = req.body.round_number.toString()
|
||||||
|
if (round_number.length == 1) {
|
||||||
|
round_number = "0" + round_number;
|
||||||
|
}
|
||||||
|
console.log(`SENDING mp_backup_restore_load_file backup_round${round_number}.txt`)
|
||||||
|
rcon.rcons[server_id].execute(`mp_backup_restore_load_file backup_round${round_number}.txt`);
|
||||||
|
rcon.rcons[server_id].execute('mp_pause_match');
|
||||||
|
return res.status(200).json({ message: 'Round Restored!' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/api/restore-latest-backup', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
const response = await rcon.rcons[server_id].execute('mp_backup_round_file_last');
|
||||||
|
const last_round_file = response.split("=")[1].trim().toString();
|
||||||
|
if (last_round_file.includes('.txt')) {
|
||||||
|
rcon.rcons[server_id].execute(`mp_backup_restore_load_file ${last_round_file}`);
|
||||||
|
rcon.rcons[server_id].execute('mp_pause_match');
|
||||||
|
return res.status(200).json({ message: `Latest Round Restored! (${last_round_file})` });
|
||||||
|
} else {
|
||||||
|
return res.status(200).json({ message: 'No latest backup found!' });
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pause Game API
|
||||||
|
router.post('/api/pause', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
rcon.rcons[server_id].execute('mp_pause_match');
|
||||||
|
return res.status(200).json({ message: 'Game paused' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Unpause Game API
|
||||||
|
router.post('/api/unpause', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
rcon.rcons[server_id].execute('mp_unpause_match');
|
||||||
|
return res.status(200).json({ message: 'Game unpaused' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/api/rcon', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
const command = req.body.command;
|
||||||
|
|
||||||
|
// Wrap the await call in a Promise and add a timeout
|
||||||
|
const executePromise = new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const response = await Promise.race([
|
||||||
|
rcon.rcons[server_id].execute(command),
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({ error: 'Command execution timed out' });
|
||||||
|
}, 1000); // 1 seconds timeout
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
resolve(response);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for the wrapped Promise to resolve
|
||||||
|
const response = await executePromise;
|
||||||
|
console.log(response)
|
||||||
|
// Check if the result is an error or the actual response
|
||||||
|
if (response.error) {
|
||||||
|
return res.status(200).json({ message: 'Command sent!' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({ message: 'Command sent! Response received:\n' + response.toString() });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function check_whitelisted_players() {
|
||||||
|
rcon.rcons[server_id].execute('status_json')
|
||||||
|
.then((response) => {
|
||||||
|
console.log(response)
|
||||||
|
const server_status = JSON.parse(response)
|
||||||
|
const players = server_status['server']['clients']
|
||||||
|
for (var i = 0; i < players.length; i++) {
|
||||||
|
let player = players[i]
|
||||||
|
if (!player.bot && player.steamid64.includes('7656') && !ALLOWED_STEAM_IDS.includes(player.steamid64)) {
|
||||||
|
console.log(`kick ${player.name}`)
|
||||||
|
rcon.rcons[server_id].execute(`kick ${player.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function execute_cfg_on_server(server_id, cfg_path) {
|
||||||
|
const fileStream = fs.createReadStream(cfg_path, 'utf-8');
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: fileStream,
|
||||||
|
terminal: false
|
||||||
|
});
|
||||||
|
|
||||||
|
rl.on('line', (line) => {
|
||||||
|
try {
|
||||||
|
console.log(`Line from file: ${line}`);
|
||||||
|
rcon.rcons[server_id].execute(line);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Error in executing line ${line}, Error: ${error}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rl.on('close', () => {
|
||||||
|
console.log('File reading completed.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
router
|
||||||
|
};
|
137
routes/server.js
Normal file
137
routes/server.js
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// In your routes/server.js (create this file)
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const { better_sqlite_client } = require('../db');
|
||||||
|
|
||||||
|
const rcon = require("../modules/rcon");
|
||||||
|
const is_authenticated = require("../modules/middleware");
|
||||||
|
|
||||||
|
router.get('/add-server', is_authenticated, (req, res) => {
|
||||||
|
res.render('add-server');
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/servers', is_authenticated, (req, res) => {
|
||||||
|
res.render('servers');
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/manage/:server_id', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.params.server_id;
|
||||||
|
const servers_query = better_sqlite_client.prepare(`
|
||||||
|
SELECT * FROM servers WHERE id = ?
|
||||||
|
`);
|
||||||
|
const server = servers_query.get(server_id);
|
||||||
|
|
||||||
|
if (!server) {
|
||||||
|
return res.status(404).send('Server not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await rcon.rcons[server_id].execute('hostname');
|
||||||
|
const hostname = response.toString().split("=")[1].trim();
|
||||||
|
const host = rcon.details[server_id].host;
|
||||||
|
const port = rcon.details[server_id].port;
|
||||||
|
|
||||||
|
res.render('manage', { server_id, hostname, host, port });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return res.status(404).send('Internal Server Error!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/api/add-server', is_authenticated, async (req, res) => {
|
||||||
|
const { server_ip, server_port, rcon_password } = req.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const insert_query = better_sqlite_client.prepare(`
|
||||||
|
INSERT INTO servers (serverIP, serverPort, rconPassword) VALUES (?, ?, ?)
|
||||||
|
`);
|
||||||
|
const result = insert_query.run(server_ip, server_port, rcon_password);
|
||||||
|
|
||||||
|
if (result.changes > 0) {
|
||||||
|
res.status(201).json({ message: 'Server added successfully' });
|
||||||
|
rcon.init();
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ error: 'Failed to add the server' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/api/servers', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const servers_query = better_sqlite_client.prepare(`
|
||||||
|
SELECT * FROM servers
|
||||||
|
`);
|
||||||
|
const servers = servers_query.all();
|
||||||
|
for (var i = 0; i < servers.length; i++) {
|
||||||
|
const server = servers[i];
|
||||||
|
const server_id = server.id.toString();
|
||||||
|
let hostname = "-";
|
||||||
|
|
||||||
|
if (server_id in rcon.rcons) {
|
||||||
|
servers[i].connected = rcon.rcons[server_id].isConnected();
|
||||||
|
servers[i].authenticated = rcon.rcons[server_id].isAuthenticated();
|
||||||
|
|
||||||
|
if (servers[i].connected && servers[i].authenticated) {
|
||||||
|
const response = await rcon.rcons[server_id].execute('hostname');
|
||||||
|
hostname = response.toString().split("=")[1].trim();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
servers[i].connected = false;
|
||||||
|
servers[i].authenticated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
servers[i].hostname = hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ servers });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'An error occurred while fetching servers.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/api/reconnect-server', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
|
||||||
|
const server_query = better_sqlite_client.prepare(`
|
||||||
|
SELECT * FROM servers WHERE id = ?
|
||||||
|
`);
|
||||||
|
const server = server_query.get(server_id);
|
||||||
|
|
||||||
|
if (!server) {
|
||||||
|
return res.status(404).json({ error: 'Server not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconnect using RCON
|
||||||
|
await rcon.connect(server_id, server);
|
||||||
|
|
||||||
|
res.status(200).json({ status: 200 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'An error occurred while reconnecting to the server.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/api/delete-server', is_authenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const server_id = req.body.server_id;
|
||||||
|
|
||||||
|
const delete_server_query = better_sqlite_client.prepare('DELETE FROM servers WHERE id = ?');
|
||||||
|
const result = delete_server_query.run(server_id);
|
||||||
|
|
||||||
|
if (result.changes > 0) {
|
||||||
|
res.status(200).json({ status: 200, message: 'Server deleted successfully' });
|
||||||
|
} else {
|
||||||
|
res.status(404).json({ status: 404, message: 'Server not found' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'An error occurred while deleting the server.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
172
views/add-server.ejs
Normal file
172
views/add-server.ejs
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Add a Server</title>
|
||||||
|
<!-- Include Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<!-- Custom CSS -->
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(to bottom right, #3494E6, #EC6EAD);
|
||||||
|
color: #fff;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #007BFF;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: #6c757d;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background-color: #5a6268;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom styling for form elements */
|
||||||
|
.form-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive styling for small screens */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.form-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="/css/navbar.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include('partials/navbar') %>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 offset-md-3 form-container">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">Add a Server</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="serverIP" class="form-label">Server IP:</label>
|
||||||
|
<input type="text" id="serverIP" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="serverPort" class="form-label">Server Port:</label>
|
||||||
|
<input type="text" id="serverPort" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="rconPassword" class="form-label">RCON Password:</label>
|
||||||
|
<input type="password" id="rconPassword" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-container">
|
||||||
|
<button id="submitButton" class="btn btn-primary">Submit</button>
|
||||||
|
<button type="reset" class="btn btn-secondary">Reset</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Include Bootstrap JS and jQuery -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
// Event listener for the submit button
|
||||||
|
$('#submitButton').on('click', function (e) {
|
||||||
|
e.preventDefault(); // Prevent the default form submission
|
||||||
|
|
||||||
|
// Get input values
|
||||||
|
const serverIP = $('#serverIP').val();
|
||||||
|
const serverPort = $('#serverPort').val();
|
||||||
|
const rconPassword = $('#rconPassword').val();
|
||||||
|
|
||||||
|
if (!serverIP || !serverPort || !rconPassword) {
|
||||||
|
alert('All fields are required.');
|
||||||
|
return; // Do not proceed with the request
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create server data object
|
||||||
|
const serverData = {
|
||||||
|
serverIP,
|
||||||
|
serverPort,
|
||||||
|
rconPassword,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make an API POST request to add the server
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/add-server', // Adjust the API endpoint
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify(serverData),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function (data) {
|
||||||
|
// Display a success toast message
|
||||||
|
alert('Server added successfully!')
|
||||||
|
$('#successToast').toast('show');
|
||||||
|
$('#serverIP, #serverPort, #rconPassword').val('');
|
||||||
|
},
|
||||||
|
error: function (error) {
|
||||||
|
console.error(error);
|
||||||
|
alert('An error occurred while adding the server.');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
165
views/login.ejs
Normal file
165
views/login.ejs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Login</title>
|
||||||
|
<!-- Include Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<!-- Custom CSS -->
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(to bottom right, #3494E6, #EC6EAD);
|
||||||
|
color: #fff;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #007BFF;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: #6c757d;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background-color: #5a6268;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom styling for form elements */
|
||||||
|
.form-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive styling for small screens */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.form-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 offset-md-3 form-container">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">Login</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label">Username:</label>
|
||||||
|
<input type="text" id="username" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Password:</label>
|
||||||
|
<input type="password" id="password" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-container">
|
||||||
|
<button id="login_btn" class="btn btn-primary">Submit</button>
|
||||||
|
<button type="reset" class="btn btn-secondary">Reset</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Include Bootstrap JS and jQuery -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
// Event listener for the submit button
|
||||||
|
$('#login_btn').on('click', function (e) {
|
||||||
|
e.preventDefault(); // Prevent the default form submission
|
||||||
|
|
||||||
|
// Get input values
|
||||||
|
const username = $('#username').val();
|
||||||
|
const password = $('#password').val();
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
alert('All fields are required.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create server data object
|
||||||
|
const user_data = {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make an API POST request to add the server
|
||||||
|
$.ajax({
|
||||||
|
url: '/auth/login', // Adjust the API endpoint
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify(user_data),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function (data) {
|
||||||
|
if (data.status == 200) {
|
||||||
|
window.location = "/servers";
|
||||||
|
} else if (data.status == 401) {
|
||||||
|
alert('Invalid Credentials')
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
error: function (error) {
|
||||||
|
console.error(error);
|
||||||
|
alert('Login Failed');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
264
views/manage.ejs
Normal file
264
views/manage.ejs
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>CSGO/CS2 Server Control Panel</title>
|
||||||
|
<!-- Add Bootstrap CSS CDN link -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<!-- Custom CSS -->
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(to bottom right, #3494E6, #EC6EAD);
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
color: #333;
|
||||||
|
height: 100vh; /* Set body height to 100% of viewport height */
|
||||||
|
margin: 0; /* Remove margin to fill the entire viewport */
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.server-setup-form {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.9); /* Semi-transparent white background */
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"], select {
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 10px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom CSS for input and button side by side */
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group input[type="text"] {
|
||||||
|
width: 70%;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group button {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom CSS for action buttons */
|
||||||
|
.action-buttons button {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="/css/navbar.css">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include('partials/navbar') %>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h1 class="mb-4"><%= hostname %> (<%= host %>:<%= port %>)</h1>
|
||||||
|
<form id="server_setup_form" class="server-setup-form">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="team1" class="form-label">Team 1:</label>
|
||||||
|
<input type="text" id="team1" name="team1" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="team2" class="form-label">Team 2:</label>
|
||||||
|
<input type="text" id="team2" name="team2" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="map" class="form-label">Map:</label>
|
||||||
|
<select id="selectedMap" name="map" class="form-select">
|
||||||
|
<option value="de_inferno">de_inferno</option>
|
||||||
|
<option value="de_mirage">de_mirage</option>
|
||||||
|
<option value="de_dust2">de_dust2</option>
|
||||||
|
<option value="de_overpass">de_overpass</option>
|
||||||
|
<option value="de_ancient">de_ancient</option>
|
||||||
|
<option value="de_anubis">de_anubis</option>
|
||||||
|
<option value="de_nuke">de_nuke</option>
|
||||||
|
<option value="de_vertigo">de_vertigo</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="gamemode" class="form-label">Gamemode:</label>
|
||||||
|
<select id="game_mode" name="gamemode" class="form-select">
|
||||||
|
<option value="1">Competitive</option>
|
||||||
|
<option value="2">Wingman</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<button type="submit" name="action" value="pause" class="btn btn-primary">
|
||||||
|
<svg viewBox="0 0 1024 1024" width="20" height="20" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#ffffff"
|
||||||
|
stroke="#ffffff">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<path
|
||||||
|
d="M511.9 135.3L182.7 323.7v376.8l329.1 188.4L841 700.5V323.7L511.9 135.3z m256 522.8l-256 146.5-256-146.5v-292l256-146.5 256 146.5v292z"
|
||||||
|
fill="#ffffff"></path>
|
||||||
|
<path
|
||||||
|
d="M365.6 512.1c0 80.8 65.5 146.3 146.3 146.3 80.8 0 146.3-65.5 146.3-146.3 0-80.8-65.5-146.3-146.3-146.3-80.8 0-146.3 65.5-146.3 146.3z m219.4 0c0 40.3-32.8 73.1-73.1 73.1s-73.1-32.8-73.1-73.1 32.8-73.1 73.1-73.1 73.1 32.8 73.1 73.1z"
|
||||||
|
fill="#ffffff"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Setup Game</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="mb-3 action-buttons">
|
||||||
|
<button type="submit" id="pause_game" name="action" value="pause" class="btn btn-primary">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pause" viewBox="0 0 16 16">
|
||||||
|
<path d="M5 0a1 1 0 0 1 1 1v14a1 1 0 0 1-2 0V1a1 1 0 0 1 1-1z"/>
|
||||||
|
<path d="M10 0a1 1 0 0 1 1 1v14a1 1 0 0 1-2 0V1a1 1 0 0 1 1-1z"/>
|
||||||
|
</svg> Pause Game
|
||||||
|
</button>
|
||||||
|
<button type="submit" id="unpause_game" name="action" value="unpause" class="btn btn-success">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-play" viewBox="0 0 16 16">
|
||||||
|
<path d="M11.742 8.47 5.975 12.764a.5.5 0 0 1-.31.108H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h1.665a.5.5 0 0 1 .31.108l5.767 4.294a.5.5 0 0 1 0 .816z"/>
|
||||||
|
</svg> Unpause Game
|
||||||
|
</button>
|
||||||
|
<button type="submit" id="restart_game" name="action" value="restart" class="btn btn-danger">
|
||||||
|
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#ffffff">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<path
|
||||||
|
d="M12 2.99988C16.9706 2.99988 21 7.02931 21 11.9999C21 16.9704 16.9706 20.9999 12 20.9999C7.02944 20.9999 3 16.9704 3 11.9999C3 9.17261 4.30367 6.64983 6.34267 4.99988"
|
||||||
|
stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path>
|
||||||
|
<path d="M3 4.49988H7V8.49988" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Restart Game
|
||||||
|
</button>
|
||||||
|
<button type="submit" id="list_backups" name="action" class="btn btn-primary">
|
||||||
|
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#ffffff">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<path
|
||||||
|
d="M8 6.00067L21 6.00139M8 12.0007L21 12.0015M8 18.0007L21 18.0015M3.5 6H3.51M3.5 12H3.51M3.5 18H3.51M4 6C4 6.27614 3.77614 6.5 3.5 6.5C3.22386 6.5 3 6.27614 3 6C3 5.72386 3.22386 5.5 3.5 5.5C3.77614 5.5 4 5.72386 4 6ZM4 12C4 12.2761 3.77614 12.5 3.5 12.5C3.22386 12.5 3 12.2761 3 12C3 11.7239 3.22386 11.5 3.5 11.5C3.77614 11.5 4 11.7239 4 12ZM4 18C4 18.2761 3.77614 18.5 3.5 18.5C3.22386 18.5 3 18.2761 3 18C3 17.7239 3.22386 17.5 3.5 17.5C3.77614 17.5 4 17.7239 4 18Z"
|
||||||
|
stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||||
|
</g>
|
||||||
|
</svg> List Backups
|
||||||
|
</button>
|
||||||
|
<button id="restore_backup" class="btn btn-primary">
|
||||||
|
<svg fill="#ffffff" height="16" width="16" version="1.1" id="XMLID_107_" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve" stroke="#ffffff">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<g id="folder-cycle">
|
||||||
|
<path d="M24,24h-5v-2h3V10H2v12h2v2H0V0h9.5l3,4H24V24z M2,8h20V6H11.5l-3-4H2V8z"></path>
|
||||||
|
<path
|
||||||
|
d="M18,12v5h-5l1.8-1.8C14.1,14.5,13.1,14,12,14c-2.2,0-4,1.8-4,4H6c0-3.3,2.7-6,6-6c1.6,0,3.1,0.7,4.2,1.8L18,12z M16,18 c0,2.2-1.8,4-4,4c-1.1,0-2.1-0.5-2.8-1.2L11,19H6v5l1.8-1.8C8.9,23.3,10.4,24,12,24c3.3,0,6-2.7,6-6H16z">
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg> Restore Backup
|
||||||
|
</button>
|
||||||
|
<button id="restore_latest_backup" class="btn btn-primary">
|
||||||
|
<svg fill="#ffffff" height="16" width="16" version="1.1" id="XMLID_107_" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve" stroke="#ffffff">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<g id="folder-cycle">
|
||||||
|
<path d="M24,24h-5v-2h3V10H2v12h2v2H0V0h9.5l3,4H24V24z M2,8h20V6H11.5l-3-4H2V8z"></path>
|
||||||
|
<path
|
||||||
|
d="M18,12v5h-5l1.8-1.8C14.1,14.5,13.1,14,12,14c-2.2,0-4,1.8-4,4H6c0-3.3,2.7-6,6-6c1.6,0,3.1,0.7,4.2,1.8L18,12z M16,18 c0,2.2-1.8,4-4,4c-1.1,0-2.1-0.5-2.8-1.2L11,19H6v5l1.8-1.8C8.9,23.3,10.4,24,12,24c3.3,0,6-2.7,6-6H16z">
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg> Restore Latest Round
|
||||||
|
</button>
|
||||||
|
<button type="submit" id="start_warmup" name="action" class="btn btn-success">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-play-fill" viewBox="0 0 16 16">
|
||||||
|
<path d="M11.742 8.47 5.975 12.764a.5.5 0 0 1-.31.108H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h1.665a.5.5 0 0 1 .31.108l5.767 4.294a.5.5 0 0 1 0 .816z"/>
|
||||||
|
</svg> Start Warmup
|
||||||
|
</button>
|
||||||
|
<button type="submit" id="knife_start" name="action" class="btn btn-success">
|
||||||
|
<svg height="16" width="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#ffffff">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<path
|
||||||
|
d="M14.7245 11.2754L16 12.4999L10.0129 17.8218C8.05054 19.5661 5.60528 20.6743 3 20.9999L3.79443 19.5435C4.6198 18.0303 5.03249 17.2737 5.50651 16.5582C5.92771 15.9224 6.38492 15.3113 6.87592 14.7278C7.42848 14.071 8.0378 13.4615 9.25644 12.2426L12 9.49822M11.5 8.99787L17.4497 3.04989C18.0698 2.42996 19.0281 2.3017 19.7894 2.73674C20.9027 3.37291 21.1064 4.89355 20.1997 5.80024L19.8415 6.15847C19.6228 6.3771 19.3263 6.49992 19.0171 6.49992H18L16 8.49992V8.67444C16 9.16362 16 9.40821 15.9447 9.63839C15.8957 9.84246 15.8149 10.0375 15.7053 10.2165C15.5816 10.4183 15.4086 10.5913 15.0627 10.9372L14.2501 11.7498L11.5 8.99787Z"
|
||||||
|
stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||||
|
</g>
|
||||||
|
</svg> Start Knife Round
|
||||||
|
</button>
|
||||||
|
<button type="submit" id="swap_team" name="action" class="btn btn-success">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" width="16" height="16" xmlns="http://www.w3.org/2000/svg" stroke="#ffffff">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
|
d="M16 3.93a.75.75 0 0 1 1.177-.617l4.432 3.069a.75.75 0 0 1 0 1.233l-4.432 3.069A.75.75 0 0 1 16 10.067V8H4a1 1 0 0 1 0-2h12V3.93zm-9.177 9.383A.75.75 0 0 1 8 13.93V16h12a1 1 0 1 1 0 2H8v2.067a.75.75 0 0 1-1.177.617l-4.432-3.069a.75.75 0 0 1 0-1.233l4.432-3.069z"
|
||||||
|
fill="#ffffff"></path>
|
||||||
|
</g>
|
||||||
|
</svg> Swap Teams
|
||||||
|
</button>
|
||||||
|
<button type="submit" id="go_live" name="action" class="btn btn-success">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-play-fill" viewBox="0 0 16 16">
|
||||||
|
<path d="M11.742 8.47 5.975 12.764a.5.5 0 0 1-.31.108H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h1.665a.5.5 0 0 1 .31.108l5.767 4.294a.5.5 0 0 1 0 .816z"/>
|
||||||
|
</svg> Go Live
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" id="rconInput" name="sayInput" class="form-control" placeholder="Send RCON command to server" required style="flex: 2;">
|
||||||
|
<button type="submit" id="rconInputBtn" class="btn btn-primary" style="flex: 1;">
|
||||||
|
Send
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" width="16" height="16" xmlns="http://www.w3.org/2000/svg" stroke="#ffffff">
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<path
|
||||||
|
d="M11.5003 12H5.41872M5.24634 12.7972L4.24158 15.7986C3.69128 17.4424 3.41613 18.2643 3.61359 18.7704C3.78506 19.21 4.15335 19.5432 4.6078 19.6701C5.13111 19.8161 5.92151 19.4604 7.50231 18.7491L17.6367 14.1886C19.1797 13.4942 19.9512 13.1471 20.1896 12.6648C20.3968 12.2458 20.3968 11.7541 20.1896 11.3351C19.9512 10.8529 19.1797 10.5057 17.6367 9.81135L7.48483 5.24303C5.90879 4.53382 5.12078 4.17921 4.59799 4.32468C4.14397 4.45101 3.77572 4.78336 3.60365 5.22209C3.40551 5.72728 3.67772 6.54741 4.22215 8.18767L5.24829 11.2793C5.34179 11.561 5.38855 11.7019 5.407 11.8459C5.42338 11.9738 5.42321 12.1032 5.40651 12.231C5.38768 12.375 5.34057 12.5157 5.24634 12.7972Z"
|
||||||
|
stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Include Bootstrap JS and jQuery CDN links -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.min.js"></script>
|
||||||
|
<script src="/js/console.js"></script>
|
||||||
|
<script>
|
||||||
|
window.server_id = '<%= server_id %>';
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
23
views/partials/navbar.ejs
Normal file
23
views/partials/navbar.ejs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<nav class="navbar navbar-expand-lg custom-navbar">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="/servers">CS2Panel</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/servers">All Servers</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/add-server">Add Server</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="ml-auto">
|
||||||
|
<form action="/auth/logout" method="post">
|
||||||
|
<button type="submit" class="btn logout-btn text-white">Logout</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
121
views/servers.ejs
Normal file
121
views/servers.ejs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Server Management</title>
|
||||||
|
<!-- Include Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<!-- Custom CSS -->
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(to bottom right, #3494E6, #EC6EAD);
|
||||||
|
color: #fff;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-list-container {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1); /* Semi-transparent white background for better readability */
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px; /* Add rounded corners */
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); /* Add a subtle shadow */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive styling for small screens */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
/* Adjust padding for small screens */
|
||||||
|
.server-list-container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
background-color: #fff;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #007BFF;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color: #dc3545;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom styling for the server cards */
|
||||||
|
.server-card {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-card .btn {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connected {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disconnected {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.authenticated {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-authenticated {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive styling for small screens */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.server-card {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-card .btn {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="/css/navbar.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include('partials/navbar') %>
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="mb-4">Server Management</h2>
|
||||||
|
<div class="server-list-container" id="serverList"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.min.js"></script>
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" rel="stylesheet">
|
||||||
|
<script src="/js/console.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user