Archived
1
0
Fork 0

Add snapshots

This commit is contained in:
Varakh 2018-04-06 13:36:08 +02:00
parent 4ae2bd5b44
commit 871dfa5675
16 changed files with 261 additions and 51 deletions

2
.gitignore vendored
View file

@ -12,3 +12,5 @@ vendor/
.idea/ .idea/
log/* log/*
!log/.gitkeep !log/.gitkeep
data/snapshots/*
!data/snapshots/.gitkeep

View file

@ -2,52 +2,15 @@
**ts3web** is a webinterface for any TeamSpeak 3 Server used with serverQuery login. **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 first. 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 before. Then create a pull request. You should use existing code to implement new features. PRs will be merged after a code review.
Things you **cannot** do: Things you **cannot** do:
- Channels create - Permissions Management (add, edit, delete for servergroups, channelgroups, clients)
- Permissions add, edit, delete (servergroups, channelgroups, client) - File Management (except viewing)
- File management (create, download, delete) - Temporary passwords
- Move online users - Move online clients
- 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
## Install ## ## Install ##

View file

@ -13,7 +13,9 @@
"par0noid/ts3admin": "^1.0", "par0noid/ts3admin": "^1.0",
"planetteamspeak/ts3-php-framework": "^1.1", "planetteamspeak/ts3-php-framework": "^1.1",
"nesbot/carbon": "^1.25", "nesbot/carbon": "^1.25",
"bryanjhv/slim-session": "^3.5" "bryanjhv/slim-session": "^3.5",
"symfony/filesystem": "^4.0",
"symfony/finder": "^4.0"
}, },
"config": { "config": {
"bin-dir": "bin/" "bin-dir": "bin/"

View file

@ -41,6 +41,11 @@ class ACL extends \Zend\Permissions\Acl\Acl
'/servers/send/{sid}', '/servers/send/{sid}',
'/servers/edit/{sid}', '/servers/edit/{sid}',
'/snapshots/{sid}',
'/snapshots/create/{sid}',
'/snapshots/deploy/{sid}/{name}',
'/snapshots/delete/{sid}/{name}',
'/tokens/{sid}', '/tokens/{sid}',
'/tokens/add/{sid}', '/tokens/add/{sid}',
'/tokens/delete/{sid}/{token}', '/tokens/delete/{sid}/{token}',

View file

@ -305,3 +305,24 @@ $container[TokenDeleteAction::class] = function ($container) {
return new TokenDeleteAction($container); return new TokenDeleteAction($container);
}; };
$app->get('/tokens/delete/{sid}/{token}', TokenDeleteAction::class); $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);

View file

@ -64,6 +64,7 @@ menu.servers.groups: "Groups"
menu.servers.bans: "Bans" menu.servers.bans: "Bans"
menu.servers.complains: "Complains" menu.servers.complains: "Complains"
menu.servers.tokens: "Tokens" menu.servers.tokens: "Tokens"
menu.servers.snapshots: "Snapshots"
menu.servers.logs: "Log" menu.servers.logs: "Log"
# titles # titles
@ -84,6 +85,7 @@ servergroup_info.title: "Server Group Info"
channelgroup_info.title: "Channelgroup" channelgroup_info.title: "Channelgroup"
profile.title: "Profile" profile.title: "Profile"
tokens.title: "Tokens" tokens.title: "Tokens"
snapshots.title: "Snapshots"
# dynamic render of key value pairs # dynamic render of key value pairs
key: "Attribute" 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.channelGroup: "Channelgroup (type CHANNEL has to be selected)"
tokens.add.channel: "Channel (type CHANNEL has to be selected)" tokens.add.channel: "Channel (type CHANNEL has to be selected)"
tokens.add.description: "Description" 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
View file

View file

@ -139,11 +139,9 @@ $container['view'] = function ($container) use ($app) {
return $container['session']->get($key); return $container['session']->get($key);
})); }));
// ts specific: file size // file size
$fileSizeFilter = new Twig_SimpleFilter('file', function($bytes, $decimals = 2) { $fileSizeFilter = new Twig_SimpleFilter('file', function($bytes, $decimals = 2) {
$sz = 'BKMGTP'; return FileHelper::humanFileSize($bytes, $decimals);
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
}); });
$view->getEnvironment()->addFilter($fileSizeFilter); $view->getEnvironment()->addFilter($fileSizeFilter);

View file

@ -10,7 +10,6 @@ final class ServerDeleteAction extends AbstractAction
$sid = $args['sid']; $sid = $args['sid'];
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']); $this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
$dataResult = $this->ts->getInstance()->serverDelete($sid); $dataResult = $this->ts->getInstance()->serverDelete($sid);

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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
View 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];
}
}

View file

@ -40,6 +40,9 @@
<a href="/tokens/{{ session_get('sid') }}"> <a href="/tokens/{{ session_get('sid') }}">
<li class="active withripple" data-target="#page">{% trans %}menu.servers.tokens{% endtrans %}</li> <li class="active withripple" data-target="#page">{% trans %}menu.servers.tokens{% endtrans %}</li>
</a> </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') }}"> <a href="/logs/{{ session_get('sid') }}">
<li class="active withripple" data-target="#page">{% trans %}menu.servers.logs{% endtrans %}</li> <li class="active withripple" data-target="#page">{% trans %}menu.servers.logs{% endtrans %}</li>
</a> </a>

View 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 %}