diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 90d4c10..31bd5cd 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -46,13 +46,11 @@ jobs:
key: '${{ runner.OS }}-build-${{ hashFiles(''**/composer.lock'') }}'
- name: Copy .env
run: php -r "file_exists('.env') || copy('.env.example', '.env');" # If .env exist, we use that, if otherwise, copy .env.example to .env and use that instead
+ - name: Create DB File
+ run: touch database/database.sqlite
- name: Install Dependencies
if: steps.vendor-cache.outputs.cache-hit != 'true'
run: composer install -q --no-ansi --no-interaction --no-dev --no-progress --prefer-dist
- - name: Generate key
- run: php artisan key:generate
- - name: Clear Config
- run: php artisan config:clear
- name: Create an Archive For Release
uses: montudor/action-zip@v0.1.0
with:
diff --git a/README.md b/README.md
index 179ba7a..6122778 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,47 @@
-# Palworld Admin Toll
+# Palworld Admin Tool
## Description
-
An Admin tool receives information via rcon and displays them.
-## Prerequisites
-
-The following packages need to be installed:
-
-1. php (8.1 or higher) with the following extensions
+## Overview
+1. [Installation Variations](#install-possibilities)
+2. [Installation via Release (1 click install)](#installation-via-release)
+3. [Full Installation](#full-installation)
+ 1. [Prerequisites full Installation](#prerequisites-developerfull-installation)
+ 2. [Install Steps](#installation-steps)
+3. [Updating](#updating)
+
+## Installation Variations
+You can install this tool in the following ways
+- Install via docker-compose
+ - Head over to https://github.com/Insax/palworld-admin-tool-docker and read the installation instructions
+- Install using the latest [release](https://github.com/Insax/palworld-admin-tool/releases/latest) for Windows Users.
+ - Go to [Installation via Release](#installation-via-release) and follow the steps (1 click install)
+- Install cloning the repository (Advanced/Developer Installation)
+ - Go to [Developer Setup](#developer-installation)
+
+
+## Installation via Release
+> :warning: **This way of installing does not support updating your installation and has limitations.**
+
+#### This way of installing provides a fully working instance for testing purposes, its not meant to be used in real production.
+
+#### If you like it go for the docker version or the full install.
+
+Steps
+1. Download the release as zip and extract it somewhere
+2. Run the script install-start.ps1 using powershell.
+3. Visit http://localhost
+
+> :warning: **Once again, this is more of a Test installation than anything else.**
+
+## Full Installation
+This will provide you with everything to update or develop the app yourself.
+
+### Prerequisites Developer/Full Installation
+This application has some requirements that must be fullfilled in order to for everything to work properly.
+
+1. PHP 8.1 with the following extensions enabled:**
- ctype
- curl
- dom
@@ -22,13 +55,14 @@ The following packages need to be installed:
- session
- tokenizer
- xml
-15. composer (https://getcomposer.org/)
-16. npm (20 or higher) https://nodejs.org/en/download
-17. Supervisord or an equivalent or http://supervisord.org/
-18. Nginx or an equivalent https://nginx.org/en/download.html
-19. Any Mysql or Postgres Database that supports column type `enum`
+ - sqlite
+1. composer (https://getcomposer.org/)
+2. npm (20 or higher) https://nodejs.org/en/download
+3. Supervisord or an equivalent http://supervisord.org/
+4. Nginx or an equivalent https://nginx.org/en/download.html
+5. [Optional] Any Mysql or Postgres Database, alternatively sqlite can be used.
-## Installation
+### Installation Steps
1. Clone the repository
@@ -50,24 +84,35 @@ The following packages need to be installed:
npm run build
```
4. Copy .env.example to .env and adjust the DB_HOST, DB_PORT, DB_USER, DB_DATABASE, DB_PASSWORD so it matches your setup
+
+ 1. If you would like to use SQLITE set `DB_CONNECTION` to `sqlite` and delete the `DB_DATABASE` line.
```bash
cp .env.example .env
```
-4. Generate an application key
+
+5. Create the database tables in your already created database.
+
+ ```bash
+ php artisan migrate --force
+ ```
+
+6. Generate an application key
```bash
php artisan key:generate
```
-4. Create a job in supervisor or an equivalent tool that auto restarts and runs
+
+7. Create a job in supervisor or an equivalent tool that auto restarts and runs
```bash
- php artisan short-schedule:run --lifetime=60
- ```
-5. Adjust the connections in config/rcon.php so they match you servers. Do not edit the default entry, it will not show up the application.
+ php artisan short-schedule:run
+ ```
+8. Configure your webserver, the content root is in `public`
+
-6. Configure your webserver, the content root is in `public`
-7. Visit the website, the installer should pop up.
+## Updating
+Rerun steps 2 - 5
## Running Tests
diff --git a/app/Console/Commands/SyncPlayersCommand.php b/app/Console/Commands/SyncPlayersCommand.php
index c20472f..93d982f 100644
--- a/app/Console/Commands/SyncPlayersCommand.php
+++ b/app/Console/Commands/SyncPlayersCommand.php
@@ -2,26 +2,25 @@
namespace App\Console\Commands;
-use App\Models\JoinAndLeave;
+use App\Gameserver\Communication\Responses\Response;
+use App\Gameserver\Communication\Responses\ShowPlayersResponse;
+use App\Models\JoinLeaveLog;
use App\Models\Player;
use App\Models\Server;
use App\Models\ServerWhitelist;
use Illuminate\Console\Command;
-use RCON;
+use Rcon;
class SyncPlayersCommand extends Command
{
+
/**
* The name and signature of the console command.
- *
- * @var string
*/
protected $signature = 'pal:sync';
/**
* The console command description.
- *
- * @var string
*/
protected $description = 'Synchronizes Players from a PalWorldServer';
@@ -30,58 +29,109 @@ class SyncPlayersCommand extends Command
*/
public function handle()
{
- foreach (Server::whereActive(true)->get() as $server)
- {
- $onlinePlayers = array();
- $result = RCON::getPlayers($server->rcon);
- foreach ($result as $player) {
- if($player['player_id'] == 00000000)
- continue;
-
- $onlinePlayers[] = $player['player_id'];
-
- $player['online'] = true;
- $player['server_id'] = $server->id;
- $players = Player::where(['player_id' => $player['player_id'], 'server_id' => $player['server_id']])->first();
-
- if(is_null($players))
- {
- $newPlayer = Player::create($player);
- JoinAndLeave::create(['player_id' => $newPlayer->id, 'action' => JoinAndLeave::$PLAYER_JOINED]);
- }
- else
- {
- if($players->online == false)
- {
- JoinAndLeave::create(['player_id' => $players->id, 'action' => JoinAndLeave::$PLAYER_JOINED]);
- }
- $players->update($player);
- }
- }
- $offlinePlayers = Player::where('server_id', $server->id)->whereNotIn('player_id', $onlinePlayers)->whereOnline(true)->get();
+ $servers = Server::whereActive(true)->with(['rconData', 'serverWhitelists', 'players'])->get();
- foreach ($offlinePlayers as $offlinePlayer)
- {
- $offlinePlayer->update(['online' => false]);
- JoinAndLeave::create(['player_id' => $offlinePlayer->id, 'action' => JoinAndLeave::$PLAYER_LEFT]);
+ foreach ($servers as $server) {
+ $response = Rcon::info($server);
+ if($response->getError() != 0) {
+ $this->handleUnreachableServer($server);
+ continue;
}
- if($server->uses_whitelist)
- {
- $whitelist = ServerWhitelist::where('server_id', $server->id)->get();
- $whitelistPlayers = array();
- foreach ($whitelist as $whitelistItem) {
- $whitelistPlayers[] = $whitelistItem->player_id;
- }
-
- $notWhitelistedPlayers = Player::where('server_id', $server->id)->whereNotIn('player_id', $whitelistPlayers)->get();
-
- foreach ($notWhitelistedPlayers as $notWhitelistedPlayer) {
- RCON::kickPlayer($server->rcon, $notWhitelistedPlayer->player_id);
- $notWhitelistedPlayer->update(['online' => false]);
- JoinAndLeave::create(['player_id' => $notWhitelistedPlayer->id, 'action' => JoinAndLeave::$PLAYER_KICKED_WHITELIST]);
- }
+ if(!$server->online)
+ $server->update(['online' => true]);
+
+ $this->syncServerPlayers($server);
+ }
+ }
+
+ private function handleUnreachableServer(Server $server)
+ {
+ $server->shutting_down = false;
+ $server->online = false;
+ $this->handleOfflinePlayers($server, []);
+ }
+
+ private function syncServerPlayers(Server $server)
+ {
+ $onlinePlayers = $this->getOnlinePlayers($server);
+
+
+ $this->handleOfflinePlayers($server, $onlinePlayers);
+
+ if ($server->uses_whitelist) {
+ $this->handleNotWhitelistedPlayers($server);
+ }
+ }
+
+ private function getOnlinePlayers(Server $server): array
+ {
+ $onlinePlayersIDs = [];
+ $result = Rcon::showPlayers($server);
+
+ foreach ($result->getResult() as $player) {
+ if ($player['player_id'] == '00000000') continue;
+
+ $onlinePlayersIDs[] = $player['player_id'];
+ $this->updatePlayerStatus($player, $server);
+ }
+
+ return $onlinePlayersIDs;
+ }
+
+ private function updatePlayerStatus(array $player, Server $server): void
+ {
+ $playerData = [
+ 'online' => true,
+ 'server_id' => $server->id,
+ 'player_id' => $player['player_id'],
+ 'steam_id' => $player['steam_id'],
+ 'name' => $player['name']
+ ];
+
+ $playerModel = Player::wherePlayerId($playerData['player_id'])->whereServerId($player['server_id'])->first();
+
+ if ($playerModel->exists){
+ if (!$playerModel->online) {
+ $this->logPlayerAction($playerModel, JoinLeaveLog::$PLAYER_JOINED);
}
+ $playerModel->update($playerData);
+ } else {
+ $playerModel = Player::create($playerData);
+ $this->logPlayerAction($playerModel, JoinLeaveLog::$PLAYER_JOINED);
+ }
+ }
+
+ private function handleOfflinePlayers(Server $server, array $onlinePlayers): void
+ {
+ $offlinePlayers = Player::where('server_id', $server->id)
+ ->whereNotIn('player_id', $onlinePlayers)
+ ->whereOnline(true)
+ ->get();
+
+ foreach ($offlinePlayers as $offlinePlayer) {
+ $offlinePlayer->update(['online' => false]);
+ $this->logPlayerAction($offlinePlayer, JoinLeaveLog::$PLAYER_LEFT);
+ }
+ }
+
+ private function handleNotWhitelistedPlayers(Server $server): void
+ {
+ $whitelistIDs = $server->serverWhitelists->pluck('player_id')->toArray();
+
+ $notWhitelistedPlayers = Player::whereServerId($server->id)
+ ->whereNotIn('player_id', $whitelistIDs)
+ ->get();
+
+ foreach ($notWhitelistedPlayers as $player) {
+ Rcon::kickPlayer($server, $player->player_id);
+ $player->update(['online' => false]);
+ $this->logPlayerAction($player, JoinLeaveLog::$PLAYER_KICKED_WHITELIST);
}
}
+
+ private function logPlayerAction(Player $player, string $action): void
+ {
+ JoinLeaveLog::create(['player_id' => $player->id, 'action' => $action]);
+ }
}
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index 7d455e9..8443ab8 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -18,11 +18,14 @@ protected function schedule(Schedule $schedule): void
/**
* Short Schedule to get the job time down to 5 seconds.
+ *
+ * @param \Spatie\ShortSchedule\ShortSchedule $shortSchedule
+ * @return void
*/
- protected function shortSchedule(\Spatie\ShortSchedule\ShortSchedule $shortSchedule)
+ protected function shortSchedule(\Spatie\ShortSchedule\ShortSchedule $shortSchedule): void
{
// this command will run every second
- $shortSchedule->command('pal:sync')->everySeconds(5);
+ $shortSchedule->command('pal:sync')->everySeconds(5)->withoutOverlapping();
}
/**
diff --git a/app/PalWorld/RCON/Facades/Facade.php b/app/Facades/Rcon.php
similarity index 69%
rename from app/PalWorld/RCON/Facades/Facade.php
rename to app/Facades/Rcon.php
index 212896a..c51ac66 100644
--- a/app/PalWorld/RCON/Facades/Facade.php
+++ b/app/Facades/Rcon.php
@@ -1,10 +1,10 @@
rconData->host, $server->rconData->port, \Crypt::decrypt($server->rconData->password), $server->rconData->timeout);
+ $response = $rcon->command('info');
+ return new InfoResponse($response);
+ }
+
+ public function showPlayers(Server $server) : Response
+ {
+ $rcon = new PalworldRcon($server->rconData->host, $server->rconData->port, \Crypt::decrypt($server->rconData->password), $server->rconData->timeout);
+ $response = $rcon->command('showPlayers');
+ return new ShowPlayersResponse($response);
+ }
+
+ public function kickPlayer(Server $server, string|int $playerId) : Response
+ {
+ $rcon = new PalworldRcon($server->rconData->host, $server->rconData->port, \Crypt::decrypt($server->rconData->password), $server->rconData->timeout);
+ $response = $rcon->command("kick $playerId");
+ return new KickPlayerResponse($response);
+ }
+
+ public function banPlayer(Server $server, string|int $playerId) : Response
+ {
+ $rcon = new PalworldRcon($server->rconData->host, $server->rconData->port, \Crypt::decrypt($server->rconData->password), $server->rconData->timeout);
+ $response = $rcon->command("ban $playerId");
+ return new BanPlayerResponse($response);
+ }
+
+ public function broadcast(Server $server, string $message) : Response
+ {
+ $message = str_replace($message, ' ', '\x1f');
+ $rcon = new PalworldRcon($server->rconData->host, $server->rconData->port, \Crypt::decrypt($server->rconData->password), $server->rconData->timeout);
+ $response = $rcon->command("broadcast $message");
+ return new BroadcastResponse($response);
+ }
+
+ public function save(Server $server)
+ {
+ $rcon = new PalworldRcon($server->rconData->host, $server->rconData->port, \Crypt::decrypt($server->rconData->password), $server->rconData->timeout);
+ $response = $rcon->command("save");
+ return new SaveResponse($response);
+ }
+}
diff --git a/app/Gameserver/Communication/Responses/BanPlayerResponse.php b/app/Gameserver/Communication/Responses/BanPlayerResponse.php
new file mode 100644
index 0000000..6beb0bb
--- /dev/null
+++ b/app/Gameserver/Communication/Responses/BanPlayerResponse.php
@@ -0,0 +1,32 @@
+ substr($this->getHeader(), 8)];
+ }
+
+ protected function isExpectedHeaderText(string $header): bool
+ {
+ return !strncmp('Banned: ', $header, 8);
+ }
+}
diff --git a/app/Gameserver/Communication/Responses/BroadcastResponse.php b/app/Gameserver/Communication/Responses/BroadcastResponse.php
new file mode 100644
index 0000000..0da1bfa
--- /dev/null
+++ b/app/Gameserver/Communication/Responses/BroadcastResponse.php
@@ -0,0 +1,32 @@
+ substr($this->getHeader(),14, null)];
+ }
+
+ protected function isExpectedHeaderText(string $header): bool
+ {
+ return !strncmp('Broadcasted: ', $header, 13);
+ }
+}
diff --git a/app/Gameserver/Communication/Responses/InfoResponse.php b/app/Gameserver/Communication/Responses/InfoResponse.php
new file mode 100644
index 0000000..ea1dbc3
--- /dev/null
+++ b/app/Gameserver/Communication/Responses/InfoResponse.php
@@ -0,0 +1,34 @@
+getHeader(), $matches);
+ return ['version' => $matches[1], 'serverName' => $matches[2]];
+ }
+
+ protected function isExpectedHeaderText(string $header) : bool
+ {
+ return !strncmp('Welcome to Pal Server', $header, 21);
+ }
+}
diff --git a/app/Gameserver/Communication/Responses/KickPlayerResponse.php b/app/Gameserver/Communication/Responses/KickPlayerResponse.php
new file mode 100644
index 0000000..d111bb7
--- /dev/null
+++ b/app/Gameserver/Communication/Responses/KickPlayerResponse.php
@@ -0,0 +1,32 @@
+ substr($this->getHeader(), 8)];
+ }
+
+ protected function isExpectedHeaderText(string $header): bool
+ {
+ return !strncmp('Kicked: ', $header, 8);
+ }
+}
diff --git a/app/Gameserver/Communication/Responses/Response.php b/app/Gameserver/Communication/Responses/Response.php
new file mode 100644
index 0000000..52c7318
--- /dev/null
+++ b/app/Gameserver/Communication/Responses/Response.php
@@ -0,0 +1,87 @@
+extractHeader();
+ if($this->isErrorResponse)
+ return;
+ $this->result = $this->extractBody($this->lines);
+ }
+
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ public function getResult() : array
+ {
+ return $this->result;
+ }
+
+ public function getHeader() : string
+ {
+ return $this->header;
+ }
+
+ protected abstract function extractBody(array $lines) : array;
+
+ private function extractHeader()
+ {
+ $this->lines = explode("\n", $this->responseText);
+ $this->header = array_shift($this->lines);
+ $this->checkHeader();
+ }
+
+ protected abstract function isExpectedHeaderText(string $header) : bool;
+
+ private function checkHeader()
+ {
+ if($this->isExpectedHeaderText($this->header))
+ return;
+
+ $this->isErrorResponse = true;
+
+ if($this->header == 'Authorization rejected')
+ {
+ $this->error = self::ERROR_AUTH;
+ \Log::alert($this->header);
+ }
+ elseif (strncmp('Could not connect to RCON', $this->header, 25))
+ {
+ $this->error = self::ERROR_CONNECT;
+ \Log::critical($this->header);
+ }
+ else
+ {
+ $this->error = self::ERROR_CMD;
+ \Log::info('Command Failed');
+ }
+ }
+}
diff --git a/app/Gameserver/Communication/Responses/SaveResponse.php b/app/Gameserver/Communication/Responses/SaveResponse.php
new file mode 100644
index 0000000..c106918
--- /dev/null
+++ b/app/Gameserver/Communication/Responses/SaveResponse.php
@@ -0,0 +1,32 @@
+ $playerData[0],
+ 'player_id' => $playerData[1],
+ 'steam_id' => $playerData[2]
+ ];
+ }
+ return $body;
+ }
+
+ protected function isExpectedHeaderText(string $header): bool
+ {
+ return $header == 'name,playeruid,steamid';
+ }
+}
diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php
deleted file mode 100644
index 746087e..0000000
--- a/app/Http/Controllers/DashboardController.php
+++ /dev/null
@@ -1,10 +0,0 @@
- 'required',
+ 'port' => 'required',
+ 'password' => 'required',
+ 'testSuccessful' => [
+ 'required',
+ Rule::in(true)
+ ]
+ ];
+ }
+
+ public function mount()
+ {
+
+ }
+
+ public function testConnection()
+ {
+ $rcon = new PalworldRcon($this->ip, $this->port, $this->password, 5);
+ $result = new InfoResponse($rcon->command('info'));
+ if ($result->getError() == 0) {
+ $this->testSuccessful = true;
+ Toaster::success("RCON Connection to ".$result->getResult()['serverName']." successful");
+ } else
+ Toaster::error($result->getHeader());
+
+ $this->rconResponse = $result->getHeader();
+ $this->quickServerName = $result->getResult()['serverName'];
+ }
+
+ public function addRcon()
+ {
+ $this->testConnection();
+ $this->validate();
+
+ $rcon = RconData::create(['host' => $this->ip, 'port' => $this->port, 'password' => \Crypt::encrypt($this->password)]);
+
+ if($this->quickServer) {
+ $server = Server::create(['name' => $this->quickServerName, 'rcon_data_id' => $rcon->id]);
+ return redirect()->route('server-dashboard', ['id' => $server->id]);
+ }
+
+ $this->reset(['ip', 'port', 'password', 'testSuccessful', 'quickServer', 'quickServerName', 'rconResponse']);
+ Toaster::success('Rcon Connection Created');
+ }
+
+ public function render()
+ {
+ return view('livewire.add-rcon')->layout('layouts.app');
+ }
+}
diff --git a/app/Livewire/AddServer.php b/app/Livewire/AddServer.php
index 93548e6..e237166 100644
--- a/app/Livewire/AddServer.php
+++ b/app/Livewire/AddServer.php
@@ -2,9 +2,11 @@
namespace App\Livewire;
+use App\Models\RconData;
use App\Models\Server;
use Illuminate\Validation\Rule;
use Livewire\Component;
+use Masmerise\Toaster\Toaster;
use Spatie\Permission\Models\Permission;
class AddServer extends Component
@@ -29,11 +31,9 @@ public function rules()
public function mount()
{
- foreach (config('rcon.connections') as $conn => $values)
+ foreach (RconData::get() as $connection)
{
- if($conn == 'default')
- continue;
- $this->availableRCON[] = $conn;
+ $this->availableRCON[] = ['value' => $connection->id, 'text' => $connection->host.':'.$connection->port];
}
}
@@ -49,6 +49,7 @@ public function addServer()
Permission::create(['name' => 'Restart Server ['.$server->id.']']);
Permission::create(['name' => 'Edit Whitelist Server ['.$server->id.']']);
+ Toaster::success('Server created');
return redirect()->route('server-dashboard', ['id' => $server->id]);
}
diff --git a/app/Livewire/EditRcon.php b/app/Livewire/EditRcon.php
new file mode 100644
index 0000000..5b9b967
--- /dev/null
+++ b/app/Livewire/EditRcon.php
@@ -0,0 +1,69 @@
+ 'required',
+ 'ip' => 'required',
+ 'password' => 'required',
+ 'testSuccessful' => [
+ 'required',
+ Rule::in(true)
+ ]
+ ];
+ }
+
+ public function testConnection()
+ {
+ $rcon = new PalworldRcon($this->ip, $this->port, $this->password, 5);
+ $result = new InfoResponse($rcon->command('info'));
+ if ($result->getError() == 0) {
+ $this->testSuccessful = true;
+ Toaster::success("RCON Connection to ".$result->getResult()['serverName']." successful");
+ } else
+ Toaster::error($result->getHeader());
+
+ $this->rconResponse = $result->getHeader();
+ $this->quickServerName = $result->getResult()['serverName'];
+ }
+
+ public function mount($id)
+ {
+ $this->rconData = RconData::find($id);
+ $this->port = $this->rconData->port;
+ $this->ip = $this->rconData->host;
+ }
+
+ public function editRcon()
+ {
+ $this->testConnection();
+ $this->validate();
+
+ $this->rconData->update(['host' => $this->ip, 'port' => $this->port, 'password' => $this->password]);
+ Toaster::success('RCON successfully edited');
+ return redirect()->route('rcon-overview');
+ }
+
+ public function render()
+ {
+ return view('livewire.edit-rcon')->layout('layouts.app');
+ }
+}
diff --git a/app/Livewire/EditServer.php b/app/Livewire/EditServer.php
index 208111b..2903075 100644
--- a/app/Livewire/EditServer.php
+++ b/app/Livewire/EditServer.php
@@ -2,6 +2,7 @@
namespace App\Livewire;
+use App\Models\RconData;
use App\Models\Server;
use Illuminate\Validation\Rule;
use Livewire\Component;
@@ -13,20 +14,18 @@ class EditServer extends Component
public array $availableRCON;
public string $name;
- public string $rcon;
+ public int $rcon;
public bool $uses_whitelist = false;
public function mount($id)
{
- foreach (config('rcon.connections') as $conn => $values)
+ foreach (RconData::get() as $connection)
{
- if($conn == 'default')
- continue;
- $this->availableRCON[] = $conn;
+ $this->availableRCON[] = ['value' => $connection->id, 'text' => $connection->host.':'.$connection->port];
}
$this->server = Server::find($id);
$this->name = $this->server->name;
- $this->rcon = $this->server->rcon;
+ $this->rcon = $this->server->rcon_data_id;
$this->uses_whitelist = $this->server->uses_whitelist;
}
diff --git a/app/Livewire/RconOverview.php b/app/Livewire/RconOverview.php
new file mode 100644
index 0000000..3e658cf
--- /dev/null
+++ b/app/Livewire/RconOverview.php
@@ -0,0 +1,45 @@
+connections = RconData::get();
+ }
+
+ public function deleteRcon(RconData $rconData)
+ {
+ $serverExists = false;
+ foreach ($rconData->servers as $server)
+ {
+ Toaster::error('Can\'t delete RCON, Server '. $server->name .' is still connected to it.');
+ $serverExists = true;
+ }
+
+ if(!$serverExists) {
+ $rconData->delete();
+ Toaster::success('RCON successfully deleted.');
+ }
+ }
+
+ public function toggleActiveServer(Server $server)
+ {
+ $server->active = !$server->active;
+ $server->save();
+ return redirect()->route('server-overview');
+ }
+
+ public function render()
+ {
+ return view('livewire.rcon-overview')->layout('layouts.app');
+ }
+}
diff --git a/app/Livewire/ServerDashboard.php b/app/Livewire/ServerDashboard.php
index d32aa38..55df0b7 100644
--- a/app/Livewire/ServerDashboard.php
+++ b/app/Livewire/ServerDashboard.php
@@ -2,11 +2,12 @@
namespace App\Livewire;
-use App\Models\JoinAndLeave;
+use App\Models\JoinLeaveLog;
use App\Models\Player;
use App\Models\Server;
-use RCON;
+use Masmerise\Toaster\Toaster;
use Livewire\Component;
+use Rcon;
class ServerDashboard extends Component
{
@@ -24,18 +25,21 @@ public function mount($id)
public function kickPlayer($player)
{
- $rcon = Server::find($this->id)->rcon;
- RCON::kickPlayer($rcon, $player['player_id']);
- JoinAndLeave::create(['player_id' => $player['id'], 'action' => JoinAndLeave::$PLAYER_KICKED_USER]);
- return redirect()->route('server-dashboard', ['id' => $this->id]);
+ $server = Server::find($this->id);
+ Rcon::kickPlayer($server, $player['player_id']);
+ JoinLeaveLog::create(['player_id' => $player['id'], 'action' => JoinLeaveLog::$PLAYER_KICKED_USER]);
+ Player::whereId($player['id'])->update(['online' => false]);
+ Rcon::broadcast($server, 'Kicked_Player: '.$player['name']);
+ Toaster::success('Player kicked');
}
public function banPlayer($player)
{
- $rcon = Server::find($this->id)->rcon;
- RCON::banPlayer($rcon, $player['player_id']);
- JoinAndLeave::create(['player_id' => $player['id'], 'action' => JoinAndLeave::$PLAYER_BAN_USR]);
- return redirect()->route('server-dashboard', ['id' => $this->id]);
+ $server = Server::find($this->id);
+ Rcon::banPlayer($server, $player['player_id']);
+ JoinLeaveLog::create(['player_id' => $player['id'], 'action' => JoinLeaveLog::$PLAYER_BAN_USR]);
+ Player::whereId($player['id'])->update(['online' => false]);
+ Toaster::success('Player banned');
}
public function buildPlayerList()
@@ -46,13 +50,14 @@ public function buildPlayerList()
public function buildJoinLeaveLog()
{
- $this->joinLeaveLog = JoinAndLeave::whereRelation('player', 'server_id', $this->id)->orderBy('created_at', 'desc')->with('player')->limit(200)->get();
+ $this->joinLeaveLog = JoinLeaveLog::whereRelation('player', 'server_id', $this->id)->orderBy('created_at', 'desc')->with('player')->limit(200)->get();
}
public function shutdownServer()
{
- $rcon = Server::find($this->id)->rcon;
- \RCON::shutdownServer($rcon);
+ $server = Server::find($this->id);
+ Rcon::shutdownServer($server);
+ Toaster::success('Shutdown Initialized');
}
public function render()
diff --git a/app/Livewire/ServerOverview.php b/app/Livewire/ServerOverview.php
index a76c28c..70713ba 100644
--- a/app/Livewire/ServerOverview.php
+++ b/app/Livewire/ServerOverview.php
@@ -4,6 +4,7 @@
use App\Models\Server;
use Livewire\Component;
+use Masmerise\Toaster\Toaster;
class ServerOverview extends Component
{
@@ -11,7 +12,15 @@ class ServerOverview extends Component
public function mount()
{
- $this->servers = Server::get();
+ $this->servers = Server::with('rconData')->get();
+ }
+
+ public function deleteServer(Server $server)
+ {
+ $server->serverWhitelists()->delete();
+ $server->players()->delete();
+ $server->delete();
+ Toaster::success('Server has been successfully deleted');
}
public function toggleActiveServer(Server $server)
diff --git a/app/Models/JoinAndLeave.php b/app/Models/JoinLeaveLog.php
similarity index 51%
rename from app/Models/JoinAndLeave.php
rename to app/Models/JoinLeaveLog.php
index 0db266f..d953a2a 100644
--- a/app/Models/JoinAndLeave.php
+++ b/app/Models/JoinLeaveLog.php
@@ -10,7 +10,7 @@
use Illuminate\Database\Eloquent\Model;
/**
- * Class JoinAndLeave
+ * Class JoinLeaveLog
*
* @property int $id
* @property int $player_id
@@ -19,19 +19,19 @@
* @property Carbon|null $updated_at
* @property Player $player
* @package App\Models
- * @method static \Illuminate\Database\Eloquent\Builder|JoinAndLeave newModelQuery()
- * @method static \Illuminate\Database\Eloquent\Builder|JoinAndLeave newQuery()
- * @method static \Illuminate\Database\Eloquent\Builder|JoinAndLeave query()
- * @method static \Illuminate\Database\Eloquent\Builder|JoinAndLeave whereAction($value)
- * @method static \Illuminate\Database\Eloquent\Builder|JoinAndLeave whereCreatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|JoinAndLeave whereId($value)
- * @method static \Illuminate\Database\Eloquent\Builder|JoinAndLeave wherePlayerId($value)
- * @method static \Illuminate\Database\Eloquent\Builder|JoinAndLeave whereUpdatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|JoinLeaveLog newModelQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder|JoinLeaveLog newQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder|JoinLeaveLog query()
+ * @method static \Illuminate\Database\Eloquent\Builder|JoinLeaveLog whereAction($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|JoinLeaveLog whereCreatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|JoinLeaveLog whereId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|JoinLeaveLog wherePlayerId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|JoinLeaveLog whereUpdatedAt($value)
* @mixin \Eloquent
*/
-class JoinAndLeave extends Model
+class JoinLeaveLog extends Model
{
- protected $table = 'join_and_leave';
+ protected $table = 'join_leave_log';
protected $casts = [
'player_id' => 'int'
diff --git a/app/Models/Player.php b/app/Models/Player.php
index 88692d6..f913094 100644
--- a/app/Models/Player.php
+++ b/app/Models/Player.php
@@ -22,9 +22,9 @@
* @property Carbon|null $updated_at
* @property int|null $server_id
* @property Server|null $server
- * @property Collection|JoinAndLeave[] $join_and_leaves
+ * @property Collection|JoinLeaveLog[] $joinLeaveLogs
* @package App\Models
- * @property-read int|null $join_and_leaves_count
+ * @property-read int|null $joinLeaveLogs_count
* @method static \Illuminate\Database\Eloquent\Builder|Player newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Player newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Player query()
@@ -60,8 +60,8 @@ public function server()
return $this->belongsTo(Server::class);
}
- public function join_and_leaves()
+ public function joinLeaveLogs()
{
- return $this->hasMany(JoinAndLeave::class);
+ return $this->hasMany(JoinLeaveLog::class);
}
}
diff --git a/app/Models/RconData.php b/app/Models/RconData.php
new file mode 100644
index 0000000..c9e9423
--- /dev/null
+++ b/app/Models/RconData.php
@@ -0,0 +1,62 @@
+ 'int',
+ 'timeout' => 'int'
+ ];
+
+ protected $hidden = [
+ 'password'
+ ];
+
+ protected $fillable = [
+ 'host',
+ 'port',
+ 'password',
+ 'timeout'
+ ];
+
+ public function servers()
+ {
+ return $this->hasMany(Server::class, 'rcon_data_id');
+ }
+}
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 9aba460..e977bec 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -15,18 +15,19 @@
*
* @property int $id
* @property string $name
- * @property string $rcon
* @property bool $online
* @property bool $active
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property bool $shutting_down
* @property bool $uses_whitelist
+ * @property int $rcon_data_id
+ * @property RconData $rconData
* @property Collection|Player[] $players
- * @property Collection|ServerWhitelist[] $server_whitelists
+ * @property Collection|ServerWhitelist[] $serverWhitelists
* @package App\Models
* @property-read int|null $players_count
- * @property-read int|null $server_whitelists_count
+ * @property-read int|null $serverWhitelists_count
* @method static \Illuminate\Database\Eloquent\Builder|Server newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Server newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Server query()
@@ -35,7 +36,7 @@
* @method static \Illuminate\Database\Eloquent\Builder|Server whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|Server whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|Server whereOnline($value)
- * @method static \Illuminate\Database\Eloquent\Builder|Server whereRcon($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|Server whereRconDataId($value)
* @method static \Illuminate\Database\Eloquent\Builder|Server whereShuttingDown($value)
* @method static \Illuminate\Database\Eloquent\Builder|Server whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Server whereUsesWhitelist($value)
@@ -49,24 +50,30 @@ class Server extends Model
'online' => 'bool',
'active' => 'bool',
'shutting_down' => 'bool',
- 'uses_whitelist' => 'bool'
+ 'uses_whitelist' => 'bool',
+ 'rcon_data_id' => 'int'
];
protected $fillable = [
'name',
- 'rcon',
'online',
'active',
'shutting_down',
- 'uses_whitelist'
+ 'uses_whitelist',
+ 'rcon_data_id'
];
+ public function rconData()
+ {
+ return $this->belongsTo(RconData::class, 'rcon_data_id');
+ }
+
public function players()
{
return $this->hasMany(Player::class);
}
- public function server_whitelists()
+ public function serverWhitelists()
{
return $this->hasMany(ServerWhitelist::class);
}
diff --git a/app/PalWorld/RCON/Connection.php b/app/PalWorld/RCON/Connection.php
deleted file mode 100644
index 605818f..0000000
--- a/app/PalWorld/RCON/Connection.php
+++ /dev/null
@@ -1,209 +0,0 @@
-host = $host;
- $this->port = $port;
- $this->timeout = $timeout;
- $this->rcon = $rcon;
- }
-
- /**
- * Connect to RCON server.
- *
- * @throws Exception
- * @return void
- */
- public function connect()
- {
- try {
- $this->socket = fsockopen($this->host, $this->port, $errno,
- $errstr, $this->timeout);
- } catch (\Exception $ex) {}
-
- if (! $this->isConnected()) {
- $servers = Server::where('rcon', $this->rcon)->get();
- foreach ($servers as $server) {
- $server->update(['shutting_down' => false]);
- $server->update(['online' => false]);
- }
- throw new Exception("Socket error: $errstr ($errno)");
- }
- }
-
- /**
- * Disconnect from RCON server if connection is established.
- *
- * @return void
- */
- public function disconnect()
- {
- if ($this->isConnected()) {
- fclose($this->socket);
- }
- }
-
- /**
- * Check if connection to RCON server is established.
- *
- * @return bool
- */
- public function isConnected()
- {
- return is_resource($this->socket);
- }
-
- /**
- * Check if connection is established before
- * sending data.
- *
- * @return void
- * @throws Exception
- */
- protected function checkConnection()
- {
- if (! $this->isConnected()) {
- $this->connect();
- }
- }
-
- /**
- * Authorize connection with given password.
- *
- * @param string $password
- * @return bool
- */
- public function authorize($password)
- {
- $this->checkConnection();
-
- $this->authorized = false;
-
- $response = $this->send(
- Packet::ID_AUTHORIZE, Packet::TYPE_SERVERDATA_AUTH, $password
- );
-
- if ($response->getId() == Packet::ID_AUTHORIZE &&
- $response->getType() == Packet::TYPE_SERVERDATA_AUTH_RESPONSE) {
- $this->authorized = true;
- }
-
- return $this->isAuthorized();
- }
-
- /**
- * Check if connection is authorized.
- *
- * @return bool
- */
- public function isAuthorized()
- {
- return $this->authorized;
- }
-
- /**
- * Execute given command on RCON server.
- *
- * @param string $command
- * @return string
- * @throws Exception
- */
- public function command($command)
- {
- $this->checkConnection();
-
- $response = $this->send(
- Packet::ID_COMMAND, Packet::TYPE_SERVERDATA_EXECCOMMAND, $command
- );
-
- if ($response->getId() == Packet::TYPE_SERVERDATA_RESPONSE_VALUE &&
- $response->getType() == Packet::TYPE_SERVERDATA_RESPONSE_VALUE) {
- return $response->getBody();
- }
-
- throw new Exception('Received invalid response');
- }
-
- /**
- * Send packet do RCON server and receive response.
- *
- * @param int $id
- * @param int $type
- * @param string $body
- * @return Packet
- */
- public function send($id, $type, $body)
- {
- $this->sendPacket($id, $type, $body);
-
- return $this->receivePacket();
- }
-
- /**
- * Generate packet binary structure used by RCON server
- * and send it through socket.
- *
- * @param int $id
- * @param int $type
- * @param string $body
- * @return void
- */
- public function sendPacket($id, $type, $body)
- {
- $this->checkConnection();
-
- $packet = Packet::fromFields(
- compact('id', 'type', 'body')
- );
-
- fwrite($this->socket, $packet->getBytes());
- }
-
- /**
- * Receive packet from RCON server and decode its
- * binary structure to data object.
- *
- * @return Packet
- */
- public function receivePacket()
- {
- $this->checkConnection();
-
- $bytes = fread($this->socket, 4);
- $size = unpack('V1size', $bytes);
-
- $bytes .= fread($this->socket, $size['size']);
-
- return Packet::fromBytes($bytes);
- }
-}
diff --git a/app/PalWorld/RCON/ConnectionInterface.php b/app/PalWorld/RCON/ConnectionInterface.php
deleted file mode 100644
index 9cbdf06..0000000
--- a/app/PalWorld/RCON/ConnectionInterface.php
+++ /dev/null
@@ -1,47 +0,0 @@
-connections = [];
- $this->defaultConnectionName = config('rcon.default', 'default');
- }
-
- /**
- * Make connection with given name in config.
- *
- * @param string $name
- * @return Connection
- */
- protected function makeConnection($name)
- {
- $config = $this->getConnectionConfig($name);
-
- if (is_null($config)) {
- throw new Exception("Connection $name does not exists in config");
- }
-
- $connection = new Connection($config['host'], $config['port'], $config['timeout'], $name);
-
- if (array_key_exists('password', $config) && isset($config['password'])) {
- $connection->authorize($config['password']);
- }
-
- return $this->connections[$name] = $connection;
- }
-
- /**
- * Return connection config by its name.
- *
- * @param string $name
- * @return array
- */
- public function getConnectionConfig($name)
- {
- return config('rcon.connections.' . $name);
- }
-
- /**
- * Return given connection by its name. If connection is not
- * established creates new connection.
- *
- * @param string $name
- * @return Connection
- */
- public function connection($name)
- {
- if (! array_key_exists($name, $this->connections)) {
- $this->makeConnection($name);
- }
-
- return $this->connections[$name];
- }
-
- /**
- * Return default connection set in config.
- *
- * @return Connection
- */
- public function defaultConnection()
- {
- return $this->connection($this->defaultConnectionName);
- }
-
- /**
- * Check if default connection to RCON server is established.
- *
- * @return bool
- */
- public function isConnected()
- {
- if (! array_key_exists($this->defaultConnectionName, $this->connections)) {
- return false;
- }
-
- return $this->defaultConnection()
- ->isConnected();
- }
-
- /**
- * Sends packet to default connection.
- *
- * @param int $id
- * @param int $type
- * @param string $body
- * @return Packet
- */
- public function send($id, $type, $body)
- {
- return $this->defaultConnection()
- ->send($id, $type, $body);
- }
-
- /**
- * Check if connection default is authorized.
- *
- * @return bool
- */
- public function isAuthorized()
- {
- if (! $this->isConnected()) {
- return false;
- }
-
- return $this->defaultConnection()
- ->isAuthorized();
- }
-
- /**
- * Authorize default connection with given password.
- *
- * @param string $password
- * @return bool
- */
- public function authorize($password)
- {
- return $this->defaultConnection()
- ->authorize($password);
- }
-
- /**
- * Execute given command on default RCON server.
- *
- * @param string $command
- * @return string
- * @throws Exception
- */
- public function command($command)
- {
- return $this->defaultConnection()
- ->command($command);
- }
-
- public function getPlayers($rcon)
- {
- try {
- $result = $this->connection($rcon)->command('showPlayers');
- //First split return text by lines, then by commas
- $lines = explode("\n", $result);
- $players = [];
- array_shift($lines);
- foreach ($lines as $line) {
- if(empty($line))
- break;
-
- $playerData = explode(",", $line);
- $players[] = [
- 'name' => $playerData[0],
- 'player_id' => $playerData[1],
- 'steam_id' => $playerData[2]
- ];
- }
- $servers = Server::where('rcon', $rcon)->get();
- foreach ($servers as $server) {
- $server->update(['online' => true]);
- }
- return $players;
- } catch (Exception $ex) {
- return [];
- }
- }
-
- public function shutdownServer($rcon)
- {
- $this->connection($rcon)->command('broadcast Restart_triggered_('.\Auth::user()->name.')');
- $this->connection($rcon)->command('save');
- $this->connection($rcon)->command('shutdown 20');
- $servers = Server::where('rcon', $rcon)->get();
- foreach ($servers as $server)
- {
- $server->update(['shutting_down' => true]);
- }
- }
-
- public function kickPlayer($rcon, $player_id)
- {
- $this->connection($rcon)->command('KickPlayer '.$player_id);
- }
-
- public function banPlayer($rcon, $player_id)
- {
- $this->connection($rcon)->command('BanPlayer '.$player_id);
- }
-}
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 452e6b6..969f1d3 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -2,6 +2,7 @@
namespace App\Providers;
+use App\Gameserver\Communication\Rcon;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@@ -19,6 +20,8 @@ public function register(): void
*/
public function boot(): void
{
- //
+ $this->app->bind('Rcon', function() {
+ return new Rcon();
+ });
}
}
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index e49d0b1..1972a82 100644
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -2,7 +2,7 @@
namespace App\Providers;
-use App\Listeners\NewUserListener;
+use App\Listeners\UserHasRegistered;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
@@ -18,7 +18,7 @@ class EventServiceProvider extends ServiceProvider
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
- NewUserListener::class,
+ UserHasRegistered::class,
],
];
diff --git a/app/Providers/RCONServiceProvider.php b/app/Providers/RCONServiceProvider.php
deleted file mode 100644
index b455a60..0000000
--- a/app/Providers/RCONServiceProvider.php
+++ /dev/null
@@ -1,50 +0,0 @@
-app->bind('RCON', function () {
- return new RCON();
- });
- }
-
- /**
- * Register bindings in the container.
- *
- * @return void
- */
- public function register()
- {
-
- }
-
- /**
- * Get the services provided by the provider.
- *
- * @return array
- */
- public function provides()
- {
- return [
- RCON::class
- ];
- }
-}
diff --git a/app/Support/RCON/PalworldRcon.php b/app/Support/RCON/PalworldRcon.php
new file mode 100644
index 0000000..ed2a069
--- /dev/null
+++ b/app/Support/RCON/PalworldRcon.php
@@ -0,0 +1,23 @@
+connection = new PalworldRconConnection($host, $port, $password, $timeout);
+ }
+
+ public function command(string $command) : string
+ {
+ try {
+ $this->connection->sendCommand($command);
+ return $this->connection->getResponse();
+ } catch (\Exception $exception) {
+ return $exception->getMessage();
+ }
+ }
+}
diff --git a/app/Support/RCON/PalworldRconConnection.php b/app/Support/RCON/PalworldRconConnection.php
new file mode 100644
index 0000000..7fe3f3c
--- /dev/null
+++ b/app/Support/RCON/PalworldRconConnection.php
@@ -0,0 +1,101 @@
+isConnected())
+ $this->connect();
+
+ if($this->password)
+ $this->authorize();
+
+ if(!$this->isAuthorized())
+ {
+ $this->disconnect();
+ throw new \Exception("Authorization rejected", 0);
+ }
+
+ $this->sendPacket(PalworldRconPacket::ID_COMMAND, PalworldRconPacket::TYPE_SERVERDATA_EXECCOMMAND, $command);
+ }
+
+ public function getResponse()
+ {
+ $response = $this->receivePacket()->getBody();
+ $this->disconnect();
+ return $response;
+ }
+
+ public function sendPacket(int $id, int $type, string $body)
+ {
+ $packet = PalworldRconPacket::fromFields(['id' => $id, 'type' => $type, 'body' => $body]);
+ fwrite($this->socket, $packet->getBytes());
+ }
+
+ public function receivePacket()
+ {
+ $bytes = fread($this->socket, 4);
+ $size = unpack('V1size', $bytes);
+
+ $bytes .= fread($this->socket, $size['size']);
+
+ return PalworldRconPacket::fromBytes($bytes);
+ }
+
+ public function authorize()
+ {
+ $this->sendPacket(PalworldRconPacket::ID_AUTHORIZE, PalworldRconPacket::TYPE_SERVERDATA_AUTH, $this->password);
+ $response = $this->receivePacket();
+ if ($response->getId() == PalworldRconPacket::ID_AUTHORIZE &&
+ $response->getType() == PalworldRconPacket::TYPE_SERVERDATA_AUTH_RESPONSE) {
+ $this->authorized = true;
+ }
+
+ return $this->isAuthorized();
+ }
+
+ public function isAuthorized()
+ {
+ return $this->authorized;
+ }
+
+ public function isConnected() : bool
+ {
+ return is_resource($this->socket);
+ }
+
+ public function disconnect() : void
+ {
+ if(is_resource($this->socket))
+ fclose($this->socket);
+ }
+ private function connect() : void
+ {
+ $this->socket = fsockopen($this->host, $this->port, $errorNumber, $errorMessage, $this->timeout);
+
+ if(!$this->socket)
+ throw new \Exception("Could not connect to RCON[$this->host]:[$this->port]: ".$errorMessage, $errorNumber);
+ }
+}
diff --git a/app/PalWorld/RCON/Packet.php b/app/Support/RCON/PalworldRconPacket.php
similarity index 88%
rename from app/PalWorld/RCON/Packet.php
rename to app/Support/RCON/PalworldRconPacket.php
index d7b36a3..7a675e8 100644
--- a/app/PalWorld/RCON/Packet.php
+++ b/app/Support/RCON/PalworldRconPacket.php
@@ -1,8 +1,8 @@
fillable as $field)
{
- if (! array_key_exists($field, $fields)) {
- throw new Exception("Invalid packet structure - missing $field field");
- }
-
- $this->{$field} = $fields[$field];
+ $this->fillable[$field] = $fields[$field];
}
$this->encode();
@@ -137,7 +133,7 @@ public function getSize()
*/
public function getId()
{
- return $this->id;
+ return $this->fillable['id'];
}
/**
@@ -147,7 +143,7 @@ public function getId()
*/
public function getType()
{
- return $this->type;
+ return $this->fillable['type'];
}
/**
@@ -157,7 +153,7 @@ public function getType()
*/
public function getBody()
{
- return $this->body;
+ return $this->fillable['body'];
}
/**
@@ -168,7 +164,7 @@ public function getBody()
*/
protected function encode()
{
- $bytes = pack("VVZ*", $this->id, $this->type, $this->body);
+ $bytes = pack("VVZ*", $this->fillable['id'], $this->fillable['type'], $this->fillable['body']);
$bytes .= "\x00";
$this->size = strlen($bytes);
diff --git a/composer.json b/composer.json
index 18084ee..65ac1fc 100644
--- a/composer.json
+++ b/composer.json
@@ -12,6 +12,7 @@
"laravel/tinker": "^2.8",
"livewire/livewire": "^3.0",
"livewire/volt": "^1.0",
+ "masmerise/livewire-toaster": "^2.1",
"rawilk/laravel-form-components": "^8.1",
"spatie/laravel-permission": "^6.3",
"spatie/laravel-short-schedule": "^1.5"
diff --git a/composer.lock b/composer.lock
index 9a523ee..cfc47a2 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "ea895cfcb3ed4fbcf65a76ffc616fa37",
+ "content-hash": "336d3849014b2374988d95fe4e672e9f",
"packages": [
{
"name": "brick/math",
@@ -2040,6 +2040,77 @@
},
"time": "2024-01-03T14:09:47+00:00"
},
+ {
+ "name": "masmerise/livewire-toaster",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/masmerise/livewire-toaster.git",
+ "reference": "35104bbb930d2638335e3685a307fb0ec348f5e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/masmerise/livewire-toaster/zipball/35104bbb930d2638335e3685a307fb0ec348f5e5",
+ "reference": "35104bbb930d2638335e3685a307fb0ec348f5e5",
+ "shasum": ""
+ },
+ "require": {
+ "laravel/framework": "^10.0",
+ "livewire/livewire": "^3.0",
+ "php": "~8.3"
+ },
+ "conflict": {
+ "stevebauman/unfinalize": "*"
+ },
+ "require-dev": {
+ "dive-be/php-crowbar": "^1.0",
+ "laravel/pint": "^1.0",
+ "nunomaduro/larastan": "^2.0",
+ "orchestra/testbench": "^8.0",
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "aliases": {
+ "Toaster": "Masmerise\\Toaster\\Toaster"
+ },
+ "providers": [
+ "Masmerise\\Toaster\\ToasterServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Masmerise\\Toaster\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Muhammed Sari",
+ "email": "support@muhammedsari.me",
+ "role": "Developer"
+ }
+ ],
+ "description": "Beautiful toast notifications for Laravel / Livewire.",
+ "homepage": "https://github.com/masmerise/livewire-toaster",
+ "keywords": [
+ "alert",
+ "laravel",
+ "livewire",
+ "toast",
+ "toaster"
+ ],
+ "support": {
+ "issues": "https://github.com/masmerise/livewire-toaster/issues",
+ "source": "https://github.com/masmerise/livewire-toaster/tree/2.1.0"
+ },
+ "time": "2023-12-13T12:33:45+00:00"
+ },
{
"name": "monolog/monolog",
"version": "3.5.0",
diff --git a/config/app.php b/config/app.php
index 57416dd..c8b7e0a 100644
--- a/config/app.php
+++ b/config/app.php
@@ -168,8 +168,7 @@
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
- App\Providers\VoltServiceProvider::class,
- App\Providers\RCONServiceProvider::class
+ App\Providers\VoltServiceProvider::class
])->toArray(),
/*
@@ -185,6 +184,6 @@
'aliases' => Facade::defaultAliases()->merge([
// 'Example' => App\Facades\Example::class,
- 'RCON' => App\PalWorld\RCON\Facades\Facade::class,
+ 'Rcon' => App\Facades\Rcon::class,
])->toArray(),
];
diff --git a/config/database.php b/config/database.php
index 137ad18..fdeafaf 100644
--- a/config/database.php
+++ b/config/database.php
@@ -57,7 +57,7 @@
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
- 'engine' => null,
+ 'engine' => 'InnoDB',
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
diff --git a/config/models.php b/config/models.php
index 8e4d387..85eeb49 100644
--- a/config/models.php
+++ b/config/models.php
@@ -447,7 +447,7 @@
| NOTE: This requires PHP 7.0 or later.
|
*/
- 'enable_return_types' => false,
+ 'enable_return_types' => true,
],
/*
diff --git a/config/rcon.php b/config/rcon.php
deleted file mode 100644
index dca1c3d..0000000
--- a/config/rcon.php
+++ /dev/null
@@ -1,41 +0,0 @@
- 'default',
-
- /*
- |--------------------------------------------------------------------------
- | RCON Connections
- |--------------------------------------------------------------------------
- |
- | Here are each of the RCON connections setup for your application.
- |
- */
-
- 'connections' => [
- 'default' => [
- 'host' => 'someHost',
- 'port' => 123,
- 'password' => 'SuperSafePassword',
- 'timeout' => 60
- ],
- 'default2' => [
- 'host' => 'someHost',
- 'port' => 123,
- 'password' => 'SuperSafePassword',
- 'timeout' => 60
- ],
- ]
-
-];
diff --git a/config/toaster.php b/config/toaster.php
new file mode 100644
index 0000000..b257694
--- /dev/null
+++ b/config/toaster.php
@@ -0,0 +1,46 @@
+ true,
+
+ /**
+ * The vertical alignment of the toast container.
+ *
+ * Supported: "bottom", "middle" or "top"
+ */
+ 'alignment' => 'bottom',
+
+ /**
+ * Allow users to close toast messages prematurely.
+ *
+ * Supported: true | false
+ */
+ 'closeable' => true,
+
+ /**
+ * The on-screen duration of each toast.
+ *
+ * Minimum: 3000 (in milliseconds)
+ */
+ 'duration' => 3000,
+
+ /**
+ * The horizontal position of each toast.
+ *
+ * Supported: "center", "left" or "right"
+ */
+ 'position' => 'right',
+
+ /**
+ * Whether messages passed as translation keys should be translated automatically.
+ *
+ * Supported: true | false
+ */
+ 'translate' => true,
+];
diff --git a/database/migrations/2024_02_03_224620_rename_join_and_leave_table.php b/database/migrations/2024_02_03_224620_rename_join_and_leave_table.php
new file mode 100644
index 0000000..ee41150
--- /dev/null
+++ b/database/migrations/2024_02_03_224620_rename_join_and_leave_table.php
@@ -0,0 +1,24 @@
+id();
+ $table->string('host');
+ $table->integer('port', false, true);
+ $table->string('password')->nullable();
+ $table->integer('timeout')->default(5);
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('rcon_data');
+ }
+};
diff --git a/database/migrations/2024_02_04_051514_add_rcon_i_dto_servers_table.php b/database/migrations/2024_02_04_051514_add_rcon_i_dto_servers_table.php
new file mode 100644
index 0000000..f124ce5
--- /dev/null
+++ b/database/migrations/2024_02_04_051514_add_rcon_i_dto_servers_table.php
@@ -0,0 +1,33 @@
+dropColumn('rcon');
+
+ $table->foreignId('rcon_data_id');
+
+ $table->foreign('rcon_data_id')->on('rcon_data')->references('id');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('servers', function (Blueprint $table) {
+ $table->dropForeign(['rcon_data_id']);
+ $table->dropColumn('rcon_data_id');
+ });
+ }
+};
diff --git a/docker/8.3/supervisord.conf b/docker/8.3/supervisord.conf
index c199a6b..488b652 100644
--- a/docker/8.3/supervisord.conf
+++ b/docker/8.3/supervisord.conf
@@ -14,10 +14,12 @@ stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:scheduler]
-command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan short-schedule:run --lifetime=60
+command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan short-schedule:run --lifetime=300
user=sail
environment=LARAVEL_SAIL="1"
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
+autostart=true
+autorestart=true
diff --git a/install-start.ps1 b/install-start.ps1
new file mode 100644
index 0000000..fb3654b
--- /dev/null
+++ b/install-start.ps1
@@ -0,0 +1,82 @@
+$PHP_INSTALL_DIR = 'C:\php'
+$PHP_IN_PATH = 0
+$PHP_8_3_URL = "https://windows.php.net/downloads/releases/php-8.3.2-Win32-vs16-x64.zip"
+$DL_DEST = "C:\php\php.zip"
+$PHP_CONFIG_PATH = "C:\php\php.ini"
+$PHP_PROD_CONFIG_PATH = "C:\php\php.ini-production"
+$PHP_PATH="php.exe"
+$SCRIPT_PATH = split-path -parent $MyInvocation.MyCommand.Definition
+$DB_PATH = "$SCRIPT_PATH\database\database.sqlite"
+
+
+
+
+#Check if php.exe is already in $PATH
+if ((Get-Command $PHP_PATH -ErrorAction SilentlyContinue) -eq $null)
+{
+ Write-Output "PHP is not in PATH"
+ #Check if a path for php already exists
+ if (Test-Path -Path $PHP_INSTALL_DIR) {
+ Write-Output "PHP Path exists, skipping dowload"
+ } else {
+ New-Item -ItemType Directory -Path C:\php
+ Invoke-WebRequest -Uri $PHP_8_3_URL -OutFile $DL_DEST
+ Expand-Archive -LiteralPath $DL_DEST -DestinationPath $PHP_INSTALL_DIR
+ $PHP_PATH = C:\php\php.exe
+ }
+
+ $PHP_PATH = 'C:\php\php.exe'
+
+ if(Test-Path $PHP_CONFIG_PATH -PathType Leaf)
+ {
+ Write-Output "PHP ini exists, skipping extension activation"
+ } else {
+ $content = Get-Content $PHP_PROD_CONFIG_PATH
+ $content | ForEach-Object {$_ -replace ";extension=curl", "extension=curl" -replace ";extension=fileinfo", "extension=fileinfo" -replace ";extension=mbstring", "extension=mbstring" -replace ";extension=openssl", "extension=openssl" -replace ";extension=pdo_mysql", "extension=pdo_mysql" -replace ";extension=pdo_sqlite", "extension=pdo_sqlite"} | Set-Content $PHP_CONFIG_PATH
+ }
+}
+
+Get-Content "$SCRIPT_PATH\.env" | Where { $_ } | foreach {
+ $name, $value = $_.split('=')
+
+ if ([string]::IsNullOrWhiteSpace($name) -or $name.Contains('#')) {
+ continue
+ }
+
+ if($name.Equals("APP_KEY") -and [string]::IsNullOrWhiteSpace($value))
+ {
+ Write-Output "Generating App Key"
+ & "$PHP_PATH" @("$SCRIPT_PATH\artisan", 'key:generate', '--force')
+ }
+}
+
+& "$PHP_PATH" @("$SCRIPT_PATH\artisan", 'config:clear')
+& "$PHP_PATH" @("$SCRIPT_PATH\artisan", 'cache:clear')
+$content = Get-Content "$SCRIPT_PATH\.env"
+$content | ForEach-Object {$_ -replace "DB_CONNECTION=mysql", "DB_CONNECTION=sqlite" -replace "DB_DATABASE=palworld_admin_panel", ""} | Set-Content "$SCRIPT_PATH\.env"
+
+if(Test-Path $DB_PATH -PathType Leaf)
+{
+ Write-Output "DB File Exists."
+}
+else
+{
+ Write-Output "Creating new file"
+ New-Item -type file "$DB_PATH"
+}
+Write-Output ".env Adjusted"
+
+#Onetime
+Start-Process -NoNewWindow -FilePath "$PHP_PATH" -ArgumentList "$SCRIPT_PATH\artisan", "serve", "--host=127.0.0.1", "--port=80"
+Start-Process -NoNewWindow -FilePath "$PHP_PATH" -ArgumentList "$SCRIPT_PATH\artisan", "short-schedule:run"
+
+#New-Service -Name "Palworld Admin Tool WebService" -BinaryPathName "$PHP_PATH $SCRIPT_PATH\artisan serve --host=127.0.0.1 --port=80"
+#New-Service -Name "Palworld Admin Tool Scheduler" -BinaryPathName "$PHP_PATH $SCRIPT_PATH\artisan short-schedule:run"
+
+#Service Part
+#Start-Process -FilePath powershell.exe -Verb RunAs -Wait -ArgumentList '-Command', "function myService() {New-Service -Name `"Palworld Admin Tool WebService`" -BinaryPathName `"$PHP_PATH $SCRIPT_PATH\artisan serve --host=127.0.0.1 --port=80`"};myService"
+#Start-Process -FilePath powershell.exe -Verb RunAs -Wait -ArgumentList '-Command', "function myService() {New-Service -Name `"Palworld Admin Tool Scheduler`" -BinaryPathName `"$PHP_PATH $SCRIPT_PATH\artisan short-schedule:run`"};myService"
+
+
+#Start-Service -Name "Palworld Admin Tool WebService"
+#Start-Service -Name "Palworld Admin Tool Scheduler"
diff --git a/package-lock.json b/package-lock.json
index ba49794..40d8ccc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,9 +1,13 @@
{
- "name": "palworld-admin-panel",
+ "name": "html",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
+ "dependencies": {
+ "moment": "^2.30.1",
+ "moment-timezone": "^0.5.45"
+ },
"devDependencies": {
"@tailwindcss/forms": "^0.5.2",
"autoprefixer": "^10.4.2",
@@ -1477,6 +1481,25 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/moment": {
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/moment-timezone": {
+ "version": "0.5.45",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz",
+ "integrity": "sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==",
+ "dependencies": {
+ "moment": "^2.29.4"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@@ -3247,6 +3270,19 @@
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
"dev": true
},
+ "moment": {
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="
+ },
+ "moment-timezone": {
+ "version": "0.5.45",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz",
+ "integrity": "sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==",
+ "requires": {
+ "moment": "^2.29.4"
+ }
+ },
"mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
diff --git a/package.json b/package.json
index 0b3bd87..369e190 100644
--- a/package.json
+++ b/package.json
@@ -13,5 +13,9 @@
"postcss": "^8.4.31",
"tailwindcss": "^3.1.0",
"vite": "^5.0.0"
+ },
+ "dependencies": {
+ "moment": "^2.30.1",
+ "moment-timezone": "^0.5.45"
}
}
diff --git a/resources/js/app.js b/resources/js/app.js
index e59d6a0..b6223ea 100644
--- a/resources/js/app.js
+++ b/resources/js/app.js
@@ -1 +1,2 @@
import './bootstrap';
+import '../../vendor/masmerise/livewire-toaster/resources/js';
diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js
index 846d350..40d5c82 100644
--- a/resources/js/bootstrap.js
+++ b/resources/js/bootstrap.js
@@ -30,3 +30,6 @@ window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
// enabledTransports: ['ws', 'wss'],
// });
+
+import moment from 'moment-timezone';
+window.moment = moment
diff --git a/resources/views/components/link-danger-button.blade.php b/resources/views/components/link-danger-button.blade.php
new file mode 100644
index 0000000..19826ae
--- /dev/null
+++ b/resources/views/components/link-danger-button.blade.php
@@ -0,0 +1,3 @@
+merge(['href' => '#', 'class' => 'inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150']) }}>
+ {{ $slot }}
+
diff --git a/resources/views/components/link-primary-button.blade.php b/resources/views/components/link-primary-button.blade.php
new file mode 100644
index 0000000..2600b9c
--- /dev/null
+++ b/resources/views/components/link-primary-button.blade.php
@@ -0,0 +1,3 @@
+merge(['href' => '#', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 dark:bg-gray-200 border border-transparent rounded-md font-semibold text-xs text-white dark:text-gray-800 uppercase tracking-widest hover:bg-gray-700 dark:hover:bg-white focus:bg-gray-700 dark:focus:bg-white active:bg-gray-900 dark:active:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150']) }}>
+ {{ $slot }}
+
diff --git a/resources/views/components/link-secondary-button.blade.php b/resources/views/components/link-secondary-button.blade.php
new file mode 100644
index 0000000..ad1f87d
--- /dev/null
+++ b/resources/views/components/link-secondary-button.blade.php
@@ -0,0 +1,3 @@
+merge(['href' => '#', 'class' => 'inline-flex items-center px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-500 rounded-md font-semibold text-xs text-gray-700 dark:text-gray-300 uppercase tracking-widest shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-25 transition ease-in-out duration-150']) }}>
+ {{ $slot }}
+
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php
index a227d0e..3007876 100644
--- a/resources/views/layouts/app.blade.php
+++ b/resources/views/layouts/app.blade.php
@@ -32,6 +32,7 @@
{{ $slot }}
+