Add snapshots
This commit is contained in:
parent
4ae2bd5b44
commit
871dfa5675
16 changed files with 261 additions and 51 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -12,3 +12,5 @@ vendor/
|
|||
.idea/
|
||||
log/*
|
||||
!log/.gitkeep
|
||||
data/snapshots/*
|
||||
!data/snapshots/.gitkeep
|
||||
|
|
49
README.md
49
README.md
|
@ -2,52 +2,15 @@
|
|||
|
||||
**ts3web** is a webinterface for any TeamSpeak 3 Server used with serverQuery login.
|
||||
|
||||
Most TeamSpeak 3 interfaces are bloated although nearly all configuration can be done entirely in the TeamSpeak 3 Client.
|
||||
This webinterface aims to be as simple as possible. It does not provide complex features. Instead, it only supports features considered useful for a TeamSpeak 3 web interface. **The minimalistic approach is intentional!**
|
||||
|
||||
This webinterface aims to be as simple as possible. It does not provide complex features which can be configured within the client. Instead, it only supports features considered useful for a TeamSpeak 3 web interface. **The minimalistic approach is intentional!**
|
||||
|
||||
If you like to help (to translate or implement missing features), open an issue before. Then create a pull request. You should use existing code to implement new features. PRs will be merged after a code review.
|
||||
If you like to help (to translate or implement missing features), open an issue first. You should use existing code to implement new features. PRs will be merged after a code review.
|
||||
|
||||
Things you **cannot** do:
|
||||
- Channels create
|
||||
- Permissions add, edit, delete (servergroups, channelgroups, client)
|
||||
- File management (create, download, delete)
|
||||
- Move online users
|
||||
- features which are not *explicitly* supported
|
||||
|
||||
Things you **can** do:
|
||||
- view
|
||||
- instance and host information
|
||||
- global log
|
||||
- virtual servers
|
||||
- users online
|
||||
- all known clients
|
||||
- channels
|
||||
- groups
|
||||
- channel groups
|
||||
- files
|
||||
- banlist
|
||||
- complain list
|
||||
- permissions (server, channel, client)
|
||||
- edit
|
||||
- virtual server
|
||||
- instance
|
||||
- delete
|
||||
- bans
|
||||
- complains
|
||||
- virtual servers
|
||||
- clients
|
||||
- server groups
|
||||
- channel groups
|
||||
- other actions
|
||||
- create virtual servers
|
||||
- generate serverQuery password
|
||||
- send message to users, servers, channels
|
||||
- ban a user
|
||||
- kick a user
|
||||
- poke a user
|
||||
- add to server group
|
||||
- remove from server group
|
||||
- Permissions Management (add, edit, delete for servergroups, channelgroups, clients)
|
||||
- File Management (except viewing)
|
||||
- Temporary passwords
|
||||
- Move online clients
|
||||
|
||||
## Install ##
|
||||
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
"par0noid/ts3admin": "^1.0",
|
||||
"planetteamspeak/ts3-php-framework": "^1.1",
|
||||
"nesbot/carbon": "^1.25",
|
||||
"bryanjhv/slim-session": "^3.5"
|
||||
"bryanjhv/slim-session": "^3.5",
|
||||
"symfony/filesystem": "^4.0",
|
||||
"symfony/finder": "^4.0"
|
||||
},
|
||||
"config": {
|
||||
"bin-dir": "bin/"
|
||||
|
|
|
@ -41,6 +41,11 @@ class ACL extends \Zend\Permissions\Acl\Acl
|
|||
'/servers/send/{sid}',
|
||||
'/servers/edit/{sid}',
|
||||
|
||||
'/snapshots/{sid}',
|
||||
'/snapshots/create/{sid}',
|
||||
'/snapshots/deploy/{sid}/{name}',
|
||||
'/snapshots/delete/{sid}/{name}',
|
||||
|
||||
'/tokens/{sid}',
|
||||
'/tokens/add/{sid}',
|
||||
'/tokens/delete/{sid}/{token}',
|
||||
|
|
|
@ -305,3 +305,24 @@ $container[TokenDeleteAction::class] = function ($container) {
|
|||
return new TokenDeleteAction($container);
|
||||
};
|
||||
$app->get('/tokens/delete/{sid}/{token}', TokenDeleteAction::class);
|
||||
|
||||
// snapshots
|
||||
$container[SnapshotsAction::class] = function ($container) {
|
||||
return new SnapshotsAction($container);
|
||||
};
|
||||
$app->get('/snapshots/{sid}', SnapshotsAction::class);
|
||||
|
||||
$container[SnapshotCreateAction::class] = function ($container) {
|
||||
return new SnapshotCreateAction($container);
|
||||
};
|
||||
$app->get('/snapshots/create/{sid}', SnapshotCreateAction::class);
|
||||
|
||||
$container[SnapshotDeployAction::class] = function ($container) {
|
||||
return new SnapshotDeployAction($container);
|
||||
};
|
||||
$app->get('/snapshots/deploy/{sid}/{name}', SnapshotDeployAction::class);
|
||||
|
||||
$container[SnapshotDeleteAction::class] = function ($container) {
|
||||
return new SnapshotDeleteAction($container);
|
||||
};
|
||||
$app->get('/snapshots/delete/{sid}/{name}', SnapshotDeleteAction::class);
|
||||
|
|
|
@ -64,6 +64,7 @@ menu.servers.groups: "Groups"
|
|||
menu.servers.bans: "Bans"
|
||||
menu.servers.complains: "Complains"
|
||||
menu.servers.tokens: "Tokens"
|
||||
menu.servers.snapshots: "Snapshots"
|
||||
menu.servers.logs: "Log"
|
||||
|
||||
# titles
|
||||
|
@ -84,6 +85,7 @@ servergroup_info.title: "Server Group Info"
|
|||
channelgroup_info.title: "Channelgroup"
|
||||
profile.title: "Profile"
|
||||
tokens.title: "Tokens"
|
||||
snapshots.title: "Snapshots"
|
||||
|
||||
# dynamic render of key value pairs
|
||||
key: "Attribute"
|
||||
|
@ -232,5 +234,13 @@ tokens.add.serverGroup: "Servergroup (type SERVER has to be selected)"
|
|||
tokens.add.channelGroup: "Channelgroup (type CHANNEL has to be selected)"
|
||||
tokens.add.channel: "Channel (type CHANNEL has to be selected)"
|
||||
tokens.add.description: "Description"
|
||||
tokens.type.servergroup: "Server: "
|
||||
tokens.type.channelgroup: "Channel: "
|
||||
|
||||
# snapshots
|
||||
file.exists: "File already exists"
|
||||
file.notexists: "File does not exist"
|
||||
snapshots.h.actions: "Actions"
|
||||
snapshots.create: "Create a new snapshot"
|
||||
snapshots.h.details: "Details"
|
||||
snapshots.deploy: "Deploy"
|
||||
snapshots.delete: "Delete"
|
||||
|
||||
|
|
0
data/snapshots/.gitkeep
Normal file
0
data/snapshots/.gitkeep
Normal file
|
@ -139,11 +139,9 @@ $container['view'] = function ($container) use ($app) {
|
|||
return $container['session']->get($key);
|
||||
}));
|
||||
|
||||
// ts specific: file size
|
||||
// file size
|
||||
$fileSizeFilter = new Twig_SimpleFilter('file', function($bytes, $decimals = 2) {
|
||||
$sz = 'BKMGTP';
|
||||
$factor = floor((strlen($bytes) - 1) / 3);
|
||||
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
|
||||
return FileHelper::humanFileSize($bytes, $decimals);
|
||||
});
|
||||
$view->getEnvironment()->addFilter($fileSizeFilter);
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ final class ServerDeleteAction extends AbstractAction
|
|||
$sid = $args['sid'];
|
||||
|
||||
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||
|
||||
$dataResult = $this->ts->getInstance()->serverDelete($sid);
|
||||
|
||||
|
|
32
src/Control/Actions/SnapshotCreateAction.php
Normal file
32
src/Control/Actions/SnapshotCreateAction.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
final class SnapshotCreateAction extends AbstractAction
|
||||
{
|
||||
public function __invoke(Request $request, Response $response, $args)
|
||||
{
|
||||
$sid = $args['sid'];
|
||||
|
||||
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||
|
||||
$snapshotCreateResult = $this->ts->getInstance()->serverSnapshotCreate();
|
||||
|
||||
$fileSystem = new Filesystem();
|
||||
$name = Carbon::now()->getTimestamp();
|
||||
$path = FileHelper::SNAPSHOTS_PATH . DIRECTORY_SEPARATOR . $sid . DIRECTORY_SEPARATOR . $name;
|
||||
|
||||
if ($fileSystem->exists($path)) {
|
||||
$this->flash->addMessage('error', $this->translator->trans('file.exists'));
|
||||
} else {
|
||||
$fileSystem->appendToFile($path, trim($snapshotCreateResult['data']));
|
||||
$this->flash->addMessage('success', $this->translator->trans('done'));
|
||||
}
|
||||
|
||||
return $response->withRedirect('/snapshots/' . $sid);
|
||||
}
|
||||
}
|
37
src/Control/Actions/SnapshotDeleteAction.php
Normal file
37
src/Control/Actions/SnapshotDeleteAction.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
final class SnapshotDeleteAction extends AbstractAction
|
||||
{
|
||||
public function __invoke(Request $request, Response $response, $args)
|
||||
{
|
||||
$sid = $args['sid'];
|
||||
$name = $args['name'];
|
||||
|
||||
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||
|
||||
$fileSystem = new Filesystem();
|
||||
$path = FileHelper::SNAPSHOTS_PATH . DIRECTORY_SEPARATOR . $sid . DIRECTORY_SEPARATOR . $name;
|
||||
|
||||
if (!$fileSystem->exists($path)) {
|
||||
$this->flash->addMessage('error', $this->translator->trans('file.notexists'));
|
||||
} else {
|
||||
$fileSystem->remove($path);
|
||||
|
||||
$serverPath = FileHelper::SNAPSHOTS_PATH . DIRECTORY_SEPARATOR . $sid;
|
||||
|
||||
if (count(FileHelper::getFiles($serverPath)) == 0) {
|
||||
$fileSystem->remove($serverPath);
|
||||
}
|
||||
|
||||
$this->flash->addMessage('success', $this->translator->trans('done'));
|
||||
}
|
||||
|
||||
return $response->withRedirect('/snapshots/' . $sid);
|
||||
}
|
||||
}
|
31
src/Control/Actions/SnapshotDeployAction.php
Normal file
31
src/Control/Actions/SnapshotDeployAction.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
final class SnapshotDeployAction extends AbstractAction
|
||||
{
|
||||
public function __invoke(Request $request, Response $response, $args)
|
||||
{
|
||||
$sid = $args['sid'];
|
||||
$name = $args['name'];
|
||||
|
||||
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||
|
||||
$fileSystem = new Filesystem();
|
||||
$path = FileHelper::SNAPSHOTS_PATH . DIRECTORY_SEPARATOR . $sid . DIRECTORY_SEPARATOR . $name;
|
||||
|
||||
if (!$fileSystem->exists($path)) {
|
||||
$this->flash->addMessage('error', $this->translator->trans('file.notexists'));
|
||||
} else {
|
||||
$snapshotData = file_get_contents($path);
|
||||
$this->ts->getInstance()->serverSnapshotDeploy($snapshotData, true);
|
||||
$this->flash->addMessage('success', $this->translator->trans('done'));
|
||||
}
|
||||
|
||||
return $response->withRedirect('/snapshots/' . $sid);
|
||||
}
|
||||
}
|
24
src/Control/Actions/SnapshotsAction.php
Normal file
24
src/Control/Actions/SnapshotsAction.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
final class SnapshotsAction extends AbstractAction
|
||||
{
|
||||
public function __invoke(Request $request, Response $response, $args)
|
||||
{
|
||||
$sid = $args['sid'];
|
||||
|
||||
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
||||
|
||||
$snapshots = FileHelper::getFiles(FileHelper::SNAPSHOTS_PATH . DIRECTORY_SEPARATOR . $sid);
|
||||
|
||||
// render GET
|
||||
$this->view->render($response, 'snapshots.twig', [
|
||||
'title' => $this->translator->trans('snapshots.title'),
|
||||
'data' => $snapshots,
|
||||
'sid' => $sid
|
||||
]);
|
||||
}
|
||||
}
|
52
src/Util/FileHelper.php
Normal file
52
src/Util/FileHelper.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
class FileHelper
|
||||
{
|
||||
const SNAPSHOTS_PATH = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'snapshots';
|
||||
|
||||
/**
|
||||
* Get all files in FILES_DIR
|
||||
*
|
||||
* @param $directory
|
||||
* @return array
|
||||
*/
|
||||
public static function getFiles($directory)
|
||||
{
|
||||
$files = [];
|
||||
|
||||
$fileSystem = new Filesystem();
|
||||
if (!$fileSystem->exists($directory)) {
|
||||
return $files;
|
||||
}
|
||||
|
||||
$finder = new Finder();
|
||||
$finder->files()->in($directory)->sortByChangedTime();
|
||||
|
||||
foreach ($finder as $file) {
|
||||
$files[] = [
|
||||
'name' => $file->getFilename(),
|
||||
'size' => FileHelper::humanFileSize($file->getSize()),
|
||||
'date' => Carbon::createFromTimestamp($file->getMTime())
|
||||
];
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output human readable file size
|
||||
*
|
||||
* @param $bytes
|
||||
* @param int $decimals
|
||||
* @return string
|
||||
*/
|
||||
public static function humanFileSize($bytes, $decimals = 2) {
|
||||
$sz = 'BKMGTP';
|
||||
$factor = floor((strlen($bytes) - 1) / 3);
|
||||
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
|
||||
}
|
||||
}
|
|
@ -40,6 +40,9 @@
|
|||
<a href="/tokens/{{ session_get('sid') }}">
|
||||
<li class="active withripple" data-target="#page">{% trans %}menu.servers.tokens{% endtrans %}</li>
|
||||
</a>
|
||||
<a href="/snapshots/{{ session_get('sid') }}">
|
||||
<li class="active withripple" data-target="#page">{% trans %}menu.servers.snapshots{% endtrans %}</li>
|
||||
</a>
|
||||
<a href="/logs/{{ session_get('sid') }}">
|
||||
<li class="active withripple" data-target="#page">{% trans %}menu.servers.logs{% endtrans %}</li>
|
||||
</a>
|
||||
|
|
31
src/View/material/snapshots.twig
Normal file
31
src/View/material/snapshots.twig
Normal file
|
@ -0,0 +1,31 @@
|
|||
{% extends 'layout.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="header">{{ title }}</h1>
|
||||
|
||||
<h2 class="header">{% trans %}snapshots.h.actions{% endtrans %}</h2>
|
||||
<a href="/snapshots/create/{{ sid }}">{% trans %}snapshots.create{% endtrans %}</a>
|
||||
|
||||
{% if data|length > 0 %}
|
||||
<h2 class="header">{% trans %}snapshots.h.details{% endtrans %}</h2>
|
||||
|
||||
{% include 'table.twig' with {'data': data,
|
||||
|
||||
'additional_links': [
|
||||
{
|
||||
'header_label': 'snapshots.deploy'|trans,
|
||||
'label': '<i class="material-icons">check_circle</i>',
|
||||
'uri': '/snapshots/deploy/' ~ sid,
|
||||
'uri_param': 'name'
|
||||
},
|
||||
{
|
||||
'header_label': 'snapshots.delete'|trans,
|
||||
'label': '<i class="material-icons">delete</i>',
|
||||
'uri': '/snapshots/delete/' ~ sid,
|
||||
'uri_param': 'name'
|
||||
}
|
||||
],
|
||||
|
||||
} %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
Reference in a new issue