Modified channel files, removed about modal
This commit is contained in:
parent
cae20ac477
commit
3bb5e51b30
30 changed files with 292 additions and 103 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,5 +1,15 @@
|
||||||
# CHANGELOG
|
# 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
|
## 2.0.0 - 2019/08/06
|
||||||
* Replace material design with bootstrap4 theme
|
* Replace material design with bootstrap4 theme
|
||||||
* Add an about modal
|
* Add an about modal
|
||||||
|
|
53
README.md
53
README.md
|
@ -1,28 +1,46 @@
|
||||||
# README
|
# README
|
||||||
|
ts3web is a free and open-source web interface for TeamSpeak 3 instances.
|
||||||
|
|
||||||
ts3web is a web interface for one TeamSpeak 3 Server. It's using serverquery to login.
|
The minimalistic approach of this application is intentional.
|
||||||
|
|
||||||
This web interface aims to be as simple as possible. The minimalistic approach is intentional.
|
* Docker images available on https://hub.docker.com/r/varakh/ts3web
|
||||||
|
* Sources are hosted on https://git.myservermanager.com/alexander.schaeferdiek/ts3web
|
||||||
|
|
||||||
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)
|
## Limitations
|
||||||
|
Features which are currently not supported:
|
||||||
Features which are currently **not supported**:
|
|
||||||
|
|
||||||
|
* upload files (only viewing and deleting)
|
||||||
* modify permissions (only viewing)
|
* 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
|
## F.A.Q
|
||||||
TeamSpeak 3 server is a prerequisite (except for the `docker-compose.yml` type which will start also the server if
|
|
||||||
needed).
|
###### 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
|
## Configuration
|
||||||
|
|
||||||
The main configuration file is the `env` file located in `config/`. There's an example file called `env.example`
|
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.
|
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
|
The recommended way is to use docker-compose. The `network_mode = "host"` is required in order to show correct IP
|
||||||
addresses of connected users.
|
addresses of connected users.
|
||||||
|
|
||||||
|
@ -59,6 +77,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./env:/var/www/html/application/config/env
|
- ./env:/var/www/html/application/config/env
|
||||||
- ./snapshots:/var/www/html/application/data/snapshots
|
- ./snapshots:/var/www/html/application/data/snapshots
|
||||||
|
- ./log:/var/www/html/application/log
|
||||||
ports:
|
ports:
|
||||||
- 127.0.0.1:8181:80
|
- 127.0.0.1:8181:80
|
||||||
depends_on:
|
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
|
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.
|
`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.
|
* 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`.
|
* 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**.
|
* 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.
|
* 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.
|
**Prerequisite**: `php`, `composer` and probably `php-fpm` installed on the server.
|
||||||
|
|
||||||
To install:
|
To install:
|
||||||
|
@ -100,7 +116,7 @@ To upgrade:
|
||||||
* `git pull`
|
* `git pull`
|
||||||
* `composer update`
|
* `composer update`
|
||||||
|
|
||||||
## Web server setup
|
### Web server setup
|
||||||
* Example `nginx.conf` for **standalone** deployment without SSL:
|
* 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
|
* Tag the release git commit and create a new release in the VCS web interface
|
||||||
|
|
||||||
### Helpers
|
### 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.
|
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.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -70,6 +70,7 @@ class ACL extends \Zend\Permissions\Acl\Acl
|
||||||
'/channels/edit/{sid}/{cid}',
|
'/channels/edit/{sid}/{cid}',
|
||||||
'/channels/delete/{sid}/{cid}',
|
'/channels/delete/{sid}/{cid}',
|
||||||
'/channels/send/{sid}/{cid}',
|
'/channels/send/{sid}/{cid}',
|
||||||
|
'/channels/files/delete/{sid}/{cid}',
|
||||||
|
|
||||||
'/groups/{sid}',
|
'/groups/{sid}',
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,11 @@ class EnvConstants
|
||||||
*/
|
*/
|
||||||
const TEAMSPEAK_USER = "teamspeak_user";
|
const TEAMSPEAK_USER = "teamspeak_user";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TeamSpeak log lines
|
||||||
|
*/
|
||||||
|
const TEAMSPEAK_LOG_LINES = "teamspeak_log_lines";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log name
|
* Log name
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -12,6 +12,7 @@ teamspeak_host="localhost" # 'localhost' or 'name_of_docker_container' if runnin
|
||||||
teamspeak_query_port=10011
|
teamspeak_query_port=10011
|
||||||
teamspeak_user="serveradmin"
|
teamspeak_user="serveradmin"
|
||||||
teamspeak_tree_view="true" # show a tree view in the details of online clients if a server has been selected
|
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
|
||||||
log_name="ts3web" # values: all strings
|
log_name="ts3web" # values: all strings
|
||||||
|
|
|
@ -294,6 +294,11 @@ $container[ChannelSendAction::class] = function ($container) {
|
||||||
};
|
};
|
||||||
$app->post('/channels/send/{sid}/{cid}', ChannelSendAction::class);
|
$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
|
// tokens
|
||||||
$container[TokensAction::class] = function ($container) {
|
$container[TokensAction::class] = function ($container) {
|
||||||
return new TokensAction($container);
|
return new TokensAction($container);
|
||||||
|
|
|
@ -58,7 +58,7 @@ validate_valid_url: "The %field% field is required to be a valid URL."
|
||||||
# menu
|
# menu
|
||||||
menu.instance: "Instance"
|
menu.instance: "Instance"
|
||||||
menu.servers: "Servers"
|
menu.servers: "Servers"
|
||||||
menu.logs: "Instance Log"
|
menu.logs: "Logs"
|
||||||
menu.profile: "Profile"
|
menu.profile: "Profile"
|
||||||
|
|
||||||
menu.servers.info: "Info"
|
menu.servers.info: "Info"
|
||||||
|
@ -76,7 +76,6 @@ menu.servers.logs: "Log"
|
||||||
# titles
|
# titles
|
||||||
instance.title: "Instance"
|
instance.title: "Instance"
|
||||||
servers.title: "Servers"
|
servers.title: "Servers"
|
||||||
logs.title: "Latest 100 Log Entries"
|
|
||||||
server_info.title: "Server Info"
|
server_info.title: "Server Info"
|
||||||
online.title: "Online Clients"
|
online.title: "Online Clients"
|
||||||
online_info.title: "Online Info"
|
online_info.title: "Online Info"
|
||||||
|
@ -93,6 +92,9 @@ profile.title: "Profile"
|
||||||
tokens.title: "Tokens"
|
tokens.title: "Tokens"
|
||||||
snapshots.title: "Snapshots"
|
snapshots.title: "Snapshots"
|
||||||
passwords.title: "Passwords"
|
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
|
# dynamic render of key value pairs
|
||||||
key: "Attribute"
|
key: "Attribute"
|
||||||
|
@ -157,16 +159,23 @@ channels.create.parent: "Parent"
|
||||||
channel_info.h.files: "Files"
|
channel_info.h.files: "Files"
|
||||||
channel_info.h.actions: "Actions"
|
channel_info.h.actions: "Actions"
|
||||||
channel_info.h.details: "Details"
|
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: "Send a message"
|
||||||
channel_info.send.message: "Message"
|
channel_info.send.message: "Message"
|
||||||
channel_info.client: "Client"
|
channel_info.client: "Client"
|
||||||
channel_info.files.delete: "Delete"
|
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
|
||||||
groups.delete: "Delete"
|
groups.delete: "Delete"
|
||||||
groups.h.servergroups: "Server Groups"
|
groups.h.servergroups: "Server Groups"
|
||||||
groups.h.channelgroups: "Channel Groups"
|
groups.h.channelgroups: "Channel Groups"
|
||||||
|
groups.servergroup: "Server Group"
|
||||||
|
groups.channelgroup: "Channel Group"
|
||||||
|
|
||||||
# groups create/copy
|
# groups create/copy
|
||||||
groups.create: "Create or copy"
|
groups.create: "Create or copy"
|
||||||
|
@ -262,6 +271,9 @@ snapshots.create: "Create a new snapshot"
|
||||||
snapshots.h.details: "Details"
|
snapshots.h.details: "Details"
|
||||||
snapshots.deploy: "Deploy"
|
snapshots.deploy: "Deploy"
|
||||||
snapshots.delete: "Delete"
|
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
|
||||||
passwords.h.actions: "Actions"
|
passwords.h.actions: "Actions"
|
||||||
|
@ -272,10 +284,4 @@ passwords.add.duration: "Duration (in seconds)"
|
||||||
passwords.add.description: "Description"
|
passwords.add.description: "Description"
|
||||||
passwords.add.channel: "Channel (user joins)"
|
passwords.add.channel: "Channel (user joins)"
|
||||||
passwords.add.channel_password: "Channelpassword"
|
passwords.add.channel_password: "Channelpassword"
|
||||||
|
passwords.channel: "Channel"
|
||||||
# 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
|
|
|
@ -19,7 +19,8 @@ RUN mkdir -p /var/www/html/application/bin/ \
|
||||||
&& chmod -R 777 /var/www/html/application/data/ \
|
&& chmod -R 777 /var/www/html/application/data/ \
|
||||||
&& mkdir -p /var/www/html/application/log/ \
|
&& mkdir -p /var/www/html/application/log/ \
|
||||||
&& touch /var/www/html/application/log/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
|
# initialize app
|
||||||
RUN cd /var/www/html/application/ \
|
RUN cd /var/www/html/application/ \
|
||||||
|
|
|
@ -10,8 +10,13 @@ error_reporting(E_ALL);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use JeremyKendall\Slim\Auth\ServiceProvider\SlimAuthProvider;
|
||||||
use Slim\Http\Request;
|
use Slim\Http\Request;
|
||||||
use Slim\Http\Response;
|
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
|
// 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
|
// something which should probably be served as a static file
|
||||||
|
@ -77,17 +82,17 @@ $container['authAdapter'] = function ($container) {
|
||||||
$container['acl'] = function () {
|
$container['acl'] = function () {
|
||||||
return new ACL();
|
return new ACL();
|
||||||
};
|
};
|
||||||
$container->register(new \JeremyKendall\Slim\Auth\ServiceProvider\SlimAuthProvider());
|
$container->register(new SlimAuthProvider());
|
||||||
$app->add($app->getContainer()->get('slimAuthRedirectMiddleware'));
|
$app->add($app->getContainer()->get('slimAuthRedirectMiddleware'));
|
||||||
|
|
||||||
// session
|
// session
|
||||||
$app->add(new \Slim\Middleware\Session([
|
$app->add(new Session([
|
||||||
'name' => 'dummy_session',
|
'name' => 'dummy_session',
|
||||||
'autorefresh' => true,
|
'autorefresh' => true,
|
||||||
'lifetime' => '1 hour'
|
'lifetime' => '1 hour'
|
||||||
]));
|
]));
|
||||||
$container['session'] = function () {
|
$container['session'] = function () {
|
||||||
return new \SlimSession\Helper;
|
return new Helper;
|
||||||
};
|
};
|
||||||
|
|
||||||
// view
|
// view
|
||||||
|
@ -103,8 +108,8 @@ $container['view'] = function ($container) use ($app) {
|
||||||
$themeCacheDir = false;
|
$themeCacheDir = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$view = new \Slim\Views\Twig($themeDir, ['cache' => $themeCacheDir]);
|
$view = new Twig($themeDir, ['cache' => $themeCacheDir]);
|
||||||
$view->addExtension(new \Slim\Views\TwigExtension(
|
$view->addExtension(new TwigExtension(
|
||||||
$container['router'],
|
$container['router'],
|
||||||
$container['request']->getUri()
|
$container['request']->getUri()
|
||||||
));
|
));
|
||||||
|
|
27
src/Control/Actions/ChannelFilesDeleteAction.php
Normal file
27
src/Control/Actions/ChannelFilesDeleteAction.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
final class ChannelFilesDeleteAction extends AbstractAction
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$sid = $args['sid'];
|
||||||
|
$cid = $args['cid'];
|
||||||
|
$file = null;
|
||||||
|
|
||||||
|
if (array_key_exists('file', $request->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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,12 +45,12 @@ final class ChannelInfoAction extends AbstractAction
|
||||||
if (!empty($foundFiles)) {
|
if (!empty($foundFiles)) {
|
||||||
foreach ($foundFiles as $file) {
|
foreach ($foundFiles as $file) {
|
||||||
|
|
||||||
if ($file['type'] !== "0") {
|
if ($file['type'] !== "0") { // a file
|
||||||
$file['path'] = $path;
|
$file['path'] = $path;
|
||||||
$files[] = $file;
|
$files[] = $file;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($file['type'] === "0") {
|
if ($file['type'] === "0") { // a directory
|
||||||
|
|
||||||
if ($path === '/') {
|
if ($path === '/') {
|
||||||
$newPath = $path . $file['name'];
|
$newPath = $path . $file['name'];
|
||||||
|
@ -58,6 +58,9 @@ final class ChannelInfoAction extends AbstractAction
|
||||||
$newPath = $path . '/' . $file['name'];
|
$newPath = $path . '/' . $file['name'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$file['path'] = $path;
|
||||||
|
$files[] = $file;
|
||||||
|
|
||||||
$files = $this->getAllFilesIn($sid, $cid, $newPath, $files);
|
$files = $this->getAllFilesIn($sid, $cid, $newPath, $files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,17 +11,21 @@ final class LogsAction extends AbstractAction
|
||||||
if (array_key_exists('sid', $args)) $sid = $args['sid'];
|
if (array_key_exists('sid', $args)) $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']);
|
||||||
|
|
||||||
|
$appLog = [];
|
||||||
if (empty($sid)) {
|
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 {
|
} else {
|
||||||
$selectResult = $this->ts->getInstance()->selectServer($sid, 'serverId');
|
$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
|
// render GET
|
||||||
$this->view->render($response, 'logs.twig', [
|
$this->view->render($response, 'logs.twig', [
|
||||||
'title' => $this->translator->trans('logs.title'),
|
'title' => empty($sid) ? $this->translator->trans('instance_logs.title') : $this->translator->trans('server_logs.title'),
|
||||||
'data' => $this->ts->getInstance()->getElement('data', $dataResult),
|
'log' => $this->ts->getInstance()->getElement('data', $dataResult),
|
||||||
|
'appLog' => $appLog,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Slim\Http\Request;
|
use Slim\Http\Request;
|
||||||
use Slim\Http\Response;
|
use Slim\Http\Response;
|
||||||
|
use Symfony\Component\Filesystem\Exception\IOException;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
final class SnapshotCreateAction extends AbstractAction
|
final class SnapshotCreateAction extends AbstractAction
|
||||||
|
@ -23,8 +24,13 @@ final class SnapshotCreateAction extends AbstractAction
|
||||||
if ($fileSystem->exists($path)) {
|
if ($fileSystem->exists($path)) {
|
||||||
$this->flash->addMessage('error', $this->translator->trans('file.exists'));
|
$this->flash->addMessage('error', $this->translator->trans('file.exists'));
|
||||||
} else {
|
} else {
|
||||||
|
try {
|
||||||
$fileSystem->appendToFile($path, trim($snapshotCreateResult['data']));
|
$fileSystem->appendToFile($path, trim($snapshotCreateResult['data']));
|
||||||
$this->flash->addMessage('success', $this->translator->trans('done'));
|
$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);
|
return $response->withRedirect('/snapshots/' . $sid);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Slim\Http\Request;
|
use Slim\Http\Request;
|
||||||
use Slim\Http\Response;
|
use Slim\Http\Response;
|
||||||
|
use Symfony\Component\Filesystem\Exception\IOException;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
final class SnapshotDeleteAction extends AbstractAction
|
final class SnapshotDeleteAction extends AbstractAction
|
||||||
|
@ -21,8 +22,8 @@ final class SnapshotDeleteAction extends AbstractAction
|
||||||
if (!$fileSystem->exists($path)) {
|
if (!$fileSystem->exists($path)) {
|
||||||
$this->flash->addMessage('error', $this->translator->trans('file.notexists'));
|
$this->flash->addMessage('error', $this->translator->trans('file.notexists'));
|
||||||
} else {
|
} else {
|
||||||
|
try {
|
||||||
$fileSystem->remove($path);
|
$fileSystem->remove($path);
|
||||||
|
|
||||||
$serverPath = FileHelper::SNAPSHOTS_PATH . DIRECTORY_SEPARATOR . $sid;
|
$serverPath = FileHelper::SNAPSHOTS_PATH . DIRECTORY_SEPARATOR . $sid;
|
||||||
|
|
||||||
if (count(FileHelper::getFiles($serverPath)) == 0) {
|
if (count(FileHelper::getFiles($serverPath)) == 0) {
|
||||||
|
@ -30,6 +31,10 @@ final class SnapshotDeleteAction extends AbstractAction
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->flash->addMessage('success', $this->translator->trans('done'));
|
$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'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $response->withRedirect('/snapshots/' . $sid);
|
return $response->withRedirect('/snapshots/' . $sid);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Slim\Http\Request;
|
use Slim\Http\Request;
|
||||||
use Slim\Http\Response;
|
use Slim\Http\Response;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
@ -21,9 +20,14 @@ final class SnapshotDeployAction extends AbstractAction
|
||||||
if (!$fileSystem->exists($path)) {
|
if (!$fileSystem->exists($path)) {
|
||||||
$this->flash->addMessage('error', $this->translator->trans('file.notexists'));
|
$this->flash->addMessage('error', $this->translator->trans('file.notexists'));
|
||||||
} else {
|
} else {
|
||||||
|
try {
|
||||||
$snapshotData = file_get_contents($path);
|
$snapshotData = file_get_contents($path);
|
||||||
$this->ts->getInstance()->serverSnapshotDeploy($snapshotData, true);
|
$this->ts->getInstance()->serverSnapshotDeploy($snapshotData, true);
|
||||||
$this->flash->addMessage('success', $this->translator->trans('done'));
|
$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);
|
return $response->withRedirect('/snapshots/' . $sid);
|
||||||
|
|
|
@ -45,7 +45,7 @@ class TSAuthAdapter extends \Zend\Authentication\Adapter\AbstractAdapter
|
||||||
$password = $this->getCredential();
|
$password = $this->getCredential();
|
||||||
|
|
||||||
if ($this->ts->login($user, $password)) {
|
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];
|
$user = ['identity' => $user, 'user' => $user, 'password'=> $password, 'role' => ACL::ACL_DEFAULT_ROLE_ADMIN];
|
||||||
return new Result(Result::SUCCESS, $user, array());
|
return new Result(Result::SUCCESS, $user, array());
|
||||||
|
|
|
@ -5,6 +5,8 @@ use Monolog\Handler\ErrorLogHandler;
|
||||||
use Monolog\Handler\StreamHandler;
|
use Monolog\Handler\StreamHandler;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use Monolog\Processor\UidProcessor;
|
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\Loader\YamlFileLoader;
|
||||||
use Symfony\Component\Translation\MessageSelector;
|
use Symfony\Component\Translation\MessageSelector;
|
||||||
use Symfony\Component\Translation\Translator;
|
use Symfony\Component\Translation\Translator;
|
||||||
|
@ -34,6 +36,7 @@ class BootstrapHelper
|
||||||
EnvConstants::TEAMSPEAK_HOST,
|
EnvConstants::TEAMSPEAK_HOST,
|
||||||
EnvConstants::TEAMSPEAK_QUERY_PORT,
|
EnvConstants::TEAMSPEAK_QUERY_PORT,
|
||||||
EnvConstants::TEAMSPEAK_USER,
|
EnvConstants::TEAMSPEAK_USER,
|
||||||
|
EnvConstants::TEAMSPEAK_LOG_LINES,
|
||||||
EnvConstants::LOG_NAME,
|
EnvConstants::LOG_NAME,
|
||||||
EnvConstants::LOG_LEVEL
|
EnvConstants::LOG_LEVEL
|
||||||
]);
|
]);
|
||||||
|
@ -109,9 +112,43 @@ class BootstrapHelper
|
||||||
$logger->pushProcessor(new UidProcessor());
|
$logger->pushProcessor(new UidProcessor());
|
||||||
$logger->pushHandler(new ErrorLogHandler(NULL, $logLevelTranslated));
|
$logger->pushHandler(new ErrorLogHandler(NULL, $logLevelTranslated));
|
||||||
|
|
||||||
$logPath = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'application.log';
|
$dir = self::getLogDir();
|
||||||
$logger->pushHandler(new StreamHandler($logPath, $logLevelTranslated));
|
$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;
|
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';
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
<div class="modal" id="aboutModal">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
|
|
||||||
<div class="modal-header">
|
|
||||||
<h4 class="modal-title">{% trans with {'%name%': getenv('site_title') } %}about.header{% endtrans %}</h4>
|
|
||||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-body">
|
|
||||||
{{ 'about.body'|trans|nl2br }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -8,6 +8,10 @@
|
||||||
{'key': 'duration', 'apply': 'timeInSeconds'},
|
{'key': 'duration', 'apply': 'timeInSeconds'},
|
||||||
{'key': 'created', 'apply': 'timestamp'}
|
{'key': 'created', 'apply': 'timestamp'}
|
||||||
],
|
],
|
||||||
|
'hiddenColumns': ['banid', 'name', 'invokercldbid', 'invokeruid'],
|
||||||
|
'links': [
|
||||||
|
{'key': 'invokername', 'uri': '/clients/' ~ sid, 'uri_param': 'invokercldbid'},
|
||||||
|
],
|
||||||
'additional_links': [
|
'additional_links': [
|
||||||
{
|
{
|
||||||
'header_label': 'bans.delete'|trans,
|
'header_label': 'bans.delete'|trans,
|
||||||
|
|
|
@ -39,13 +39,9 @@
|
||||||
|
|
||||||
{% if files|length > 0 %}
|
{% if files|length > 0 %}
|
||||||
<h5>{% trans %}channel_info.h.files{% endtrans %}</h5>
|
<h5>{% trans %}channel_info.h.files{% endtrans %}</h5>
|
||||||
{% include 'table.twig' with {'data': files,
|
{% if files|length > 0 %}
|
||||||
'filters': [
|
{% include 'files.twig' with {'data': files} %}
|
||||||
{'key': 'datetime', 'apply': 'timestamp'},
|
{% endif %}
|
||||||
{'key': 'size', 'apply': 'file'},
|
|
||||||
],
|
|
||||||
'hiddenColumns': ['cid', 'type']
|
|
||||||
} %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h5>{% trans %}channel_info.h.details{% endtrans %}</h5>
|
<h5>{% trans %}channel_info.h.details{% endtrans %}</h5>
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
{% if channels|length > 0 %}
|
{% if channels|length > 0 %}
|
||||||
<h5>{% trans %}channels.h.details{% endtrans %}</h5>
|
<h5>{% trans %}channels.h.details{% endtrans %}</h5>
|
||||||
{% include 'table.twig' with {'data': channels,
|
{% 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': [
|
'additional_links': [
|
||||||
{
|
{
|
||||||
'header_label': 'channels.delete'|trans,
|
'header_label': 'channels.delete'|trans,
|
||||||
|
|
|
@ -9,9 +9,9 @@
|
||||||
{'key': 'client_lastconnected', 'apply': 'timestamp'},
|
{'key': 'client_lastconnected', 'apply': 'timestamp'},
|
||||||
],
|
],
|
||||||
'links': [
|
'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': [
|
'additional_links': [
|
||||||
{
|
{
|
||||||
'header_label': 'clients.delete'|trans,
|
'header_label': 'clients.delete'|trans,
|
||||||
|
|
|
@ -7,6 +7,11 @@
|
||||||
'filters': [
|
'filters': [
|
||||||
{'key': 'timestamp', 'apply': 'timestamp'},
|
{'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': [
|
'additional_links': [
|
||||||
{
|
{
|
||||||
'header_label': 'complains.delete'|trans,
|
'header_label': 'complains.delete'|trans,
|
||||||
|
|
42
src/View/bootstrap4/files.twig
Normal file
42
src/View/bootstrap4/files.twig
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm small sortable-bootstrap table-striped table-bordered" data-sortable>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{% trans %}channel_info.files.h.type{% endtrans %}</th>
|
||||||
|
<th scope="col">{% trans %}channel_info.files.h.path{% endtrans %}</th>
|
||||||
|
<th scope="col">{% trans %}channel_info.files.h.size{% endtrans %}</th>
|
||||||
|
<th scope="col">{% trans %}channel_info.files.h.datetime{% endtrans %}</th>
|
||||||
|
<th scope="col">{% trans %}channel_info.files.h.delete{% endtrans %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
{% 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 %}
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>{{ file.type == 0 ? '<i class="fa fa-folder"></i>' : '<i class="fa fa-file-o"></i>' }}</td>
|
||||||
|
<td>{{ path }}</td>
|
||||||
|
<td>{{ file.size == 0 ? '' : file.size|file }}</td>
|
||||||
|
<td>{{ file.datetime|timestamp }}</td>
|
||||||
|
<td><a href="/channels/files/delete/{{ sid }}/{{ cid }}?file={{ path }}"><i class="fa fa-trash"></i></a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td class="font-weight-bold">{{ total|file }}</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
|
@ -24,9 +24,14 @@
|
||||||
|
|
||||||
{% if serverGroups|length > 0 %}
|
{% if serverGroups|length > 0 %}
|
||||||
{% include 'table.twig' with {'data': serverGroups,
|
{% include 'table.twig' with {'data': serverGroups,
|
||||||
'links': [{'key': 'sgid', 'uri': '/servergroups/' ~ sid}],
|
'hiddenColumns': ['sgid', 'savedb', 'sortid', 'namemode', 'n_modifyp', 'n_member_addp', 'n_member_removep'],
|
||||||
'hiddenColumns': ['savedb', 'sortid', 'namemode', 'n_modifyp', 'n_member_addp', 'n_member_removep'],
|
|
||||||
'additional_links': [
|
'additional_links': [
|
||||||
|
{
|
||||||
|
'header_label': 'groups.servergroup'|trans,
|
||||||
|
'label': '<i class="fa fa-info"></i>',
|
||||||
|
'uri': '/servergroups/' ~ sid,
|
||||||
|
'uri_param': 'sgid'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'header_label': 'groups.delete'|trans,
|
'header_label': 'groups.delete'|trans,
|
||||||
'label': '<i class="fa fa-trash"></i>',
|
'label': '<i class="fa fa-trash"></i>',
|
||||||
|
@ -70,9 +75,14 @@
|
||||||
|
|
||||||
{% if channelGroups|length > 0 %}
|
{% if channelGroups|length > 0 %}
|
||||||
{% include 'table.twig' with {'data': channelGroups,
|
{% include 'table.twig' with {'data': channelGroups,
|
||||||
'links': [{'key': 'cgid', 'uri': '/channelgroups/' ~ sid}],
|
'hiddenColumns': ['cgid', 'savedb', 'sortid', 'namemode', 'n_modifyp', 'n_member_addp', 'n_member_removep'],
|
||||||
'hiddenColumns': ['savedb', 'sortid', 'namemode', 'n_modifyp', 'n_member_addp', 'n_member_removep'],
|
|
||||||
'additional_links': [
|
'additional_links': [
|
||||||
|
{
|
||||||
|
'header_label': 'groups.channelgroup'|trans,
|
||||||
|
'label': '<i class="fa fa-info"></i>',
|
||||||
|
'uri': '/channelgroups/' ~ sid,
|
||||||
|
'uri_param': 'cgid'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'header_label': 'channelgroups.delete'|trans,
|
'header_label': 'channelgroups.delete'|trans,
|
||||||
'label': '<i class="fa fa-trash"></i>',
|
'label': '<i class="fa fa-trash"></i>',
|
||||||
|
|
|
@ -58,13 +58,10 @@
|
||||||
</main><!-- /.container -->
|
</main><!-- /.container -->
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
{% include 'about.twig' %}
|
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
<i class="fa fa-copyright"></i> {{ "now"|date("Y") }} {{ getenv('site_title') }} |
|
<i class="fa fa-copyright"></i> {{ "now"|date("Y") }} {{ getenv('site_title') }}
|
||||||
<a data-toggle="modal" data-target="#aboutModal"><i class="fa fa-question"></i></a>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<div class="container-fluid h-100">
|
<div class="container-fluid h-100">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-8">
|
<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">{{ title }}</div>
|
<div class="card-header">{{ title }}</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
@ -12,25 +12,25 @@
|
||||||
<form class="form-signin" role="form" name="login" id="login" method="post">
|
<form class="form-signin" role="form" name="login" id="login" method="post">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="host" class="col-4 col-form-label">{% trans %}login.form.host{% endtrans %}</label>
|
<label for="host" class="sr-only">{% trans %}login.form.host{% endtrans %}</label>
|
||||||
<input type="text" id="host" name="host" class="form-control form-control-lg"
|
<input type="text" id="host" name="host" class="form-control form-control-sm"
|
||||||
placeholder="{{ getenv('teamspeak_host') ~ ':' ~ getenv('teamspeak_query_port') }}"
|
placeholder="{{ getenv('teamspeak_host') ~ ':' ~ getenv('teamspeak_query_port') }}"
|
||||||
value="{{ getenv('teamspeak_host') ~ ':' ~ getenv('teamspeak_query_port') }}"
|
value="{{ getenv('teamspeak_host') ~ ':' ~ getenv('teamspeak_query_port') }}"
|
||||||
required disabled>
|
required disabled>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username" class="col-4 col-form-label">{% trans %}login.form.username{% endtrans %}</label>
|
<label for="username" class="sr-only">{% trans %}login.form.username{% endtrans %}</label>
|
||||||
<input type="text" id="username" name="username" class="form-control form-control-lg"
|
<input type="text" id="username" name="username" class="form-control form-control-sm"
|
||||||
placeholder="{{ getenv('teamspeak_user') }}"
|
placeholder="{{ getenv('teamspeak_user') }}"
|
||||||
value="{{ getenv('teamspeak_user') }}"
|
value="{{ getenv('teamspeak_user') }}"
|
||||||
required>
|
required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password" class="col-4 col-form-label">{% trans %}login.form.password{% endtrans %}</label>
|
<label for="password" class="sr-only">{% trans %}login.form.password{% endtrans %}</label>
|
||||||
<input type="password" id="password" name="password"
|
<input type="password" id="password" name="password"
|
||||||
class="form-control form-control-lg"
|
class="form-control form-control-sm"
|
||||||
placeholder="{% trans %}login.form.password.placeholder{% endtrans %}" autofocus required>
|
placeholder="{% trans %}login.form.password.placeholder{% endtrans %}" autofocus required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
{% if log|length > 0 %}
|
{% if log|length > 0 %}
|
||||||
|
|
||||||
<div class="small">
|
<div class="small">
|
||||||
{% for log in data %}
|
{% for line in log %}
|
||||||
<code>{{ log.l }}</code>
|
<code>{{ line.l }}</code><br/>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -15,4 +15,13 @@
|
||||||
{% include 'no_entities.twig' %}
|
{% include 'no_entities.twig' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if appLog|length > 0 %}
|
||||||
|
<br />
|
||||||
|
<h3>{% trans %}app_log.title{% endtrans %}</h3>
|
||||||
|
<div class="small">
|
||||||
|
{% for line in appLog %}
|
||||||
|
<code>{{ line }}</code><br/>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -27,10 +27,15 @@
|
||||||
{% if data|length > 0 %}
|
{% if data|length > 0 %}
|
||||||
<h5>{% trans %}passwords.h.details{% endtrans %}</h5>
|
<h5>{% trans %}passwords.h.details{% endtrans %}</h5>
|
||||||
{% include 'table.twig' with {'data': data,
|
{% include 'table.twig' with {'data': data,
|
||||||
'hiddenColumns': ['uid', 'tcpw'],
|
'hiddenColumns': ['tcid', 'uid', 'tcpw'],
|
||||||
'filters': [{'key': 'start', 'apply': 'timestamp'}, {'key': 'end', 'apply': 'timestamp'}],
|
'filters': [{'key': 'start', 'apply': 'timestamp'}, {'key': 'end', 'apply': 'timestamp'}],
|
||||||
'links': [
|
'additional_links': [
|
||||||
{'key': 'tcid', 'uri': '/channels/' ~ sid, 'uri_param': 'tcid'},
|
{
|
||||||
|
'header_label': 'passwords.channel'|trans,
|
||||||
|
'label': '<i class="fa fa-tv"></i>',
|
||||||
|
'uri': '/channels/' ~ sid,
|
||||||
|
'uri_param': 'tcid'
|
||||||
|
}
|
||||||
],
|
],
|
||||||
'attributesEditable': [
|
'attributesEditable': [
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
'links': [
|
'links': [
|
||||||
{'key': 'client_nickname', 'uri': '/clients/' ~ sid, 'uri_param': 'cldbid'}
|
{'key': 'client_nickname', 'uri': '/clients/' ~ sid, 'uri_param': 'cldbid'}
|
||||||
],
|
],
|
||||||
'hiddenColumns': ['cldbid'],
|
'hiddenColumns': ['cldbid', 'client_unique_identifier'],
|
||||||
'additional_links': [
|
'additional_links': [
|
||||||
{
|
{
|
||||||
'header_label': 'servergroup_info.remove'|trans,
|
'header_label': 'servergroup_info.remove'|trans,
|
||||||
|
|
Reference in a new issue