Use 4 spaces as tabs and reformat
This commit is contained in:
parent
df2d9520ca
commit
41234b34bf
34 changed files with 509 additions and 421 deletions
6
.editorconfig
Normal file
6
.editorconfig
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -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,22 +27,27 @@
|
||||||
* 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
|
||||||
|
@ -49,6 +58,7 @@
|
||||||
* 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
|
74
README.md
74
README.md
|
@ -1,4 +1,5 @@
|
||||||
# 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.
|
||||||
|
@ -6,28 +7,35 @@ 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
|
||||||
|
@ -38,6 +46,7 @@ server on `localhost` with default port. Docker deployments can and *should* hos
|
||||||
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
|
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.
|
TeamSpeak 3 instance is a prerequisite.
|
||||||
|
|
||||||
|
@ -55,7 +64,9 @@ In the `docker-compose.yml` [example](#dockercompose), a setup together with a t
|
||||||
`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.
|
||||||
|
@ -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
|
||||||
|
@ -188,16 +205,19 @@ Now execute `docker-compose up -d` to start those containers. If you like to upd
|
||||||
`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,6 +280,7 @@ 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
|
||||||
|
@ -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
|
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
|
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`.
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,7 +58,8 @@ 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';
|
||||||
|
|
|
@ -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'])) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
*
|
||||||
$err = [
|
* Arrays as FIELDS are added here as a custom feature
|
||||||
'field' => $field,
|
*
|
||||||
'value' => $input[$field],
|
* @access public
|
||||||
'rule' => __FUNCTION__,
|
* @param mixed $input
|
||||||
'param' => $param,
|
* @param array $ruleset
|
||||||
];
|
* @return mixed
|
||||||
|
* @throws \Exception
|
||||||
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)
|
public function validate(array $input, array $ruleset)
|
||||||
{
|
{
|
||||||
$err = [
|
$this->errors = [];
|
||||||
'field' => $field,
|
|
||||||
'value' => $input[$field],
|
|
||||||
'rule' => __FUNCTION__,
|
|
||||||
'param' => $param,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!isset($input[$field]) || empty($input[$field]) || empty($param) || !isset($param)) {
|
foreach ($ruleset as $field => $rules) {
|
||||||
return $err;
|
#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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($input[$field] != $param || $input[$field] !== $param) {
|
// array required
|
||||||
return $err;
|
if ($rule === "required" && !isset($input[$field])) {
|
||||||
|
$result = $this->$method($field, $input, $param);
|
||||||
|
$this->errors[] = $result;
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,14 +88,18 @@
|
||||||
<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>
|
||||||
|
|
Reference in a new issue