Archived
1
0
Fork 0

Use 4 spaces as tabs and reformat

This commit is contained in:
Varakh 2021-05-21 01:05:38 +02:00
parent df2d9520ca
commit 41234b34bf
34 changed files with 509 additions and 421 deletions

6
.editorconfig Normal file
View file

@ -0,0 +1,6 @@
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4

View file

@ -3,18 +3,22 @@
## 2.2.4 - UNRELEASED ## 2.2.4 - UNRELEASED
## 2.2.3 - 2021/01/08 ## 2.2.3 - 2021/01/08
* Change docker base to alpine * Change docker base to alpine
## 2.2.2 - 2020/03/22 ## 2.2.2 - 2020/03/22
* Stop auto-sorting tables * Stop auto-sorting tables
* Add bandwidth formatter * Add bandwidth formatter
* Check PHP 7.4 compatibility * Check PHP 7.4 compatibility
* Increase docker image base to PHP 7.4 * Increase docker image base to PHP 7.4
## 2.2.1 - 2019/11/10 ## 2.2.1 - 2019/11/10
* Use separate JavaScript file to initialize DataTables * Use separate JavaScript file to initialize DataTables
## 2.2.0 - 2019/11/10 ## 2.2.0 - 2019/11/10
* Add version tag to footer * Add version tag to footer
* Add sortable tables * Add sortable tables
* Add search on tables * Add search on tables
@ -23,32 +27,38 @@
* Fix dependency version * Fix dependency version
## 2.1.4 - 2019/11/08 ## 2.1.4 - 2019/11/08
* Use autofocus on username input field instead of the password field * Use autofocus on username input field instead of the password field
* Fill missing cells on incorrect cell count in table views when only partial data is available * Fill missing cells on incorrect cell count in table views when only partial data is available
## 2.1.3 - 2019/08/08 ## 2.1.3 - 2019/08/08
* Fixed false rendering of forms * Fixed false rendering of forms
* Fixed channel tree view showing the wrong virtual server after selection * Fixed channel tree view showing the wrong virtual server after selection
* Minor code refactor * Minor code refactor
## 2.1.2 - 2019/08/07 ## 2.1.2 - 2019/08/07
* Minor refactoring * Minor refactoring
* Update documentation * Update documentation
## 2.1.1 - 2019/08/07 ## 2.1.1 - 2019/08/07
* Updated translation * Updated translation
## 2.1.0 - 2019/08/07 ## 2.1.0 - 2019/08/07
* Fixed file handling on snapshots * Fixed file handling on snapshots
* Cleaned up template links * Cleaned up template links
* Updated documentation * Updated documentation
* Added application log view * Added application log view
* Fixed files view * Fixed files view
* Added show total used space for files in a channel * Added show total used space for files in a channel
* Added file delete action * Added file delete action
* Removed about modal * 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
* Add tree view for online clients * Add tree view for online clients
@ -56,34 +66,45 @@
* Update dependencies and force PHP 7.3 * Update dependencies and force PHP 7.3
## 1.2.4 - 2019/01/18 ## 1.2.4 - 2019/01/18
* No info text * No info text
## 1.2.3 - 2019/01/18 ## 1.2.3 - 2019/01/18
* No info text * No info text
## 1.2.2 - 2018/09/01 ## 1.2.2 - 2018/09/01
* No info text * No info text
## 1.2.1 - 2018/06/04 ## 1.2.1 - 2018/06/04
* No info text * No info text
## 1.2.0 - 2018/06/04 ## 1.2.0 - 2018/06/04
* No info text * No info text
## 1.1.1 - 2018/05/04 ## 1.1.1 - 2018/05/04
* No info text * No info text
## 1.1.0 - 2018/05/04 ## 1.1.0 - 2018/05/04
* No info text * No info text
## 1.0.3 - 2018/04/04 ## 1.0.3 - 2018/04/04
* No info text * No info text
## 1.0.2 - 2018/04/04 ## 1.0.2 - 2018/04/04
* No info text * No info text
## 1.0.1 - 2018/04/03 ## 1.0.1 - 2018/04/03
* No info text * No info text
## 1.0.0 - 2018/04/03 ## 1.0.0 - 2018/04/03
* No info text
* No info text

100
README.md
View file

@ -1,45 +1,54 @@
# README # README
ts3web is a free and open-source web interface for TeamSpeak 3 instances. ts3web is a free and open-source web interface for TeamSpeak 3 instances.
The minimalistic approach of this application is intentional. The minimalistic approach of this application is intentional.
* Docker images available on [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](https://hub.docker.com/r/varakh/ts3web)
* Sources are hosted on [https://github.com/v4rakh/ts3web](https://github.com/v4rakh/ts3web) * Sources are hosted on [https://github.com/v4rakh/ts3web](https://github.com/v4rakh/ts3web)
There are many TeamSpeak 3 web interfaces out. Why should I pick ts3web? There are many TeamSpeak 3 web interfaces out. Why should I pick ts3web? Free, simple, stateless, easy to extend,
Free, simple, stateless, easy to extend, standard bootstrap theme. standard bootstrap theme.
## F.A.Q ## F.A.Q
Questions? Here you'll hopefully get the answer. Feel free to read before starting. Questions? Here you'll hopefully get the answer. Feel free to read before starting.
<a name="flood"></a> <a name="flood"></a>
###### I always get `flood client` message when clicking anywhere in the web interface. ###### I always get `flood client` message when clicking anywhere in the web interface.
ts3web makes heavy use of query commands. When your instance is up and running, you should be able to change ts3web makes heavy use of query commands. When your instance is up and running, you should be able to change
`serverinstance_serverquery_flood_commands` to a high value, e.g. `100` and `serverinstance_serverquery_flood_time` to `serverinstance_serverquery_flood_commands` to a high value, e.g. `100` and `serverinstance_serverquery_flood_time` to
`1` which is enough. `1` which is enough.
<a name="whitelist"></a> <a name="whitelist"></a>
###### I always get `TSException: Error: host isn't a ts3 instance!` when selecting a server. ###### 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](#whitelisttxtexample)
You probably got query banned from your server. You need to properly define
your [`whitelist.txt` file](#whitelisttxtexample)
and include it in your TeamSpeak application. and include it in your TeamSpeak application.
<a name="dockerperms"></a> <a name="dockerperms"></a>
###### I always get `no write permissions` or something similar when trying to save snapshots or when a log entry is created. ###### I always get `no write permissions` or something similar when trying to save snapshots or when a log entry is created.
This probably happens when you're in the docker setup. Ensure that host binds have permissions set up properly.
The user which is used in the docker container is `nobody` with id `65534`. If, e.g. logs are host bound, then execute This probably happens when you're in the docker setup. Ensure that host binds have permissions set up properly. The user
which is used in the docker container is `nobody` with id `65534`. If, e.g. logs are host bound, then execute
`chown -R 65534:65534 host/path/to/log`. The same holds true for snapshots. `chown -R 65534:65534 host/path/to/log`. The same holds true for snapshots.
## Configuration ## Configuration
The main configuration file for the *web interface* is the `env` file located in `config/`. There's an example file The main configuration file for the *web interface* is the `env` file located in `config/`. There's an example file
called `env.example` which you **need** to copy to `config/env`. Defaults will assume you're running your TeamSpeak called `env.example` 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 and *should* host bind this file into the container server on `localhost` with default port. Docker deployments can and *should* host bind this file into the container
directly and just maintain the `env` file. directly and just maintain the `env` file.
## Deployment ## Deployment
The application can be deployed two different ways. See below for more information. For each deployment type a running
TeamSpeak 3 instance is a prerequisite. The application can be deployed two different ways. See below for more information. For each deployment type a running
TeamSpeak 3 instance is a prerequisite.
In the `docker-compose.yml` [example](#dockercompose), a setup together with a teamspeak server instance is shown. In the `docker-compose.yml` [example](#dockercompose), a setup together with a teamspeak server instance is shown.
@ -50,17 +59,19 @@ In the `docker-compose.yml` [example](#dockercompose), a setup together with a t
1. [Setup write permissions if you're using host binds](#dockerperms) 1. [Setup write permissions if you're using host binds](#dockerperms)
2. [Ensure that you set `flood commands` to a higher value in your TeamSpeak](#flood). 2. [Ensure that you set `flood commands` to a higher value in your TeamSpeak](#flood).
3. [Use a `whitelist.txt` to ensure the web interface will not be query banned](#whitelist) 3. [Use a `whitelist.txt` to ensure the web interface will not be query banned](#whitelist)
4. Be aware that the web interface will not be able to use `localhost` as TeamSpeak 3 server address because it's not 4. Be aware that the web interface will not be able to use `localhost` as TeamSpeak 3 server address because it's not
available in a docker container. The public address also has to match the environment variable available in a docker container. The public address also has to match the environment variable
`teamspeak_host=your-public-address` within the `env` file. `teamspeak_host=your-public-address` within the `env` file.
<a name="dockerrun"></a> <a name="dockerrun"></a>
#### docker run #### docker run
The following section outlines a manual setup. Feel free to use the provided `docker-compose.yml` as quick setup. The following section outlines a manual setup. Feel free to use the provided `docker-compose.yml` as quick setup.
1. Create docker volumes for `snapshots`, `log` and `env`. Alternative is to host bind them into your containers. 1. Create docker volumes for `snapshots`, `log` and `env`. Alternative is to host bind them into your containers.
2. Create a docker network with a fixed IP range or later use host network. 2. Create a docker network with a fixed IP range or later use host network.
3. Depending on your setup, you need to change `teamspeak_host` of your `env` file to point either to `your IP` or to a 3. Depending on your setup, you need to change `teamspeak_host` of your `env` file to point either to `your IP` or to a
`fixed docker IP` which your teamspeak uses. `localhost` is not valid if you're using it in docker. If you're unsure, `fixed docker IP` which your teamspeak uses. `localhost` is not valid if you're using it in docker. If you're unsure,
please take a look at the example `docker-compose.yml` files. please take a look at the example `docker-compose.yml` files.
4. Start a container using the docker image `varakh/ts3web` and provide the following bindings for volumes: 4. Start a container using the docker image `varakh/ts3web` and provide the following bindings for volumes:
@ -68,19 +79,24 @@ The following section outlines a manual setup. Feel free to use the provided `do
* `{snapshot_volume|host_folder}:/var/www/html/application/data/snapshots` * `{snapshot_volume|host_folder}:/var/www/html/application/data/snapshots`
* `{log_volume|host_folder}:/var/www/html/application/log` * `{log_volume|host_folder}:/var/www/html/application/log`
5. [Ensure that you're whitelisting the IP from which the webinterface will issue commands.](#whitelist) 5. [Ensure that you're whitelisting the IP from which the webinterface will issue commands.](#whitelist)
6. Run the `docker run` command including your settings, volumes and networks (if any): `docker run --name teamspeak_web -v ./env:/var/www/html/application/config/env -p 8181:80 varakh/ts3web:latest`. 6. Run the `docker run` command including your settings, volumes and networks (if
any): `docker run --name teamspeak_web -v ./env:/var/www/html/application/config/env -p 8181:80 varakh/ts3web:latest`
.
<a name="dockercompose"></a> <a name="dockercompose"></a>
#### docker-compose
In order for TeamSpeak to show correct IP and country flags, the `network_mode = "host"` is advised. It's also
possible to set everything up [without using the host network mode and use fixed IPs](#withouthostmode).
The examples will use host binds for volumes. Feel free to adapt the `docker-compose.yml` template and use docker volumes #### docker-compose
instead if you like.
In order for TeamSpeak to show correct IP and country flags, the `network_mode = "host"` is advised. It's also possible
to set everything up [without using the host network mode and use fixed IPs](#withouthostmode).
The examples will use host binds for volumes. Feel free to adapt the `docker-compose.yml` template and use docker
volumes instead if you like.
Ensure to [apply permissions](#dockerperms) for volumes though. Ensure to [apply permissions](#dockerperms) for volumes though.
<a name="withhostmode"></a> <a name="withhostmode"></a>
#### With 'host' mode #### With 'host' mode
``` ```
@ -121,6 +137,7 @@ services:
``` ```
<a name="withouthostmode"></a> <a name="withouthostmode"></a>
#### Without 'host' mode #### Without 'host' mode
``` ```
@ -169,13 +186,13 @@ services:
``` ```
<a name="whitelisttxtexample"></a> <a name="whitelisttxtexample"></a>
#### whitelist.txt #### whitelist.txt
The following illustrates a valid `whitelist.txt` file which can be used for the above `docker-compose` setups. You The following illustrates a valid `whitelist.txt` file which can be used for the above `docker-compose` setups. You need
need to replace `your-public-ip` with the TeamSpeak's public IP address if required or remove the fixed internal to replace `your-public-ip` with the TeamSpeak's public IP address if required or remove the fixed internal docker IP if
docker IP if you're on 'host' mode. you're on 'host' mode.
``` ```
127.0.0.1 127.0.0.1
@ -184,20 +201,23 @@ docker IP if you're on 'host' mode.
your-public-ip your-public-ip
``` ```
Now execute `docker-compose up -d` to start those containers. If you like to update, do `docker-compose down`, Now execute `docker-compose up -d` to start those containers. If you like to update, do `docker-compose down`,
`docker-compose pull` and then `docker-compose up -d` again. `docker-compose pull` and then `docker-compose up -d` again.
Your TeamSpeak 3 Server will be available under `public-server-ip:9987`. The web interface will be available on Your TeamSpeak 3 Server will be available under `public-server-ip:9987`. The web interface will be available on
`127.0.0.1:8181`. You need to add a [reverse proxy](#reverseproxy) and probably you also want SSL configured if you expose it via domain. `127.0.0.1:8181`. You need to add a [reverse proxy](#reverseproxy) and probably you also want SSL configured if you
For testing purposes, change `- 127.0.0.1:8181:80` to `- 8181:80`. The web interface will then be available under expose it via domain. 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`. `public-server-ip:8181`.
This is **not recommended**! Secure your setup properly via [reverse proxy and SSL](#reverseproxy). This is **not recommended**! Secure your setup properly via [reverse proxy and SSL](#reverseproxy).
### As native PHP application ### As native PHP application
**Prerequisite**: `php`, `composer` and probably `php-fpm` installed on the server. **Prerequisite**: `php`, `composer` and probably `php-fpm` installed on the server.
#### Install: #### Install:
* Clone repository * Clone repository
* Change directory to project home * Change directory to project home
* Execute `composer install` * Execute `composer install`
@ -205,15 +225,19 @@ This is **not recommended**! Secure your setup properly via [reverse proxy and S
* Do the configuration by coping the `env.example` file (see information above) * Do the configuration by coping the `env.example` file (see information above)
* Use a web server _or_ run directly via the embedded PHP server: `php -S localhost:8080 -t public public/index.php`. * Use a web server _or_ run directly via the embedded PHP server: `php -S localhost:8080 -t public public/index.php`.
* Point your browser to [localhost:8080](http://localhost:8080) * Point your browser to [localhost:8080](http://localhost:8080)
* Apply any [whitelist.txt](#whitelisttxtexample) changes if you configured `teamspeak_host` differently than `localhost` * Apply any [whitelist.txt](#whitelisttxtexample) changes if you configured `teamspeak_host` differently
than `localhost`
#### Upgrade: #### Upgrade:
* Change directory to project home * Change directory to project home
* `git pull` * `git pull`
* `composer update` * `composer update`
<a name="reverseproxy"></a> <a name="reverseproxy"></a>
### Reverse proxy ### Reverse proxy
Here's an example on how to configure a reverse proxy for the web interface docker container Here's an example on how to configure a reverse proxy for the web interface docker container
``` ```
@ -256,12 +280,13 @@ supported:
If you're willing to contribute, here's some information. If you're willing to contribute, here's some information.
### Release ### Release
* Set a date in the `CHANGELOG.md` file * Set a date in the `CHANGELOG.md` file
* Remove `SNAPSHOT` from the version in `Constants.php` * Remove `SNAPSHOT` from the version in `Constants.php`
* Build the docker image from the project * Build the docker image from the project
* if necessary, add GitHub access token to let composer pull dependencies within the image correctly: * if necessary, add GitHub access token to let composer pull dependencies within the image correctly:
add `&& composer config --global --auth github-oauth.github.com <token> \` before the `composer install` command, add `&& composer config --global --auth github-oauth.github.com <token> \` before the `composer install` command,
where `<token>` can be retrieved from [GitHub settings](https://github.com/settings/tokens) where `<token>` can be retrieved from [GitHub settings](https://github.com/settings/tokens)
* execute `sudo docker build --no-cache -t varakh/ts3web:latest .` to build * execute `sudo docker build --no-cache -t varakh/ts3web:latest .` to build
* publish it * publish it
* 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
@ -274,7 +299,9 @@ If you're willing to contribute, here's some information.
4. Don't forget to clean up all created branches 4. Don't forget to clean up all created branches
### 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.
``` ```
hiddenDependingOnAttribute // hides a row depending on a value in a table hiddenDependingOnAttribute // hides a row depending on a value in a table
@ -289,10 +316,15 @@ fields // define fields for a form
See example usage in the folder `View/bootstrap4`. See example usage in the folder `View/bootstrap4`.
### Translations ### Translations
- This app uses Symfony Translator. It's bootstrapped in `Util\BootstrapHelper` and locales are placed under `data/locale/` and the data table `.json` file, e.g. `en_dataTable.json`. Adjust to your needs or help translating.
- Form fields (name/id should be the same) are also translated. For a field named `content` or `ConT enT` translate `form_field_content`. - This app uses Symfony Translator. It's bootstrapped in `Util\BootstrapHelper` and locales are placed
under `data/locale/` and the data table `.json` file, e.g. `en_dataTable.json`. Adjust to your needs or help
translating.
- Form fields (name/id should be the same) are also translated. For a field named `content` or `ConT enT`
translate `form_field_content`.
### Theme ### Theme
Themes can be chosen in the `env` file by editing the `theme` variable. Templates are mapped to the corresponding view
folder in `src/View/<themeName>`. `.css`, `.js` and other style files like `.ttf` or `.woff2` for fonts should be placed Themes can be chosen in the `env` file by editing the `theme` variable. Templates are mapped to the corresponding view
folder in `src/View/<themeName>`. `.css`, `.js` and other style files like `.ttf` or `.woff2` for fonts should be placed
in `public/theme/<themeName>` and accessed accordingly. See an example in `src/View/boostrap4/layout.twig`. in `public/theme/<themeName>` and accessed accordingly. See an example in `src/View/boostrap4/layout.twig`.

View file

@ -15,11 +15,11 @@ final class AuthLoginAction extends AbstractAction
$validator = new Validator(); $validator = new Validator();
$body = $validator->sanitize($body); $body = $validator->sanitize($body);
$validator->filter_rules([ $validator->filter_rules([
'username' => 'trim', 'username' => 'trim',
]); ]);
$validator->validation_rules([ $validator->validation_rules([
'username' => 'required|min_len,1', 'username' => 'required|min_len,1',
'password' => 'required|min_len,1', 'password' => 'required|min_len,1',
]); ]);
if (!$validator->run($body)) { if (!$validator->run($body)) {
$validator->addErrorsToFlashMessage($this->flash); $validator->addErrorsToFlashMessage($this->flash);
@ -42,7 +42,7 @@ final class AuthLoginAction extends AbstractAction
} }
$this->view->render($response, 'login.twig', [ $this->view->render($response, 'login.twig', [
'title' => $this->translator->trans('login.title'), 'title' => $this->translator->trans('login.title'),
]); ]);
} }
} }

View file

@ -8,7 +8,7 @@ final class AuthLogoutAction extends AbstractAction
public function __invoke(Request $request, Response $response, $args) public function __invoke(Request $request, Response $response, $args)
{ {
$this->flash->addMessage('success', $this->translator->trans('logout.flash.success')); $this->flash->addMessage('success', $this->translator->trans('logout.flash.success'));
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']); $this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
$this->ts->getInstance()->logout(); $this->ts->getInstance()->logout();
$this->auth->logout(); $this->auth->logout();
@ -16,4 +16,4 @@ final class AuthLogoutAction extends AbstractAction
return $response->withRedirect('/login'); return $response->withRedirect('/login');
} }
} }

View file

@ -8,8 +8,8 @@ final class ForbiddenAction extends AbstractAction
public function __invoke(Request $request, Response $response, $args) public function __invoke(Request $request, Response $response, $args)
{ {
return $this->view->render($response, 'error.twig', [ return $this->view->render($response, 'error.twig', [
'title' => $this->translator->trans('error.403.title'), 'title' => $this->translator->trans('error.403.title'),
'content' => $this->translator->trans('error.403.content') 'content' => $this->translator->trans('error.403.content')
]); ]);
} }
} }

View file

@ -14,8 +14,8 @@ final class InstanceAction extends AbstractAction
$data['data'] = array_merge($hostResult['data'], $instanceResult['data']); $data['data'] = array_merge($hostResult['data'], $instanceResult['data']);
$this->view->render($response, 'instance.twig', [ $this->view->render($response, 'instance.twig', [
'title' => $this->translator->trans('instance.title'), 'title' => $this->translator->trans('instance.title'),
'data' => $this->ts->getInstance()->getElement('data', $data) 'data' => $this->ts->getInstance()->getElement('data', $data)
]); ]);
} }
} }

View file

@ -8,8 +8,8 @@ final class InternalApplicationError extends AbstractAction
public function __invoke(Request $request, Response $response, $args) public function __invoke(Request $request, Response $response, $args)
{ {
return $this->view->render($response, 'error.twig', [ return $this->view->render($response, 'error.twig', [
'title' => $this->translator->trans('error.500.title'), 'title' => $this->translator->trans('error.500.title'),
'content' => $this->translator->trans('error.500.content') 'content' => $this->translator->trans('error.500.content')
]); ]);
} }
} }

View file

@ -8,8 +8,8 @@ final class NotAuthorizedAction extends AbstractAction
public function __invoke(Request $request, Response $response, $args) public function __invoke(Request $request, Response $response, $args)
{ {
return $this->view->render($response, 'error.twig', [ return $this->view->render($response, 'error.twig', [
'title' => $this->translator->trans('error.401.title'), 'title' => $this->translator->trans('error.401.title'),
'content' => $this->translator->trans('error.401.content') 'content' => $this->translator->trans('error.401.content')
]); ]);
} }
} }

View file

@ -8,8 +8,8 @@ final class NotFoundAction extends AbstractAction
public function __invoke(Request $request, Response $response, $args) public function __invoke(Request $request, Response $response, $args)
{ {
return $this->view->render($response, 'error.twig', [ return $this->view->render($response, 'error.twig', [
'title' => $this->translator->trans('error.404.title'), 'title' => $this->translator->trans('error.404.title'),
'content' => $this->translator->trans('error.404.content') 'content' => $this->translator->trans('error.404.content')
]); ]);
} }
} }

View file

@ -16,8 +16,8 @@ final class ProfileAction extends AbstractAction
$whoisResult = $this->ts->getInstance()->whoAmI(); $whoisResult = $this->ts->getInstance()->whoAmI();
$this->view->render($response, 'profile.twig', [ $this->view->render($response, 'profile.twig', [
'title' => $this->translator->trans('profile.title'), 'title' => $this->translator->trans('profile.title'),
'whois' => $this->ts->getInstance()->getElement('data', $whoisResult), 'whois' => $this->ts->getInstance()->getElement('data', $whoisResult),
]); ]);
} }
} }

View file

@ -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\Exception\IOException; use Symfony\Component\Filesystem\Exception\IOException;
@ -39,4 +38,4 @@ final class SnapshotDeleteAction extends AbstractAction
return $response->withRedirect('/snapshots/' . $sid); return $response->withRedirect('/snapshots/' . $sid);
} }
} }

View file

@ -47,7 +47,7 @@ class TSAuthAdapter extends \Zend\Authentication\Adapter\AbstractAdapter
if ($this->ts->login($user, $password)) { if ($this->ts->login($user, $password)) {
$this->logger->debug(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());
} else { } else {
return new Result( return new Result(
@ -57,4 +57,4 @@ class TSAuthAdapter extends \Zend\Authentication\Adapter\AbstractAdapter
); );
} }
} }
} }

View file

@ -44,7 +44,8 @@ class FileHelper
* @param int $decimals * @param int $decimals
* @return string * @return string
*/ */
public static function humanFileSize($bytes, $decimals = 2) { public static function humanFileSize($bytes, $decimals = 2)
{
$sz = 'BKMGTP'; $sz = 'BKMGTP';
$factor = floor((strlen($bytes) - 1) / 3); $factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor]; return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
@ -57,9 +58,10 @@ class FileHelper
* @param int $decimals * @param int $decimals
* @return string * @return string
*/ */
public static function humanBandwidth($bytes, $decimals = 2) { public static function humanBandwidth($bytes, $decimals = 2)
{
$sz = 'BKMGTP'; $sz = 'BKMGTP';
$factor = floor((strlen($bytes) - 1) / 3); $factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor] . '/s'; return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor] . '/s';
} }
} }

View file

@ -12,12 +12,14 @@ class TS3AdminProxy
* @param ts3admin $object * @param ts3admin $object
* @param $logger LoggerInterface * @param $logger LoggerInterface
*/ */
public function __construct(ts3admin $object, $logger) { public function __construct(ts3admin $object, $logger)
{
$this->object = $object; $this->object = $object;
$this->logger = $logger; $this->logger = $logger;
} }
public function __call($method, $args) { public function __call($method, $args)
{
// hide sensitive args // hide sensitive args
if (in_array($method, ['login'])) { if (in_array($method, ['login'])) {
@ -67,4 +69,4 @@ class TS3AdminProxy
return $result; return $result;
} }
} }

View file

@ -51,49 +51,6 @@ class TSInstance
} }
} }
/**
* Login
*
* @param $user
* @param $password
* @return bool
*/
public function login($user, $password)
{
if (!empty($user) && !empty($password)) {
$this->ts->login($user, $password);
$this->logger->debug(sprintf('Logged in as %s', $user));
} else {
throw new InvalidArgumentException('User and password not provided');
}
return true;
}
/**
* @return string
*/
public function getHost()
{
return $this->host;
}
/**
* @return int
*/
public function getQueryPort()
{
return $this->queryPort;
}
/**
* @return ts3admin
*/
public function getInstance()
{
return $this->ts;
}
/** /**
* @return array * @return array
*/ */
@ -224,4 +181,47 @@ class TSInstance
return $arr; return $arr;
} }
}
/**
* Login
*
* @param $user
* @param $password
* @return bool
*/
public function login($user, $password)
{
if (!empty($user) && !empty($password)) {
$this->ts->login($user, $password);
$this->logger->debug(sprintf('Logged in as %s', $user));
} else {
throw new InvalidArgumentException('User and password not provided');
}
return true;
}
/**
* @return string
*/
public function getHost()
{
return $this->host;
}
/**
* @return int
*/
public function getQueryPort()
{
return $this->queryPort;
}
/**
* @return ts3admin
*/
public function getInstance()
{
return $this->ts;
}
}

View file

@ -13,52 +13,110 @@ class Validator extends GUMP
$this->translator = BootstrapHelper::bootTranslator(); $this->translator = BootstrapHelper::bootTranslator();
} }
protected function validate_set_min_len($field, $input, $param = NULL) /**
* Perform data validation against the provided ruleset
*
* Arrays as FIELDS are added here as a custom feature
*
* @access public
* @param mixed $input
* @param array $ruleset
* @return mixed
* @throws \Exception
*/
public function validate(array $input, array $ruleset)
{ {
$this->errors = [];
$err = [ foreach ($ruleset as $field => $rules) {
'field' => $field, #if(!array_key_exists($field, $input))
'value' => $input[$field], #{
'rule' => __FUNCTION__, # continue;
'param' => $param, #}
];
if (!is_array($input[$field])) { $rules = explode('|', $rules);
return $err;
if (in_array("required", $rules) || (isset($input[$field]) && (is_array($input[$field]) || trim($input[$field]) != ''))) {
foreach ($rules as $rule) {
$method = NULL;
$param = NULL;
if (strstr($rule, ',') !== false) // has params
{
$rule = explode(',', $rule);
$method = 'validate_' . $rule[0];
$param = $rule[1];
$rule = $rule[0];
} else {
$method = 'validate_' . $rule;
}
// array required
if ($rule === "required" && !isset($input[$field])) {
$result = $this->$method($field, $input, $param);
$this->errors[] = $result;
return;
}
if (is_callable([$this, $method])) {
$result = $this->$method($field, $input, $param);
if (is_array($result)) // Validation Failed
{
$this->errors[] = $result;
return $this->errors;
}
} else {
if (isset(self::$validation_methods[$rule])) {
if (isset($input[$field])) {
$result = call_user_func(self::$validation_methods[$rule], $field, $input, $param);
$result = $this->$method($field, $input, $param);
if (is_array($result)) // Validation Failed
{
$this->errors[] = $result;
return $this->errors;
}
}
} else {
throw new \Exception("Validator method '$method' does not exist.");
}
}
}
}
} }
// default value return (count($this->errors) > 0) ? $this->errors : true;
if (empty($param)) $param = 1;
if (count($input[$field]) < $param) return $err;
return true;
} }
/** Validates if $field content is equal to $param public function filter_upper($value, $param = NULL)
* @param $field
* @param $input
* @param $param
* @return bool
*/
protected function validate_equals($field, $input, $param)
{ {
$err = [ return strtoupper($value);
'field' => $field, }
'value' => $input[$field],
'rule' => __FUNCTION__,
'param' => $param,
];
if (!isset($input[$field]) || empty($input[$field]) || empty($param) || !isset($param)) { public function filter_lower($value, $param = NULL)
return $err; {
return strtolower($value);
}
/**
* Converts all error array into a single string
* @return void
*/
public function addErrorsToFlashMessage($flash)
{
$errors = $this->get_errors_array(true);
if (!empty($errors)) {
foreach ($errors as $error) {
$flash->addMessage('error', $error);
}
} }
if ($input[$field] != $param || $input[$field] !== $param) {
return $err;
}
return true;
} }
/** /**
@ -171,112 +229,6 @@ class Validator extends GUMP
return $resp; return $resp;
} }
/**
* Perform data validation against the provided ruleset
*
* Arrays as FIELDS are added here as a custom feature
*
* @access public
* @param mixed $input
* @param array $ruleset
* @return mixed
* @throws \Exception
*/
public function validate(array $input, array $ruleset)
{
$this->errors = [];
foreach ($ruleset as $field => $rules) {
#if(!array_key_exists($field, $input))
#{
# continue;
#}
$rules = explode('|', $rules);
if (in_array("required", $rules) || (isset($input[$field]) && (is_array($input[$field]) || trim($input[$field]) != ''))) {
foreach ($rules as $rule) {
$method = NULL;
$param = NULL;
if (strstr($rule, ',') !== false) // has params
{
$rule = explode(',', $rule);
$method = 'validate_' . $rule[0];
$param = $rule[1];
$rule = $rule[0];
} else {
$method = 'validate_' . $rule;
}
// array required
if ($rule === "required" && !isset($input[$field])) {
$result = $this->$method($field, $input, $param);
$this->errors[] = $result;
return;
}
if (is_callable([$this, $method])) {
$result = $this->$method($field, $input, $param);
if (is_array($result)) // Validation Failed
{
$this->errors[] = $result;
return $this->errors;
}
} else {
if (isset(self::$validation_methods[$rule])) {
if (isset($input[$field])) {
$result = call_user_func(self::$validation_methods[$rule], $field, $input, $param);
$result = $this->$method($field, $input, $param);
if (is_array($result)) // Validation Failed
{
$this->errors[] = $result;
return $this->errors;
}
}
} else {
throw new \Exception("Validator method '$method' does not exist.");
}
}
}
}
}
return (count($this->errors) > 0) ? $this->errors : true;
}
public function filter_upper($value, $param = NULL)
{
return strtoupper($value);
}
public function filter_lower($value, $param = NULL)
{
return strtolower($value);
}
/**
* Converts all error array into a single string
* @return void
*/
public function addErrorsToFlashMessage($flash)
{
$errors = $this->get_errors_array(true);
if (!empty($errors)) {
foreach ($errors as $error) {
$flash->addMessage('error', $error);
}
}
}
/** /**
* @param array $input * @param array $input
* @param array $fields * @param array $fields
@ -285,7 +237,7 @@ class Validator extends GUMP
*/ */
public function sanitize(array $input, array $fields = array(), $utf8_encode = true) public function sanitize(array $input, array $fields = array(), $utf8_encode = true)
{ {
$magic_quotes = (bool) get_magic_quotes_gpc(); $magic_quotes = (bool)get_magic_quotes_gpc();
if (empty($fields)) { if (empty($fields)) {
$fields = array_keys($input); $fields = array_keys($input);
@ -324,4 +276,52 @@ class Validator extends GUMP
return $return; return $return;
} }
}
protected function validate_set_min_len($field, $input, $param = NULL)
{
$err = [
'field' => $field,
'value' => $input[$field],
'rule' => __FUNCTION__,
'param' => $param,
];
if (!is_array($input[$field])) {
return $err;
}
// default value
if (empty($param)) $param = 1;
if (count($input[$field]) < $param) return $err;
return true;
}
/** Validates if $field content is equal to $param
* @param $field
* @param $input
* @param $param
* @return bool
*/
protected function validate_equals($field, $input, $param)
{
$err = [
'field' => $field,
'value' => $input[$field],
'rule' => __FUNCTION__,
'param' => $param,
];
if (!isset($input[$field]) || empty($input[$field]) || empty($param) || !isset($param)) {
return $err;
}
if ($input[$field] != $param || $input[$field] !== $param) {
return $err;
}
return true;
}
}

View file

@ -11,9 +11,9 @@
'uri': '/clients/ban/' ~ sid ~ '/' ~ cldbid, 'uri': '/clients/ban/' ~ sid ~ '/' ~ cldbid,
'uri_method': 'post', 'uri_method': 'post',
'fields': [ 'fields': [
{'type': 'text', 'key': 'reason', 'label': 'client_info.ban.reason'|trans}, {'type': 'text', 'key': 'reason', 'label': 'client_info.ban.reason'|trans},
{'type': 'number', 'key': 'time', 'label': 'client_info.ban.time'|trans} {'type': 'number', 'key': 'time', 'label': 'client_info.ban.time'|trans}
] ]
}, },
{ {
'header_label': 'client_info.send'|trans, 'header_label': 'client_info.send'|trans,
@ -37,7 +37,7 @@
{% endif %} {% endif %}
{% if channelGroups|length > 0 %} {% if channelGroups|length > 0 %}
<h5>{% trans %}client_info.h.channelgroups{% endtrans %}</h5> <h5>{% trans %}client_info.h.channelgroups{% endtrans %}</h5>
{% include 'table.twig' with {'data': channelGroups, {% include 'table.twig' with {'data': channelGroups,
'hiddenColumns': ['cldbid', 'cid', 'cgid'], 'hiddenColumns': ['cldbid', 'cid', 'cgid'],
'additional_links': [ 'additional_links': [
@ -71,4 +71,4 @@
<h5>{% trans %}client_info.h.permissions{% endtrans %}</h5> <h5>{% trans %}client_info.h.permissions{% endtrans %}</h5>
{% include 'table.twig' with {'data': permissions} %} {% include 'table.twig' with {'data': permissions} %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -27,7 +27,8 @@
<td>{{ path }}</td> <td>{{ path }}</td>
<td>{{ file.size == 0 ? '' : file.size|file }}</td> <td>{{ file.size == 0 ? '' : file.size|file }}</td>
<td>{{ file.datetime|timestamp }}</td> <td>{{ file.datetime|timestamp }}</td>
<td><a href="/channels/files/delete/{{ sid }}/{{ cid }}?file={{ path }}"><i class="fa fa-trash"></i></a></td> <td><a href="/channels/files/delete/{{ sid }}/{{ cid }}?file={{ path }}"><i class="fa fa-trash"></i></a>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
<tr> <tr>
@ -39,4 +40,4 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>

View file

@ -27,7 +27,7 @@
</div> </div>
{% elseif field.type == 'checkbox' %} {% elseif field.type == 'checkbox' %}
<div class="form-group row"> <div class="form-group row">
<label class="col-4 col-form-label" for="{{ field.key }}">{{ field.label }}</label> <label class="col-4 col-form-label" for="{{ field.key }}">{{ field.label }}</label>
<div class="col-8"> <div class="col-8">
<input class="form-control form-control-sm" type="hidden" name="{{ field.key }}" <input class="form-control form-control-sm" type="hidden" name="{{ field.key }}"
id="{{ editable.key }}" value="0"/> id="{{ editable.key }}" value="0"/>
@ -40,10 +40,10 @@
<label class="col-4 col-form-label" for="{{ field.key }}">{{ field.label }}</label> <label class="col-4 col-form-label" for="{{ field.key }}">{{ field.label }}</label>
<div class="col-8"> <div class="col-8">
<input <input
class="form-control form-control-sm" class="form-control form-control-sm"
name="{{ field.key }}" name="{{ field.key }}"
id="{{ field.key }}" id="{{ field.key }}"
type="{{ field.type }}"/> type="{{ field.type }}"/>
</div> </div>
</div> </div>
{% endif %} {% endif %}
@ -53,4 +53,4 @@
<button class="btn btn-primary btn-sm" type="submit">{{ form.label|raw }}</button> <button class="btn btn-primary btn-sm" type="submit">{{ form.label|raw }}</button>
</div> </div>
</form> </form>
{% endfor %} {% endfor %}

View file

@ -18,39 +18,39 @@
<input type="hidden" name="{{ editable.key }}" <input type="hidden" name="{{ editable.key }}"
id="{{ editable.key }}" value="0" id="{{ editable.key }}" value="0"
{% if editable.readOnly is defined and editable.readOnly == true %} {% if editable.readOnly is defined and editable.readOnly == true %}
readonly readonly
{% endif %} {% endif %}
/> />
<input class="form-check-input" type="checkbox" name="{{ editable.key }}" <input class="form-check-input" type="checkbox" name="{{ editable.key }}"
id="{{ editable.key }}" value="1" checked id="{{ editable.key }}" value="1" checked
{% if editable.readOnly is defined and editable.readOnly == true %} {% if editable.readOnly is defined and editable.readOnly == true %}
readonly readonly
{% endif %} {% endif %}
/> />
{% else %} {% else %}
<input type="hidden" name="{{ editable.key }}" <input type="hidden" name="{{ editable.key }}"
id="{{ editable.key }}" value="0" id="{{ editable.key }}" value="0"
{% if editable.readOnly is defined and editable.readOnly == true %} {% if editable.readOnly is defined and editable.readOnly == true %}
readonly readonly
{% endif %} {% endif %}
/> />
<input class="form-check-input" type="checkbox" name="{{ editable.key }}" <input class="form-check-input" type="checkbox" name="{{ editable.key }}"
id="{{ editable.key }}" value="1" id="{{ editable.key }}" value="1"
{% if editable.readOnly is defined and editable.readOnly == true %} {% if editable.readOnly is defined and editable.readOnly == true %}
readonly readonly
{% endif %} {% endif %}
/> />
{% endif %} {% endif %}
{% elseif editable.type == 'select' %} {% elseif editable.type == 'select' %}
<select class="form-control form-control-sm" name="{{ editable.key }}" id="{{ editable.key }}" <select class="form-control form-control-sm" name="{{ editable.key }}" id="{{ editable.key }}"
{% if editable.readOnly is defined and editable.readOnly == true %} {% if editable.readOnly is defined and editable.readOnly == true %}
readonly readonly
{% endif %} {% endif %}
> >
{% for k,v in editable.options %} {% for k,v in editable.options %}
@ -64,13 +64,13 @@
{% else %} {% else %}
<input class="form-control form-control-sm" name="{{ editable.key }}" id="{{ editable.key }}" <input class="form-control form-control-sm" name="{{ editable.key }}" id="{{ editable.key }}"
type="{{ editable.type }}" type="{{ editable.type }}"
{% if editable.blank is defined and editable.blank == true %} {% if editable.blank is defined and editable.blank == true %}
{% else %} {% else %}
value="{{ item }}" value="{{ item }}"
{% endif %} {% endif %}
{% if editable.readOnly is defined and editable.readOnly == true %} {% if editable.readOnly is defined and editable.readOnly == true %}
readonly readonly
{% endif %} {% endif %}
/> />
{% endif %} {% endif %}
</div> </div>
@ -79,4 +79,4 @@
type="submit">{{ editable.submit_label|raw }}</button> type="submit">{{ editable.submit_label|raw }}</button>
</div> </div>
</div> </div>
</form> </form>

View file

@ -13,11 +13,11 @@
'uri': '/servergroups/create/' ~ sid, 'uri': '/servergroups/create/' ~ sid,
'uri_method': 'post', 'uri_method': 'post',
'fields': [ 'fields': [
{'type': 'text', 'key': 'name', 'label': 'groups.create.name'|trans}, {'type': 'text', 'key': 'name', 'label': 'groups.create.name'|trans},
{'type': 'select', 'key': 'type', 'options': groupTypes, 'label': 'groups.create.type'|trans}, {'type': 'select', 'key': 'type', 'options': groupTypes, 'label': 'groups.create.type'|trans},
{'type': 'checkbox', 'key': 'copy', 'label': 'groups.create.copy'|trans}, {'type': 'checkbox', 'key': 'copy', 'label': 'groups.create.copy'|trans},
{'type': 'select', 'key': 'template', 'options': serverGroupsTemplate, 'label': 'groups.create.template'|trans} {'type': 'select', 'key': 'template', 'options': serverGroupsTemplate, 'label': 'groups.create.template'|trans}
] ]
}, },
] ]
} %} } %}
@ -104,4 +104,4 @@
{% else %} {% else %}
{% include 'no_entities.twig' %} {% include 'no_entities.twig' %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -1,7 +1,7 @@
{% extends 'layout.twig' %} {% extends 'layout.twig' %}
{% block content %} {% block content %}
<h3 >{{ title }}</h3> <h3>{{ title }}</h3>
{% include 'keyvalue.twig' with {'data': data, {% include 'keyvalue.twig' with {'data': data,
'filters': [ 'filters': [
{'key': 'instance_uptime', 'apply': 'timeInSeconds'}, {'key': 'instance_uptime', 'apply': 'timeInSeconds'},
@ -28,4 +28,4 @@
{'key': 'serverinstance_serverquery_flood_ban_time', 'type': 'number', 'uri': '/instance/edit', 'uri_method': 'post', 'submit_label': '<i class="fa fa-check"></i>'}, {'key': 'serverinstance_serverquery_flood_ban_time', 'type': 'number', 'uri': '/instance/edit', 'uri_method': 'post', 'submit_label': '<i class="fa fa-check"></i>'},
] ]
} %} } %}
{% endblock %} {% endblock %}

View file

@ -33,7 +33,7 @@
{% if editable is not empty %} {% if editable is not empty %}
{% include 'form_inline.twig' with {'editable': editable} %} {% include 'form_inline.twig' with {'editable': editable} %}
<!-- cell is static or a link--> <!-- cell is static or a link-->
{% else %} {% else %}
{{ item }} {{ item }}
{% endif %} {% endif %}
@ -43,4 +43,4 @@
</tbody> </tbody>
</table> </table>
</div> </div>

View file

@ -20,7 +20,8 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="username" class="sr-only">{% 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-sm" <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') }}"
@ -28,7 +29,8 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password" class="sr-only">{% 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-sm" class="form-control form-control-sm"
placeholder="{% trans %}login.form.password.placeholder{% endtrans %}" required> placeholder="{% trans %}login.form.password.placeholder{% endtrans %}" required>
@ -44,4 +46,4 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -16,7 +16,7 @@
{% endif %} {% endif %}
{% if appLog|length > 0 %} {% if appLog|length > 0 %}
<br /> <br/>
<h3>{% trans %}app_log.title{% endtrans %}</h3> <h3>{% trans %}app_log.title{% endtrans %}</h3>
<div class="small"> <div class="small">
{% for line in appLog %} {% for line in appLog %}
@ -24,4 +24,4 @@
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -9,49 +9,67 @@
<ul class="navbar-nav ml-auto"> <ul class="navbar-nav ml-auto">
{% if currentUser is not empty %} {% if currentUser is not empty %}
<li class="nav-item"><a class="nav-link" href="/instance"><i class="fa fa-info"></i> {% trans %}menu.instance{% endtrans %}</a> <li class="nav-item"><a class="nav-link" href="/instance"><i
class="fa fa-info"></i> {% trans %}menu.instance{% endtrans %}</a>
</li> </li>
<li class="nav-item"><a class="nav-link" href="/logs"><i class="fa fa-file"></i> {% trans %}menu.logs{% endtrans %}</a></li> <li class="nav-item"><a class="nav-link" href="/logs"><i
class="fa fa-file"></i> {% trans %}menu.logs{% endtrans %}</a></li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
{% if session_exists('sid') %} {% if session_exists('sid') %}
{% if session_exists('sport') %} {% if session_exists('sport') %}
<a class="nav-link dropdown-toggle" href="#" id="dropdownServer" data-toggle="dropdown" <a class="nav-link dropdown-toggle" href="#" id="dropdownServer" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false"><i class="fa fa-star"></i> {{ session_get('sport') }}</a> aria-haspopup="true" aria-expanded="false"><i
class="fa fa-star"></i> {{ session_get('sport') }}</a>
{% elseif session_exists('sname') %} {% elseif session_exists('sname') %}
<a class="nav-link dropdown-toggle" href="#" id="dropdownServer" data-toggle="dropdown" <a class="nav-link dropdown-toggle" href="#" id="dropdownServer" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false"><i class="fa fa-star"></i> {{ session_get('sname') }}</a> aria-haspopup="true" aria-expanded="false"><i
class="fa fa-star"></i> {{ session_get('sname') }}</a>
{% endif %} {% endif %}
<div class="dropdown-menu" aria-labelledby="dropdownServer"> <div class="dropdown-menu" aria-labelledby="dropdownServer">
<a class="dropdown-item" <a class="dropdown-item"
href="/servers/{{ session_get('sid') }}"><i class="fa fa-info"></i> {% trans %}menu.servers.info{% endtrans %}</a> href="/servers/{{ session_get('sid') }}"><i
class="fa fa-info"></i> {% trans %}menu.servers.info{% endtrans %}</a>
<a class="dropdown-item" <a class="dropdown-item"
href="/online/{{ session_get('sid') }}"><i class="fa fa-signal"></i> {% trans %}menu.servers.online{% endtrans %}</a> href="/online/{{ session_get('sid') }}"><i
class="fa fa-signal"></i> {% trans %}menu.servers.online{% endtrans %}</a>
<a class="dropdown-item" <a class="dropdown-item"
href="/clients/{{ session_get('sid') }}"><i class="fa fa-user-plus"></i> {% trans %}menu.servers.clients{% endtrans %}</a> href="/clients/{{ session_get('sid') }}"><i
class="fa fa-user-plus"></i> {% trans %}menu.servers.clients{% endtrans %}</a>
<a class="dropdown-item" <a class="dropdown-item"
href="/groups/{{ session_get('sid') }}"><i class="fa fa-group"></i> {% trans %}menu.servers.groups{% endtrans %}</a> href="/groups/{{ session_get('sid') }}"><i
class="fa fa-group"></i> {% trans %}menu.servers.groups{% endtrans %}</a>
<a class="dropdown-item" <a class="dropdown-item"
href="/channels/{{ session_get('sid') }}"><i class="fa fa-tv"></i> {% trans %}menu.servers.channels{% endtrans %}</a> href="/channels/{{ session_get('sid') }}"><i
class="fa fa-tv"></i> {% trans %}menu.servers.channels{% endtrans %}</a>
<a class="dropdown-item" <a class="dropdown-item"
href="/bans/{{ session_get('sid') }}"><i class="fa fa-ban"></i> {% trans %}menu.servers.bans{% endtrans %}</a> href="/bans/{{ session_get('sid') }}"><i
class="fa fa-ban"></i> {% trans %}menu.servers.bans{% endtrans %}</a>
<a class="dropdown-item" <a class="dropdown-item"
href="/complains/{{ session_get('sid') }}"><i class="fa fa-thumbs-down"></i> {% trans %}menu.servers.complains{% endtrans %}</a> href="/complains/{{ session_get('sid') }}"><i
class="fa fa-thumbs-down"></i> {% trans %}menu.servers.complains{% endtrans %}
</a>
<a class="dropdown-item" <a class="dropdown-item"
href="/passwords/{{ session_get('sid') }}"><i class="fa fa-key"></i> {% trans %}menu.servers.passwords{% endtrans %}</a> href="/passwords/{{ session_get('sid') }}"><i
class="fa fa-key"></i> {% trans %}menu.servers.passwords{% endtrans %}</a>
<a class="dropdown-item" <a class="dropdown-item"
href="/tokens/{{ session_get('sid') }}"><i class="fa fa-user-secret"></i> {% trans %}menu.servers.tokens{% endtrans %}</a> href="/tokens/{{ session_get('sid') }}"><i
class="fa fa-user-secret"></i> {% trans %}menu.servers.tokens{% endtrans %}</a>
<a class="dropdown-item" <a class="dropdown-item"
href="/snapshots/{{ session_get('sid') }}"><i class="fa fa-save"></i> {% trans %}menu.servers.snapshots{% endtrans %}</a> href="/snapshots/{{ session_get('sid') }}"><i
class="fa fa-save"></i> {% trans %}menu.servers.snapshots{% endtrans %}</a>
<a class="dropdown-item" <a class="dropdown-item"
href="/logs/{{ session_get('sid') }}"><i class="fa fa-file"></i> {% trans %}menu.servers.logs{% endtrans %}</a> href="/logs/{{ session_get('sid') }}"><i
class="fa fa-file"></i> {% trans %}menu.servers.logs{% endtrans %}</a>
<hr/> <hr/>
<a class="dropdown-item" href="/servers/deselect"><i class="fa fa-close"></i> {% trans %}menu.servers.deselect{% endtrans %}</a> <a class="dropdown-item" href="/servers/deselect"><i
class="fa fa-close"></i> {% trans %}menu.servers.deselect{% endtrans %}</a>
<hr/> <hr/>
<a class="dropdown-item" <a class="dropdown-item"
href="/servers"><i class="fa fa-server"></i> {% trans %}menu.servers{% endtrans %}</a> href="/servers"><i class="fa fa-server"></i> {% trans %}menu.servers{% endtrans %}
</a>
</div> </div>
{% else %} {% else %}
@ -61,7 +79,8 @@
aria-expanded="false">{% trans %}menu.servers.select{% endtrans %}</a> aria-expanded="false">{% trans %}menu.servers.select{% endtrans %}</a>
<div class="dropdown-menu" aria-labelledby="dropdownServer"> <div class="dropdown-menu" aria-labelledby="dropdownServer">
<a class="dropdown-item" <a class="dropdown-item"
href="/servers"><i class="fa fa-server"></i> {% trans %}menu.servers{% endtrans %}</a> href="/servers"><i class="fa fa-server"></i> {% trans %}menu.servers{% endtrans %}
</a>
</div> </div>
{% endif %} {% endif %}
</li> </li>
@ -69,16 +88,20 @@
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="dropdownUser" data-toggle="dropdown" <a class="nav-link dropdown-toggle" href="#" id="dropdownUser" data-toggle="dropdown"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="false"><i class="fa fa-user"></i> {% trans with {'%username%': currentUser.user } %}menu.currentlyloggedin{% endtrans %}</a> aria-expanded="false"><i
class="fa fa-user"></i> {% trans with {'%username%': currentUser.user } %}menu.currentlyloggedin{% endtrans %}
</a>
<div class="dropdown-menu" aria-labelledby="dropdownUser"> <div class="dropdown-menu" aria-labelledby="dropdownUser">
<a class="dropdown-item" href="/logout"><i class="fa fa-sign-out"></i> {% trans %}menu.logout{% endtrans %}</a> <a class="dropdown-item" href="/logout"><i
class="fa fa-sign-out"></i> {% trans %}menu.logout{% endtrans %}</a>
</div> </div>
</li> </li>
{% else %} {% else %}
<li class="nav-item"><a class="nav-link" href="/login"><i class="fa fa-sign-in"></i> {% trans %}menu.login{% endtrans %}</a></li> <li class="nav-item"><a class="nav-link" href="/login"><i
class="fa fa-sign-in"></i> {% trans %}menu.login{% endtrans %}</a></li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
</div> </div>
</nav> </nav>

View file

@ -13,8 +13,8 @@
</div> </div>
<div class="col-sm-9"> <div class="col-sm-9">
{% else %} {% else %}
<div class="col-md-12"> <div class="col-md-12">
{% endif %} {% endif %}
{% if data|length >0 %} {% if data|length >0 %}
{% include 'table.twig' with {'data': data, {% include 'table.twig' with {'data': data,
'hiddenDependingOnAttribute': [{'key': 'client_type', 'values': ['1']}], 'hiddenDependingOnAttribute': [{'key': 'client_type', 'values': ['1']}],
@ -49,4 +49,4 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -12,8 +12,8 @@
'uri': '/online/poke/' ~ sid ~ '/' ~ clid, 'uri': '/online/poke/' ~ sid ~ '/' ~ clid,
'uri_method': 'post', 'uri_method': 'post',
'fields': [ 'fields': [
{'type': 'text', 'key': 'message', 'label': 'online.poke.message'|trans}, {'type': 'text', 'key': 'message', 'label': 'online.poke.message'|trans},
] ]
}, },
{ {
'header_label': 'online.kick'|trans, 'header_label': 'online.kick'|trans,
@ -21,8 +21,8 @@
'uri': '/online/kick/' ~ sid ~ '/' ~ clid, 'uri': '/online/kick/' ~ sid ~ '/' ~ clid,
'uri_method': 'post', 'uri_method': 'post',
'fields': [ 'fields': [
{'type': 'text', 'key': 'reason', 'label': 'online.kick.reason'|trans}, {'type': 'text', 'key': 'reason', 'label': 'online.kick.reason'|trans},
] ]
}, },
{ {
'header_label': 'online.ban'|trans, 'header_label': 'online.ban'|trans,
@ -30,9 +30,9 @@
'uri': '/online/ban/' ~ sid ~ '/' ~ clid, 'uri': '/online/ban/' ~ sid ~ '/' ~ clid,
'uri_method': 'post', 'uri_method': 'post',
'fields': [ 'fields': [
{'type': 'text', 'key': 'reason', 'label': 'online.ban.reason'|trans}, {'type': 'text', 'key': 'reason', 'label': 'online.ban.reason'|trans},
{'type': 'number', 'key': 'time', 'label': 'online.ban.time'|trans} {'type': 'number', 'key': 'time', 'label': 'online.ban.time'|trans}
] ]
}, },
{ {
'header_label': 'online.move'|trans, 'header_label': 'online.move'|trans,
@ -40,9 +40,9 @@
'uri': '/online/move/' ~ sid ~ '/' ~ clid, 'uri': '/online/move/' ~ sid ~ '/' ~ clid,
'uri_method': 'post', 'uri_method': 'post',
'fields': [ 'fields': [
{'type': 'select', 'key': 'channel', 'options': channels,'label': 'online.move.channel'|trans}, {'type': 'select', 'key': 'channel', 'options': channels,'label': 'online.move.channel'|trans},
{'type': 'text', 'key': 'channel_password', 'label': 'online.move.channel_password'|trans} {'type': 'text', 'key': 'channel_password', 'label': 'online.move.channel_password'|trans}
] ]
}, },
{ {
'header_label': 'online.send'|trans, 'header_label': 'online.send'|trans,
@ -50,8 +50,8 @@
'uri': '/online/send/' ~ sid ~ '/' ~ clid, 'uri': '/online/send/' ~ sid ~ '/' ~ clid,
'uri_method': 'post', 'uri_method': 'post',
'fields': [ 'fields': [
{'type': 'text', 'key': 'message', 'label': 'online.send.message'|trans}, {'type': 'text', 'key': 'message', 'label': 'online.send.message'|trans},
] ]
} }
] ]
} %} } %}
@ -78,4 +78,4 @@
{'key': 'connection_bytes_sent_total', 'apply': 'file'}, {'key': 'connection_bytes_sent_total', 'apply': 'file'},
{'key': 'connection_bytes_received_total', 'apply': 'file'}, {'key': 'connection_bytes_received_total', 'apply': 'file'},
]} %} ]} %}
{% endblock %} {% endblock %}

View file

@ -13,12 +13,12 @@
'uri': '/passwords/add/' ~ sid, 'uri': '/passwords/add/' ~ sid,
'uri_method': 'post', 'uri_method': 'post',
'fields': [ 'fields': [
{'type': 'text', 'key': 'password', 'label': 'passwords.add.password'|trans}, {'type': 'text', 'key': 'password', 'label': 'passwords.add.password'|trans},
{'type': 'number', 'key': 'duration', 'label': 'passwords.add.duration'|trans}, {'type': 'number', 'key': 'duration', 'label': 'passwords.add.duration'|trans},
{'type': 'text', 'key': 'description', 'label': 'passwords.add.description'|trans}, {'type': 'text', 'key': 'description', 'label': 'passwords.add.description'|trans},
{'type': 'select', 'key': 'channel', 'options': channels,'label': 'passwords.add.channel'|trans}, {'type': 'select', 'key': 'channel', 'options': channels,'label': 'passwords.add.channel'|trans},
{'type': 'text', 'key': 'channel_password', 'label': 'passwords.add.channel_password'|trans}, {'type': 'text', 'key': 'channel_password', 'label': 'passwords.add.channel_password'|trans},
] ]
}, },
] ]
} %} } %}
@ -49,4 +49,4 @@
] ]
} %} } %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -11,15 +11,15 @@
<h5>{% trans %}server_info.h.actions{% endtrans %}</h5> <h5>{% trans %}server_info.h.actions{% endtrans %}</h5>
{% include 'form.twig' with { {% include 'form.twig' with {
'fields': [ 'fields': [
{ {
'header_label': 'server_info.send'|trans, 'header_label': 'server_info.send'|trans,
'label': '<i class="fa fa-check"></i>', 'label': '<i class="fa fa-check"></i>',
'uri': '/servers/send/' ~ sid, 'uri': '/servers/send/' ~ sid,
'uri_method': 'post', 'uri_method': 'post',
'fields': [ 'fields': [
{'type': 'text', 'key': 'message', 'label': 'server_info.send.message'|trans}, {'type': 'text', 'key': 'message', 'label': 'server_info.send.message'|trans},
] ]
}] }]
} %} } %}
<h5>{% trans %}server_info.h.details{% endtrans %}</h5> <h5>{% trans %}server_info.h.details{% endtrans %}</h5>
@ -90,4 +90,4 @@
] ]
} %} } %}
{% endblock %} {% endblock %}

View file

@ -12,8 +12,8 @@
'uri': '/servergroups/add/' ~ sid ~ '/' ~ sgid, 'uri': '/servergroups/add/' ~ sid ~ '/' ~ sgid,
'uri_method': 'post', 'uri_method': 'post',
'fields': [ 'fields': [
{'type': 'text', 'key': 'cldbid', 'label': 'servergroup_info.add.cldbid'|trans}, {'type': 'text', 'key': 'cldbid', 'label': 'servergroup_info.add.cldbid'|trans},
] ]
} }
] ]
} %} } %}
@ -41,4 +41,4 @@
{% include 'table.twig' with {'data': permissions} %} {% include 'table.twig' with {'data': permissions} %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -5,42 +5,42 @@
<h5>{% trans %}servers.h.details{% endtrans %}</h5> <h5>{% trans %}servers.h.details{% endtrans %}</h5>
{% if data|length > 0 %} {% if data|length > 0 %}
{% include 'table.twig' with {'data': data, {% include 'table.twig' with {'data': data,
'filters': [ 'filters': [
{'key': 'virtualserver_uptime', 'apply': 'timeInSeconds'}, {'key': 'virtualserver_uptime', 'apply': 'timeInSeconds'},
], ],
'links': [ 'links': [
{'key': 'virtualserver_port', 'uri': '/servers', 'uri_param': 'virtualserver_id'}, {'key': 'virtualserver_port', 'uri': '/servers', 'uri_param': 'virtualserver_id'},
{'key': 'virtualserver_clientsonline', 'uri': '/online', 'uri_param': 'virtualserver_id'} {'key': 'virtualserver_clientsonline', 'uri': '/online', 'uri_param': 'virtualserver_id'}
], ],
'hiddenColumns': ['virtualserver_id', 'virtualserver_queryclientsonline', 'virtualserver_autostart', 'virtualserver_machine_id'], 'hiddenColumns': ['virtualserver_id', 'virtualserver_queryclientsonline', 'virtualserver_autostart', 'virtualserver_machine_id'],
'additional_links': [ 'additional_links': [
{ {
'header_label': 'servers.select'|trans, 'header_label': 'servers.select'|trans,
'label': '<i class="fa fa-check"></i>', 'label': '<i class="fa fa-check"></i>',
'uri': '/servers/select', 'uri': '/servers/select',
'uri_param': 'virtualserver_id' 'uri_param': 'virtualserver_id'
}, },
{ {
'header_label': 'servers.start'|trans, 'header_label': 'servers.start'|trans,
'label': '<i class="fa fa-play"></i>', 'label': '<i class="fa fa-play"></i>',
'uri': '/servers/start', 'uri': '/servers/start',
'uri_param': 'virtualserver_id' 'uri_param': 'virtualserver_id'
}, },
{ {
'header_label': 'servers.stop'|trans, 'header_label': 'servers.stop'|trans,
'label': '<i class="fa fa-stop"></i>', 'label': '<i class="fa fa-stop"></i>',
'uri': '/servers/stop', 'uri': '/servers/stop',
'uri_param': 'virtualserver_id' 'uri_param': 'virtualserver_id'
}, },
{ {
'header_label': 'servers.delete'|trans, 'header_label': 'servers.delete'|trans,
'label': '<i class="fa fa-trash"></i>', 'label': '<i class="fa fa-trash"></i>',
'uri': '/servers/delete', 'uri': '/servers/delete',
'uri_param': 'virtualserver_id' 'uri_param': 'virtualserver_id'
} }
], ],
} %} } %}
{% else %} {% else %}
{% include 'no_entities.twig' %} {% include 'no_entities.twig' %}
{% endif %} {% endif %}
@ -54,11 +54,11 @@
'uri': '/servers/create', 'uri': '/servers/create',
'uri_method': 'post', 'uri_method': 'post',
'fields': [ 'fields': [
{'type': 'text', 'key': 'VIRTUALSERVER_NAME', 'label': 'server_create.VIRTUALSERVER_NAME'|trans}, {'type': 'text', 'key': 'VIRTUALSERVER_NAME', 'label': 'server_create.VIRTUALSERVER_NAME'|trans},
{'type': 'text', 'key': 'VIRTUALSERVER_PASSWORD', 'label': 'server_create.VIRTUALSERVER_PASSWORD'|trans}, {'type': 'text', 'key': 'VIRTUALSERVER_PASSWORD', 'label': 'server_create.VIRTUALSERVER_PASSWORD'|trans},
{'type': 'number', 'key': 'VIRTUALSERVER_PORT', 'label': 'server_create.VIRTUALSERVER_PORT'|trans}, {'type': 'number', 'key': 'VIRTUALSERVER_PORT', 'label': 'server_create.VIRTUALSERVER_PORT'|trans},
{'type': 'number', 'key': 'VIRTUALSERVER_MAXCLIENTS', 'label': 'server_create.VIRTUALSERVER_MAXCLIENTS'|trans}, {'type': 'number', 'key': 'VIRTUALSERVER_MAXCLIENTS', 'label': 'server_create.VIRTUALSERVER_MAXCLIENTS'|trans},
] ]
}] }]
} %} } %}
{% endblock %} {% endblock %}

View file

@ -11,21 +11,21 @@
{% include 'table.twig' with {'data': data, {% include 'table.twig' with {'data': data,
'additional_links': [ 'additional_links': [
{ {
'header_label': 'snapshots.deploy'|trans, 'header_label': 'snapshots.deploy'|trans,
'label': '<i class="fa fa-check"></i>', 'label': '<i class="fa fa-check"></i>',
'uri': '/snapshots/deploy/' ~ sid, 'uri': '/snapshots/deploy/' ~ sid,
'uri_param': 'name' 'uri_param': 'name'
}, },
{ {
'header_label': 'snapshots.delete'|trans, 'header_label': 'snapshots.delete'|trans,
'label': '<i class="fa fa-trash"></i>', 'label': '<i class="fa fa-trash"></i>',
'uri': '/snapshots/delete/' ~ sid, 'uri': '/snapshots/delete/' ~ sid,
'uri_param': 'name' 'uri_param': 'name'
} }
], ],
} %} } %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}