diff --git a/CHANGELOG.md b/CHANGELOG.md index b7b5710..b5bb611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # CHANGELOG +## 2.1.0 - 2019/08/07 +* Fixed file handling on snapshots +* Cleaned up template links +* Updated documentation +* Added application log view +* Fixed files view +* Added show total used space for files in a channel +* Added file delete action +* Removed about modal + ## 2.0.0 - 2019/08/06 * Replace material design with bootstrap4 theme * Add an about modal diff --git a/README.md b/README.md index b56adc9..bf8b48b 100755 --- a/README.md +++ b/README.md @@ -1,28 +1,46 @@ # README - -ts3web is a web interface for one TeamSpeak 3 Server. It's using serverquery to login. +ts3web is a free and open-source web interface for TeamSpeak 3 instances. -This web interface aims to be as simple as possible. The minimalistic approach is intentional. +The minimalistic approach of this application is intentional. -Feel free to submit pull requests if you like to help. More information are here: [https://hub.docker.com/r/varakh/ts3web](https://hub.docker.com/r/varakh/ts3web) +* Docker images available on https://hub.docker.com/r/varakh/ts3web +* Sources are hosted on https://git.myservermanager.com/alexander.schaeferdiek/ts3web -Features which are currently **not supported**: +## Limitations +Features which are currently not supported: +* upload files (only viewing and deleting) * modify permissions (only viewing) -* modify files (only viewing) -**ts3web** can be deployed in different ways. See below for more information. For each deployment type a running -TeamSpeak 3 server is a prerequisite (except for the `docker-compose.yml` type which will start also the server if -needed). +## F.A.Q + +###### There are lots of TeamSpeak 3 web interfaces out. Why should I pick ts3web? +Free, simple, stateless, easy to extend, standard bootstrap theme. + +###### I always get `TSException: Error: host isn't a ts3 instance!` when selecting a server. +You probably got query banned from your server. You need to properly define your `whitelist.txt` file and include it in +your TeamSpeak application. ## Configuration - The main configuration file is the `env` file located in `config/`. There's an example file called `env.example` -which you can copy to `config/env`. Defaults will assume you're running your TeamSpeak server on `localhost` with +which you **need* to copy to `config/env`. Defaults will assume you're running your TeamSpeak server on `localhost` with default port. Docker deployments can host bind this file into the container directly and just maintain the `env` file. -## Usage with docker-compose +## Deployment +The application can be deployed in different ways. See below for more information. For each deployment type a running +TeamSpeak 3 instance is a prerequisite (except for the `docker-compose.yml` type which will start also the server if +needed). +### Exposed volumes on docker images +* Snapshots are saved in `/var/www/html/application/data/snapshots`. You should create a volume for this location if +you're using docker as deployment type. +* Logs are saved in `/var/www/html/application/log` for docker containers. You should create a volume +for this location if you're using docker as deployment type. + +**Important**: Ensure that host binds have permissions set up properly. The user which is used in the docker container is `www-data` with +id `82`. If, e.g. logs are host bound, then execute `chown -R 82:82 host/path/to/log`. The same holds true for snapshots. + +### Usage with docker-compose The recommended way is to use docker-compose. The `network_mode = "host"` is required in order to show correct IP addresses of connected users. @@ -59,6 +77,7 @@ services: volumes: - ./env:/var/www/html/application/config/env - ./snapshots:/var/www/html/application/data/snapshots + - ./log:/var/www/html/application/log ports: - 127.0.0.1:8181:80 depends_on: @@ -76,16 +95,13 @@ Your TeamSpeak 3 Server will be available under `public-server-ip:9987`. The web For testing purposes, change `- 127.0.0.1:8181:80` to `- 8181:80`. The web interface will then be available under `public-server-ip:8181`. This is **not recommended**! Secure your setup properly via reverse proxy and SSL. -Snapshots are saved in `/var/www/html/application/data/snapshots`. You should create a volume for this location. - -## Usage as single docker container - +### Usage as single docker container * Copy `env.example` to `env` and adjust to your needs. It's recommended to make it persistent outside of the container. * Create a container with the image, e.g. `docker run --name teamspeak_web -v ./env:/var/www/html/application/config/env -p 8181:80 varakh/ts3web:latest`. * Make sure that if teamspeak and ts3web share the same docker instance they should be put into one network and the subnet **needs be added to teamspeak's query whitelist**. * Point your browser to `8181` to see the web interface. -## Usage as native application +### Usage as native application **Prerequisite**: `php`, `composer` and probably `php-fpm` installed on the server. To install: @@ -100,7 +116,7 @@ To upgrade: * `git pull` * `composer update` -## Web server setup +### Web server setup * Example `nginx.conf` for **standalone** deployment without SSL: ``` @@ -153,7 +169,6 @@ To upgrade: * Tag the release git commit and create a new release in the VCS web interface ### Helpers - Attributes can be defined when including `table`, `keyvalues` and `form` templates of twig. This helps to generate tables and forms without the need to specify all attributes. ``` diff --git a/config/ACL.php b/config/ACL.php index b38b9de..bc69977 100644 --- a/config/ACL.php +++ b/config/ACL.php @@ -70,6 +70,7 @@ class ACL extends \Zend\Permissions\Acl\Acl '/channels/edit/{sid}/{cid}', '/channels/delete/{sid}/{cid}', '/channels/send/{sid}/{cid}', + '/channels/files/delete/{sid}/{cid}', '/groups/{sid}', diff --git a/config/EnvConstants.php b/config/EnvConstants.php index 94a1516..2273e51 100644 --- a/config/EnvConstants.php +++ b/config/EnvConstants.php @@ -50,6 +50,11 @@ class EnvConstants */ const TEAMSPEAK_USER = "teamspeak_user"; + /** + * TeamSpeak log lines + */ + const TEAMSPEAK_LOG_LINES = "teamspeak_log_lines"; + /** * Log name */ diff --git a/config/env.example b/config/env.example index 4703e66..7276a34 100644 --- a/config/env.example +++ b/config/env.example @@ -12,6 +12,7 @@ teamspeak_host="localhost" # 'localhost' or 'name_of_docker_container' if runnin teamspeak_query_port=10011 teamspeak_user="serveradmin" teamspeak_tree_view="true" # show a tree view in the details of online clients if a server has been selected +teamspeak_log_lines=100 # show this amount of latest log lines # log log_name="ts3web" # values: all strings diff --git a/config/routes.php b/config/routes.php index 87c9eb3..a553994 100644 --- a/config/routes.php +++ b/config/routes.php @@ -294,6 +294,11 @@ $container[ChannelSendAction::class] = function ($container) { }; $app->post('/channels/send/{sid}/{cid}', ChannelSendAction::class); +$container[ChannelFilesDeleteAction::class] = function ($container) { + return new ChannelFilesDeleteAction($container); +}; +$app->get('/channels/files/delete/{sid}/{cid}', ChannelFilesDeleteAction::class); + // tokens $container[TokensAction::class] = function ($container) { return new TokensAction($container); diff --git a/data/locale/en.yml b/data/locale/en.yml index c456791..21b57fa 100644 --- a/data/locale/en.yml +++ b/data/locale/en.yml @@ -58,7 +58,7 @@ validate_valid_url: "The %field% field is required to be a valid URL." # menu menu.instance: "Instance" menu.servers: "Servers" -menu.logs: "Instance Log" +menu.logs: "Logs" menu.profile: "Profile" menu.servers.info: "Info" @@ -76,7 +76,6 @@ menu.servers.logs: "Log" # titles instance.title: "Instance" servers.title: "Servers" -logs.title: "Latest 100 Log Entries" server_info.title: "Server Info" online.title: "Online Clients" online_info.title: "Online Info" @@ -93,6 +92,9 @@ profile.title: "Profile" tokens.title: "Tokens" snapshots.title: "Snapshots" passwords.title: "Passwords" +instance_logs.title: "Instance log: latest 100 entries" +server_logs.title: "Server log: latest 100 entries" +app_log.title: "Application log" # dynamic render of key value pairs key: "Attribute" @@ -157,16 +159,23 @@ channels.create.parent: "Parent" channel_info.h.files: "Files" channel_info.h.actions: "Actions" channel_info.h.details: "Details" -channel_info.h.clients: "Clients" +channel_info.h.clients: "Current clients" channel_info.send: "Send a message" channel_info.send.message: "Message" channel_info.client: "Client" channel_info.files.delete: "Delete" - +channel_info.files.h.path: "Path" +channel_info.files.h.type: "Type" +channel_info.files.h.size: "Size" +channel_info.files.h.datetime: "Datetime" +channel_info.files.h.delete: "Delete" +channel_info.files.delete.success: "Deleted %file%." # groups groups.delete: "Delete" groups.h.servergroups: "Server Groups" groups.h.channelgroups: "Channel Groups" +groups.servergroup: "Server Group" +groups.channelgroup: "Channel Group" # groups create/copy groups.create: "Create or copy" @@ -262,6 +271,9 @@ snapshots.create: "Create a new snapshot" snapshots.h.details: "Details" snapshots.deploy: "Deploy" snapshots.delete: "Delete" +snapshots.error.create: "An error occurred when creating a snapshot. Please view the application log." +snapshots.error.delete: "An error occurred when deleting a snapshot. Please view the application log." +snapshots.error.deploy: "An error occurred when deploying a snapshot. Please view the application log." # passwords passwords.h.actions: "Actions" @@ -272,10 +284,4 @@ passwords.add.duration: "Duration (in seconds)" passwords.add.description: "Description" passwords.add.channel: "Channel (user joins)" passwords.add.channel_password: "Channelpassword" - -# about -about.header: "About %name%" -about.body: | - ts3web is a web interface for one TeamSpeak 3 Server. It's using serverquery to login. This web interface aims to be as simple as possible. The minimalistic approach is intentional. - - Feel free to submit pull requests if you like to help. More information are here: https://hub.docker.com/r/varakh/ts3web \ No newline at end of file +passwords.channel: "Channel" \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 8ce1622..a3a56f4 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -19,7 +19,8 @@ RUN mkdir -p /var/www/html/application/bin/ \ && chmod -R 777 /var/www/html/application/data/ \ && mkdir -p /var/www/html/application/log/ \ && touch /var/www/html/application/log/application.log \ - && chmod 777 /var/www/html/application/log/application.log + && chmod 777 /var/www/html/application/log/application.log \ + && chown -R www-data:www-data /var/www/html/application # initialize app RUN cd /var/www/html/application/ \ diff --git a/public/index.php b/public/index.php index 999a774..e1270e5 100644 --- a/public/index.php +++ b/public/index.php @@ -10,8 +10,13 @@ error_reporting(E_ALL); */ use Carbon\Carbon; +use JeremyKendall\Slim\Auth\ServiceProvider\SlimAuthProvider; use Slim\Http\Request; use Slim\Http\Response; +use Slim\Middleware\Session; +use Slim\Views\Twig; +use Slim\Views\TwigExtension; +use SlimSession\Helper; // To help the built-in PHP dev server, check if the request was actually for // something which should probably be served as a static file @@ -77,17 +82,17 @@ $container['authAdapter'] = function ($container) { $container['acl'] = function () { return new ACL(); }; -$container->register(new \JeremyKendall\Slim\Auth\ServiceProvider\SlimAuthProvider()); +$container->register(new SlimAuthProvider()); $app->add($app->getContainer()->get('slimAuthRedirectMiddleware')); // session -$app->add(new \Slim\Middleware\Session([ +$app->add(new Session([ 'name' => 'dummy_session', 'autorefresh' => true, 'lifetime' => '1 hour' ])); $container['session'] = function () { - return new \SlimSession\Helper; + return new Helper; }; // view @@ -103,8 +108,8 @@ $container['view'] = function ($container) use ($app) { $themeCacheDir = false; } - $view = new \Slim\Views\Twig($themeDir, ['cache' => $themeCacheDir]); - $view->addExtension(new \Slim\Views\TwigExtension( + $view = new Twig($themeDir, ['cache' => $themeCacheDir]); + $view->addExtension(new TwigExtension( $container['router'], $container['request']->getUri() )); diff --git a/src/Control/Actions/ChannelFilesDeleteAction.php b/src/Control/Actions/ChannelFilesDeleteAction.php new file mode 100644 index 0000000..b9ae881 --- /dev/null +++ b/src/Control/Actions/ChannelFilesDeleteAction.php @@ -0,0 +1,27 @@ +getQueryParams())) { + $file = urldecode($request->getQueryParams()['file']); + } + + $this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']); + $this->ts->getInstance()->selectServer($sid, 'serverId'); + + $files = [$file]; + $this->ts->getInstance()->ftDeleteFile($cid, '', $files); + $this->flash->addMessage('success', $this->translator->trans('channel_info.files.delete.success', ['%file%' => $file])); + + return $response->withRedirect('/channels/' . $sid . '/' . $cid); + } +} \ No newline at end of file diff --git a/src/Control/Actions/ChannelInfoAction.php b/src/Control/Actions/ChannelInfoAction.php index f8c1d05..a9ca161 100644 --- a/src/Control/Actions/ChannelInfoAction.php +++ b/src/Control/Actions/ChannelInfoAction.php @@ -45,12 +45,12 @@ final class ChannelInfoAction extends AbstractAction if (!empty($foundFiles)) { foreach ($foundFiles as $file) { - if ($file['type'] !== "0") { + if ($file['type'] !== "0") { // a file $file['path'] = $path; $files[] = $file; } - if ($file['type'] === "0") { + if ($file['type'] === "0") { // a directory if ($path === '/') { $newPath = $path . $file['name']; @@ -58,6 +58,9 @@ final class ChannelInfoAction extends AbstractAction $newPath = $path . '/' . $file['name']; } + $file['path'] = $path; + $files[] = $file; + $files = $this->getAllFilesIn($sid, $cid, $newPath, $files); } } diff --git a/src/Control/Actions/LogsAction.php b/src/Control/Actions/LogsAction.php index 0247c9b..7270c9b 100644 --- a/src/Control/Actions/LogsAction.php +++ b/src/Control/Actions/LogsAction.php @@ -11,17 +11,21 @@ final class LogsAction extends AbstractAction if (array_key_exists('sid', $args)) $sid = $args['sid']; $this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']); + + $appLog = []; if (empty($sid)) { - $dataResult = $this->ts->getInstance()->logView(100, 1, 1); + $dataResult = $this->ts->getInstance()->logView(getenv(EnvConstants::TEAMSPEAK_LOG_LINES), 1, 1); + $appLog = explode("\n", file_get_contents(BootstrapHelper::getLogFile())); } else { $selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId'); - $dataResult = $this->ts->getInstance()->logView(100, 1, 0); + $dataResult = $this->ts->getInstance()->logView(getenv(EnvConstants::TEAMSPEAK_LOG_LINES), 1, 0); } // render GET $this->view->render($response, 'logs.twig', [ - 'title' => $this->translator->trans('logs.title'), - 'data' => $this->ts->getInstance()->getElement('data', $dataResult), + 'title' => empty($sid) ? $this->translator->trans('instance_logs.title') : $this->translator->trans('server_logs.title'), + 'log' => $this->ts->getInstance()->getElement('data', $dataResult), + 'appLog' => $appLog, ]); } } \ No newline at end of file diff --git a/src/Control/Actions/SnapshotCreateAction.php b/src/Control/Actions/SnapshotCreateAction.php index 6c67dd6..cd2c4eb 100644 --- a/src/Control/Actions/SnapshotCreateAction.php +++ b/src/Control/Actions/SnapshotCreateAction.php @@ -3,6 +3,7 @@ use Carbon\Carbon; use Slim\Http\Request; use Slim\Http\Response; +use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; final class SnapshotCreateAction extends AbstractAction @@ -23,8 +24,13 @@ final class SnapshotCreateAction extends AbstractAction 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')); + try { + $fileSystem->appendToFile($path, trim($snapshotCreateResult['data'])); + $this->flash->addMessage('success', $this->translator->trans('done')); + } catch (IOException $e) { + $this->logger->error('Could not write to ' . $path . '. Cause: ' . $e->getMessage()); + $this->flash->addMessage('error', $this->translator->trans('snapshots.error.create')); + } } return $response->withRedirect('/snapshots/' . $sid); diff --git a/src/Control/Actions/SnapshotDeleteAction.php b/src/Control/Actions/SnapshotDeleteAction.php index a76addc..5d4d981 100644 --- a/src/Control/Actions/SnapshotDeleteAction.php +++ b/src/Control/Actions/SnapshotDeleteAction.php @@ -3,6 +3,7 @@ use Carbon\Carbon; use Slim\Http\Request; use Slim\Http\Response; +use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; final class SnapshotDeleteAction extends AbstractAction @@ -21,15 +22,19 @@ final class SnapshotDeleteAction extends AbstractAction if (!$fileSystem->exists($path)) { $this->flash->addMessage('error', $this->translator->trans('file.notexists')); } else { - $fileSystem->remove($path); + try { + $fileSystem->remove($path); + $serverPath = FileHelper::SNAPSHOTS_PATH . DIRECTORY_SEPARATOR . $sid; - $serverPath = FileHelper::SNAPSHOTS_PATH . DIRECTORY_SEPARATOR . $sid; + if (count(FileHelper::getFiles($serverPath)) == 0) { + $fileSystem->remove($serverPath); + } - if (count(FileHelper::getFiles($serverPath)) == 0) { - $fileSystem->remove($serverPath); + $this->flash->addMessage('success', $this->translator->trans('done')); + } catch (IOException $e) { + $this->logger->error('Could not delete ' . $path . '. Cause: ' . $e->getMessage()); + $this->flash->addMessage('error', $this->translator->trans('snapshots.error.delete')); } - - $this->flash->addMessage('success', $this->translator->trans('done')); } return $response->withRedirect('/snapshots/' . $sid); diff --git a/src/Control/Actions/SnapshotDeployAction.php b/src/Control/Actions/SnapshotDeployAction.php index 123e279..2fe8ea8 100644 --- a/src/Control/Actions/SnapshotDeployAction.php +++ b/src/Control/Actions/SnapshotDeployAction.php @@ -1,6 +1,5 @@ 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')); + try { + $snapshotData = file_get_contents($path); + $this->ts->getInstance()->serverSnapshotDeploy($snapshotData, true); + $this->flash->addMessage('success', $this->translator->trans('done')); + } catch (Exception $e) { + $this->logger->error('Could not deploy ' . $path . '. Cause: ' . $e->getMessage()); + $this->flash->addMessage('error', $this->translator->trans('snapshots.error.deploy')); + } } return $response->withRedirect('/snapshots/' . $sid); diff --git a/src/Util/Auth/TSAuthAdapter.php b/src/Util/Auth/TSAuthAdapter.php index ff8dec2..add8aa6 100644 --- a/src/Util/Auth/TSAuthAdapter.php +++ b/src/Util/Auth/TSAuthAdapter.php @@ -45,7 +45,7 @@ class TSAuthAdapter extends \Zend\Authentication\Adapter\AbstractAdapter $password = $this->getCredential(); if ($this->ts->login($user, $password)) { - $this->logger->info(sprintf('Authenticated as %s', $user)); + $this->logger->debug(sprintf('Authenticated as %s', $user)); $user = ['identity' => $user, 'user' => $user, 'password'=> $password, 'role' => ACL::ACL_DEFAULT_ROLE_ADMIN]; return new Result(Result::SUCCESS, $user, array()); diff --git a/src/Util/BootstrapHelper.php b/src/Util/BootstrapHelper.php index f18ea24..2ea011b 100644 --- a/src/Util/BootstrapHelper.php +++ b/src/Util/BootstrapHelper.php @@ -5,6 +5,8 @@ use Monolog\Handler\ErrorLogHandler; use Monolog\Handler\StreamHandler; use Monolog\Logger; use Monolog\Processor\UidProcessor; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Translation\Loader\YamlFileLoader; use Symfony\Component\Translation\MessageSelector; use Symfony\Component\Translation\Translator; @@ -34,6 +36,7 @@ class BootstrapHelper EnvConstants::TEAMSPEAK_HOST, EnvConstants::TEAMSPEAK_QUERY_PORT, EnvConstants::TEAMSPEAK_USER, + EnvConstants::TEAMSPEAK_LOG_LINES, EnvConstants::LOG_NAME, EnvConstants::LOG_LEVEL ]); @@ -109,9 +112,43 @@ class BootstrapHelper $logger->pushProcessor(new UidProcessor()); $logger->pushHandler(new ErrorLogHandler(NULL, $logLevelTranslated)); - $logPath = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'application.log'; - $logger->pushHandler(new StreamHandler($logPath, $logLevelTranslated)); + $dir = self::getLogDir(); + $path = self::getLogFile(); + + try { + $fileSystem = new Filesystem(); + + if (!$fileSystem->exists($dir)) { + $fileSystem->mkdir($dir); + } + + if (!$fileSystem->exists($path)) { + $fileSystem->touch($path); + } + } catch (IOException $e) { + die('Could not create logger. Cause: ' . $e->getMessage() . '. Trace: ' . $e->getTraceAsString()); + } + + $logger->pushHandler(new StreamHandler($path, $logLevelTranslated)); return $logger; } + + /** + * Returns log dir + * + * @return string + */ + public static function getLogDir() { + return __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'log'; + } + + /** + * Returns log file + * + * @return string + */ + public static function getLogFile() { + return self::getLogDir() . DIRECTORY_SEPARATOR . 'application.log'; + } } \ No newline at end of file diff --git a/src/View/bootstrap4/about.twig b/src/View/bootstrap4/about.twig deleted file mode 100644 index 5dff821..0000000 --- a/src/View/bootstrap4/about.twig +++ /dev/null @@ -1,15 +0,0 @@ - \ No newline at end of file diff --git a/src/View/bootstrap4/bans.twig b/src/View/bootstrap4/bans.twig index 483f2c2..f9a57d0 100644 --- a/src/View/bootstrap4/bans.twig +++ b/src/View/bootstrap4/bans.twig @@ -8,6 +8,10 @@ {'key': 'duration', 'apply': 'timeInSeconds'}, {'key': 'created', 'apply': 'timestamp'} ], + 'hiddenColumns': ['banid', 'name', 'invokercldbid', 'invokeruid'], + 'links': [ + {'key': 'invokername', 'uri': '/clients/' ~ sid, 'uri_param': 'invokercldbid'}, + ], 'additional_links': [ { 'header_label': 'bans.delete'|trans, diff --git a/src/View/bootstrap4/channel_info.twig b/src/View/bootstrap4/channel_info.twig index bd68bfd..5826fba 100644 --- a/src/View/bootstrap4/channel_info.twig +++ b/src/View/bootstrap4/channel_info.twig @@ -39,13 +39,9 @@ {% if files|length > 0 %}
{% trans %}channel_info.h.files{% endtrans %}
- {% include 'table.twig' with {'data': files, - 'filters': [ - {'key': 'datetime', 'apply': 'timestamp'}, - {'key': 'size', 'apply': 'file'}, - ], - 'hiddenColumns': ['cid', 'type'] - } %} + {% if files|length > 0 %} + {% include 'files.twig' with {'data': files} %} + {% endif %} {% endif %}
{% trans %}channel_info.h.details{% endtrans %}
diff --git a/src/View/bootstrap4/channels.twig b/src/View/bootstrap4/channels.twig index 6acde66..a009d86 100644 --- a/src/View/bootstrap4/channels.twig +++ b/src/View/bootstrap4/channels.twig @@ -26,7 +26,8 @@ {% if channels|length > 0 %}
{% trans %}channels.h.details{% endtrans %}
{% include 'table.twig' with {'data': channels, - 'links': [{'key': 'cid', 'uri': '/channels/' ~ sid}], + 'links': [{'key': 'channel_name', 'uri': '/channels/' ~ sid, 'uri_param': 'cid'}, {'key': 'pid', 'uri': '/channels/' ~ sid, 'uri_param': 'pid'}], + 'hiddenColumns': ['cid'], 'additional_links': [ { 'header_label': 'channels.delete'|trans, diff --git a/src/View/bootstrap4/clients.twig b/src/View/bootstrap4/clients.twig index 9aae8ad..362a34a 100644 --- a/src/View/bootstrap4/clients.twig +++ b/src/View/bootstrap4/clients.twig @@ -9,9 +9,9 @@ {'key': 'client_lastconnected', 'apply': 'timestamp'}, ], 'links': [ - {'key': 'client_unique_identifier', 'uri': '/clients/' ~ sid, 'uri_param': 'cldbid'}, + {'key': 'client_nickname', 'uri': '/clients/' ~ sid, 'uri_param': 'cldbid'}, ], - 'hiddenColumns': ['cldbid'], + 'hiddenColumns': ['cldbid', 'client_unique_identifier'], 'additional_links': [ { 'header_label': 'clients.delete'|trans, diff --git a/src/View/bootstrap4/complains.twig b/src/View/bootstrap4/complains.twig index 8dfa5e2..b428016 100644 --- a/src/View/bootstrap4/complains.twig +++ b/src/View/bootstrap4/complains.twig @@ -7,6 +7,11 @@ 'filters': [ {'key': 'timestamp', 'apply': 'timestamp'}, ], + 'hiddenColumns': ['tcldbid', 'fcldbid'], + 'links': [ + {'key': 'tname', 'uri': '/clients/' ~ sid, 'uri_param': 'tcldbid'}, + {'key': 'fname', 'uri': '/clients/' ~ sid, 'uri_param': 'fcldbid'} + ], 'additional_links': [ { 'header_label': 'complains.delete'|trans, diff --git a/src/View/bootstrap4/files.twig b/src/View/bootstrap4/files.twig new file mode 100644 index 0000000..0f0d380 --- /dev/null +++ b/src/View/bootstrap4/files.twig @@ -0,0 +1,42 @@ +
+ + + + + + + + + + + + + {% set total = 0 %} + {% for file in data %} + {% set total = total + file.size %} + {% if file.path is empty %} + {% set path = file.name %} + {% elseif file.path == '/' %} + {% set path = '/' ~ file.name %} + {% else %} + {% set path = file.path ~ '/' ~ file.name %} + {% endif %} + + + + + + + + + {% endfor %} + + + + + + + + +
{% trans %}channel_info.files.h.type{% endtrans %}{% trans %}channel_info.files.h.path{% endtrans %}{% trans %}channel_info.files.h.size{% endtrans %}{% trans %}channel_info.files.h.datetime{% endtrans %}{% trans %}channel_info.files.h.delete{% endtrans %}
{{ file.type == 0 ? '' : '' }}{{ path }}{{ file.size == 0 ? '' : file.size|file }}{{ file.datetime|timestamp }}
{{ total|file }}
+
\ No newline at end of file diff --git a/src/View/bootstrap4/groups.twig b/src/View/bootstrap4/groups.twig index 0d5c267..9e59678 100644 --- a/src/View/bootstrap4/groups.twig +++ b/src/View/bootstrap4/groups.twig @@ -24,9 +24,14 @@ {% if serverGroups|length > 0 %} {% include 'table.twig' with {'data': serverGroups, - 'links': [{'key': 'sgid', 'uri': '/servergroups/' ~ sid}], - 'hiddenColumns': ['savedb', 'sortid', 'namemode', 'n_modifyp', 'n_member_addp', 'n_member_removep'], + 'hiddenColumns': ['sgid', 'savedb', 'sortid', 'namemode', 'n_modifyp', 'n_member_addp', 'n_member_removep'], 'additional_links': [ + { + 'header_label': 'groups.servergroup'|trans, + 'label': '', + 'uri': '/servergroups/' ~ sid, + 'uri_param': 'sgid' + }, { 'header_label': 'groups.delete'|trans, 'label': '', @@ -70,9 +75,14 @@ {% if channelGroups|length > 0 %} {% include 'table.twig' with {'data': channelGroups, - 'links': [{'key': 'cgid', 'uri': '/channelgroups/' ~ sid}], - 'hiddenColumns': ['savedb', 'sortid', 'namemode', 'n_modifyp', 'n_member_addp', 'n_member_removep'], + 'hiddenColumns': ['cgid', 'savedb', 'sortid', 'namemode', 'n_modifyp', 'n_member_addp', 'n_member_removep'], 'additional_links': [ + { + 'header_label': 'groups.channelgroup'|trans, + 'label': '', + 'uri': '/channelgroups/' ~ sid, + 'uri_param': 'cgid' + }, { 'header_label': 'channelgroups.delete'|trans, 'label': '', diff --git a/src/View/bootstrap4/layout.twig b/src/View/bootstrap4/layout.twig index ffe1dba..eb8b227 100644 --- a/src/View/bootstrap4/layout.twig +++ b/src/View/bootstrap4/layout.twig @@ -58,13 +58,10 @@ -{% include 'about.twig' %} - diff --git a/src/View/bootstrap4/login.twig b/src/View/bootstrap4/login.twig index b946efe..76c3757 100644 --- a/src/View/bootstrap4/login.twig +++ b/src/View/bootstrap4/login.twig @@ -4,7 +4,7 @@
-
+
{{ title }}
@@ -12,25 +12,25 @@