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