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
|
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -3,18 +3,22 @@
|
|||
## 2.2.4 - UNRELEASED
|
||||
|
||||
## 2.2.3 - 2021/01/08
|
||||
|
||||
* Change docker base to alpine
|
||||
|
||||
## 2.2.2 - 2020/03/22
|
||||
|
||||
* Stop auto-sorting tables
|
||||
* Add bandwidth formatter
|
||||
* Check PHP 7.4 compatibility
|
||||
* Increase docker image base to PHP 7.4
|
||||
|
||||
## 2.2.1 - 2019/11/10
|
||||
|
||||
* Use separate JavaScript file to initialize DataTables
|
||||
|
||||
## 2.2.0 - 2019/11/10
|
||||
|
||||
* Add version tag to footer
|
||||
* Add sortable tables
|
||||
* Add search on tables
|
||||
|
@ -23,32 +27,38 @@
|
|||
* Fix dependency version
|
||||
|
||||
## 2.1.4 - 2019/11/08
|
||||
|
||||
* 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
|
||||
|
||||
## 2.1.3 - 2019/08/08
|
||||
|
||||
* Fixed false rendering of forms
|
||||
* Fixed channel tree view showing the wrong virtual server after selection
|
||||
* Minor code refactor
|
||||
|
||||
## 2.1.2 - 2019/08/07
|
||||
|
||||
* Minor refactoring
|
||||
* Update documentation
|
||||
|
||||
## 2.1.1 - 2019/08/07
|
||||
|
||||
* Updated translation
|
||||
|
||||
## 2.1.0 - 2019/08/07
|
||||
|
||||
* Fixed file handling on snapshots
|
||||
* Cleaned up template links
|
||||
* Updated documentation
|
||||
* Added application log view
|
||||
* Fixed files view
|
||||
* Fixed files view
|
||||
* Added show total used space for files in a channel
|
||||
* Added file delete action
|
||||
* Removed about modal
|
||||
|
||||
## 2.0.0 - 2019/08/06
|
||||
|
||||
* Replace material design with bootstrap4 theme
|
||||
* Add an about modal
|
||||
* Add tree view for online clients
|
||||
|
@ -56,34 +66,45 @@
|
|||
* Update dependencies and force PHP 7.3
|
||||
|
||||
## 1.2.4 - 2019/01/18
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.2.3 - 2019/01/18
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.2.2 - 2018/09/01
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.2.1 - 2018/06/04
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.2.0 - 2018/06/04
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.1.1 - 2018/05/04
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.1.0 - 2018/05/04
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.0.3 - 2018/04/04
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.0.2 - 2018/04/04
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.0.1 - 2018/04/03
|
||||
|
||||
* No info text
|
||||
|
||||
## 1.0.0 - 2018/04/03
|
||||
* No info text
|
||||
|
||||
* No info text
|
||||
|
|
100
README.md
100
README.md
|
@ -1,45 +1,54 @@
|
|||
# README
|
||||
|
||||
ts3web is a free and open-source web interface for TeamSpeak 3 instances.
|
||||
|
||||
|
||||
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)
|
||||
* 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?
|
||||
Free, simple, stateless, easy to extend, standard bootstrap theme.
|
||||
There are many TeamSpeak 3 web interfaces out. Why should I pick ts3web? Free, simple, stateless, easy to extend,
|
||||
standard bootstrap theme.
|
||||
|
||||
## F.A.Q
|
||||
|
||||
Questions? Here you'll hopefully get the answer. Feel free to read before starting.
|
||||
|
||||
<a name="flood"></a>
|
||||
|
||||
###### 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
|
||||
`serverinstance_serverquery_flood_commands` to a high value, e.g. `100` and `serverinstance_serverquery_flood_time` to
|
||||
`1` which is enough.
|
||||
|
||||
<a name="whitelist"></a>
|
||||
|
||||
###### 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.
|
||||
|
||||
<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.
|
||||
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.
|
||||
|
||||
## Configuration
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
|
@ -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)
|
||||
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)
|
||||
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
|
||||
`teamspeak_host=your-public-address` within the `env` file.
|
||||
|
||||
<a name="dockerrun"></a>
|
||||
|
||||
#### docker run
|
||||
|
||||
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.
|
||||
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,
|
||||
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:
|
||||
|
@ -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`
|
||||
* `{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)
|
||||
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>
|
||||
#### 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
|
||||
instead if you like.
|
||||
#### 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 instead if you like.
|
||||
|
||||
Ensure to [apply permissions](#dockerperms) for volumes though.
|
||||
|
||||
<a name="withhostmode"></a>
|
||||
|
||||
#### With 'host' mode
|
||||
|
||||
```
|
||||
|
@ -121,6 +137,7 @@ services:
|
|||
```
|
||||
|
||||
<a name="withouthostmode"></a>
|
||||
|
||||
#### Without 'host' mode
|
||||
|
||||
```
|
||||
|
@ -169,13 +186,13 @@ services:
|
|||
|
||||
```
|
||||
|
||||
|
||||
<a name="whitelisttxtexample"></a>
|
||||
|
||||
#### whitelist.txt
|
||||
|
||||
The following illustrates a valid `whitelist.txt` file which can be used for the above `docker-compose` setups. You
|
||||
need to replace `your-public-ip` with the TeamSpeak's public IP address if required or remove the fixed internal
|
||||
docker IP if you're on 'host' mode.
|
||||
The following illustrates a valid `whitelist.txt` file which can be used for the above `docker-compose` setups. You need
|
||||
to replace `your-public-ip` with the TeamSpeak's public IP address if required or remove the fixed internal docker IP if
|
||||
you're on 'host' mode.
|
||||
|
||||
```
|
||||
127.0.0.1
|
||||
|
@ -184,20 +201,23 @@ docker IP if you're on 'host' mode.
|
|||
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.
|
||||
|
||||
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.
|
||||
For testing purposes, change `- 127.0.0.1:8181:80` to `- 8181:80`. The web interface will then be available under
|
||||
`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. For testing purposes, change `- 127.0.0.1:8181:80` to `- 8181:80`. The web interface will then be
|
||||
available under
|
||||
`public-server-ip:8181`.
|
||||
|
||||
This is **not recommended**! Secure your setup properly via [reverse proxy and SSL](#reverseproxy).
|
||||
|
||||
### As native PHP application
|
||||
|
||||
**Prerequisite**: `php`, `composer` and probably `php-fpm` installed on the server.
|
||||
|
||||
#### Install:
|
||||
|
||||
* Clone repository
|
||||
* Change directory to project home
|
||||
* 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)
|
||||
* 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)
|
||||
* 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:
|
||||
|
||||
* Change directory to project home
|
||||
* `git pull`
|
||||
* `composer update`
|
||||
|
||||
<a name="reverseproxy"></a>
|
||||
|
||||
### Reverse proxy
|
||||
|
||||
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.
|
||||
|
||||
### Release
|
||||
|
||||
* Set a date in the `CHANGELOG.md` file
|
||||
* Remove `SNAPSHOT` from the version in `Constants.php`
|
||||
* Build the docker image from the project
|
||||
* 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,
|
||||
where `<token>` can be retrieved from [GitHub settings](https://github.com/settings/tokens)
|
||||
* 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,
|
||||
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
|
||||
* publish it
|
||||
* 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
|
||||
|
||||
### 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
|
||||
|
@ -289,10 +316,15 @@ fields // define fields for a form
|
|||
See example usage in the folder `View/bootstrap4`.
|
||||
|
||||
### 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
|
||||
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`.
|
||||
|
|
|
@ -15,11 +15,11 @@ final class AuthLoginAction extends AbstractAction
|
|||
$validator = new Validator();
|
||||
$body = $validator->sanitize($body);
|
||||
$validator->filter_rules([
|
||||
'username' => 'trim',
|
||||
'username' => 'trim',
|
||||
]);
|
||||
$validator->validation_rules([
|
||||
'username' => 'required|min_len,1',
|
||||
'password' => 'required|min_len,1',
|
||||
'username' => 'required|min_len,1',
|
||||
'password' => 'required|min_len,1',
|
||||
]);
|
||||
if (!$validator->run($body)) {
|
||||
$validator->addErrorsToFlashMessage($this->flash);
|
||||
|
@ -42,7 +42,7 @@ final class AuthLoginAction extends AbstractAction
|
|||
}
|
||||
|
||||
$this->view->render($response, 'login.twig', [
|
||||
'title' => $this->translator->trans('login.title'),
|
||||
'title' => $this->translator->trans('login.title'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ final class AuthLogoutAction extends AbstractAction
|
|||
public function __invoke(Request $request, Response $response, $args)
|
||||
{
|
||||
$this->flash->addMessage('success', $this->translator->trans('logout.flash.success'));
|
||||
|
||||
|
||||
$this->ts->login($this->auth->getIdentity()['user'], $this->auth->getIdentity()['password']);
|
||||
$this->ts->getInstance()->logout();
|
||||
$this->auth->logout();
|
||||
|
@ -16,4 +16,4 @@ final class AuthLogoutAction extends AbstractAction
|
|||
|
||||
return $response->withRedirect('/login');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ final class ForbiddenAction extends AbstractAction
|
|||
public function __invoke(Request $request, Response $response, $args)
|
||||
{
|
||||
return $this->view->render($response, 'error.twig', [
|
||||
'title' => $this->translator->trans('error.403.title'),
|
||||
'content' => $this->translator->trans('error.403.content')
|
||||
'title' => $this->translator->trans('error.403.title'),
|
||||
'content' => $this->translator->trans('error.403.content')
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ final class InstanceAction extends AbstractAction
|
|||
$data['data'] = array_merge($hostResult['data'], $instanceResult['data']);
|
||||
|
||||
$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)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ final class InternalApplicationError extends AbstractAction
|
|||
public function __invoke(Request $request, Response $response, $args)
|
||||
{
|
||||
return $this->view->render($response, 'error.twig', [
|
||||
'title' => $this->translator->trans('error.500.title'),
|
||||
'content' => $this->translator->trans('error.500.content')
|
||||
'title' => $this->translator->trans('error.500.title'),
|
||||
'content' => $this->translator->trans('error.500.content')
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ final class NotAuthorizedAction extends AbstractAction
|
|||
public function __invoke(Request $request, Response $response, $args)
|
||||
{
|
||||
return $this->view->render($response, 'error.twig', [
|
||||
'title' => $this->translator->trans('error.401.title'),
|
||||
'content' => $this->translator->trans('error.401.content')
|
||||
'title' => $this->translator->trans('error.401.title'),
|
||||
'content' => $this->translator->trans('error.401.content')
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ final class NotFoundAction extends AbstractAction
|
|||
public function __invoke(Request $request, Response $response, $args)
|
||||
{
|
||||
return $this->view->render($response, 'error.twig', [
|
||||
'title' => $this->translator->trans('error.404.title'),
|
||||
'content' => $this->translator->trans('error.404.content')
|
||||
'title' => $this->translator->trans('error.404.title'),
|
||||
'content' => $this->translator->trans('error.404.content')
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ final class ProfileAction extends AbstractAction
|
|||
$whoisResult = $this->ts->getInstance()->whoAmI();
|
||||
|
||||
$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),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
|
@ -39,4 +38,4 @@ final class SnapshotDeleteAction extends AbstractAction
|
|||
|
||||
return $response->withRedirect('/snapshots/' . $sid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ class TSAuthAdapter extends \Zend\Authentication\Adapter\AbstractAdapter
|
|||
if ($this->ts->login($user, $password)) {
|
||||
$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());
|
||||
} else {
|
||||
return new Result(
|
||||
|
@ -57,4 +57,4 @@ class TSAuthAdapter extends \Zend\Authentication\Adapter\AbstractAdapter
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,8 @@ class FileHelper
|
|||
* @param int $decimals
|
||||
* @return string
|
||||
*/
|
||||
public static function humanFileSize($bytes, $decimals = 2) {
|
||||
public static function humanFileSize($bytes, $decimals = 2)
|
||||
{
|
||||
$sz = 'BKMGTP';
|
||||
$factor = floor((strlen($bytes) - 1) / 3);
|
||||
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
|
||||
|
@ -57,9 +58,10 @@ class FileHelper
|
|||
* @param int $decimals
|
||||
* @return string
|
||||
*/
|
||||
public static function humanBandwidth($bytes, $decimals = 2) {
|
||||
public static function humanBandwidth($bytes, $decimals = 2)
|
||||
{
|
||||
$sz = 'BKMGTP';
|
||||
$factor = floor((strlen($bytes) - 1) / 3);
|
||||
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor] . '/s';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,14 @@ class TS3AdminProxy
|
|||
* @param ts3admin $object
|
||||
* @param $logger LoggerInterface
|
||||
*/
|
||||
public function __construct(ts3admin $object, $logger) {
|
||||
public function __construct(ts3admin $object, $logger)
|
||||
{
|
||||
$this->object = $object;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function __call($method, $args) {
|
||||
public function __call($method, $args)
|
||||
{
|
||||
|
||||
// hide sensitive args
|
||||
if (in_array($method, ['login'])) {
|
||||
|
@ -67,4 +69,4 @@ class TS3AdminProxy
|
|||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
@ -224,4 +181,47 @@ class TSInstance
|
|||
|
||||
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();
|
||||
}
|
||||
|
||||
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 = [
|
||||
'field' => $field,
|
||||
'value' => $input[$field],
|
||||
'rule' => __FUNCTION__,
|
||||
'param' => $param,
|
||||
];
|
||||
foreach ($ruleset as $field => $rules) {
|
||||
#if(!array_key_exists($field, $input))
|
||||
#{
|
||||
# continue;
|
||||
#}
|
||||
|
||||
if (!is_array($input[$field])) {
|
||||
return $err;
|
||||
$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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default value
|
||||
if (empty($param)) $param = 1;
|
||||
|
||||
if (count($input[$field]) < $param) return $err;
|
||||
|
||||
return true;
|
||||
return (count($this->errors) > 0) ? $this->errors : 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 filter_upper($value, $param = NULL)
|
||||
{
|
||||
$err = [
|
||||
'field' => $field,
|
||||
'value' => $input[$field],
|
||||
'rule' => __FUNCTION__,
|
||||
'param' => $param,
|
||||
];
|
||||
return strtoupper($value);
|
||||
}
|
||||
|
||||
if (!isset($input[$field]) || empty($input[$field]) || empty($param) || !isset($param)) {
|
||||
return $err;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if ($input[$field] != $param || $input[$field] !== $param) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,112 +229,6 @@ class Validator extends GUMP
|
|||
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 $fields
|
||||
|
@ -285,7 +237,7 @@ class Validator extends GUMP
|
|||
*/
|
||||
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)) {
|
||||
$fields = array_keys($input);
|
||||
|
@ -324,4 +276,52 @@ class Validator extends GUMP
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
'uri': '/clients/ban/' ~ sid ~ '/' ~ cldbid,
|
||||
'uri_method': 'post',
|
||||
'fields': [
|
||||
{'type': 'text', 'key': 'reason', 'label': 'client_info.ban.reason'|trans},
|
||||
{'type': 'number', 'key': 'time', 'label': 'client_info.ban.time'|trans}
|
||||
]
|
||||
{'type': 'text', 'key': 'reason', 'label': 'client_info.ban.reason'|trans},
|
||||
{'type': 'number', 'key': 'time', 'label': 'client_info.ban.time'|trans}
|
||||
]
|
||||
},
|
||||
{
|
||||
'header_label': 'client_info.send'|trans,
|
||||
|
@ -37,7 +37,7 @@
|
|||
{% endif %}
|
||||
|
||||
{% 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,
|
||||
'hiddenColumns': ['cldbid', 'cid', 'cgid'],
|
||||
'additional_links': [
|
||||
|
@ -71,4 +71,4 @@
|
|||
<h5>{% trans %}client_info.h.permissions{% endtrans %}</h5>
|
||||
{% include 'table.twig' with {'data': permissions} %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
<td>{{ path }}</td>
|
||||
<td>{{ file.size == 0 ? '' : file.size|file }}</td>
|
||||
<td>{{ file.datetime|timestamp }}</td>
|
||||
<td><a href="/channels/files/delete/{{ sid }}/{{ cid }}?file={{ path }}"><i class="fa fa-trash"></i></a></td>
|
||||
<td><a href="/channels/files/delete/{{ sid }}/{{ cid }}?file={{ path }}"><i class="fa fa-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
|
@ -39,4 +40,4 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
</div>
|
||||
{% elseif field.type == 'checkbox' %}
|
||||
<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">
|
||||
<input class="form-control form-control-sm" type="hidden" name="{{ field.key }}"
|
||||
id="{{ editable.key }}" value="0"/>
|
||||
|
@ -40,10 +40,10 @@
|
|||
<label class="col-4 col-form-label" for="{{ field.key }}">{{ field.label }}</label>
|
||||
<div class="col-8">
|
||||
<input
|
||||
class="form-control form-control-sm"
|
||||
name="{{ field.key }}"
|
||||
id="{{ field.key }}"
|
||||
type="{{ field.type }}"/>
|
||||
class="form-control form-control-sm"
|
||||
name="{{ field.key }}"
|
||||
id="{{ field.key }}"
|
||||
type="{{ field.type }}"/>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -53,4 +53,4 @@
|
|||
<button class="btn btn-primary btn-sm" type="submit">{{ form.label|raw }}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -18,39 +18,39 @@
|
|||
<input type="hidden" name="{{ editable.key }}"
|
||||
id="{{ editable.key }}" value="0"
|
||||
|
||||
{% if editable.readOnly is defined and editable.readOnly == true %}
|
||||
readonly
|
||||
{% endif %}
|
||||
{% if editable.readOnly is defined and editable.readOnly == true %}
|
||||
readonly
|
||||
{% endif %}
|
||||
/>
|
||||
<input class="form-check-input" type="checkbox" name="{{ editable.key }}"
|
||||
id="{{ editable.key }}" value="1" checked
|
||||
|
||||
{% if editable.readOnly is defined and editable.readOnly == true %}
|
||||
readonly
|
||||
{% endif %}
|
||||
{% if editable.readOnly is defined and editable.readOnly == true %}
|
||||
readonly
|
||||
{% endif %}
|
||||
/>
|
||||
{% else %}
|
||||
<input type="hidden" name="{{ editable.key }}"
|
||||
id="{{ editable.key }}" value="0"
|
||||
|
||||
{% if editable.readOnly is defined and editable.readOnly == true %}
|
||||
readonly
|
||||
{% endif %}
|
||||
{% if editable.readOnly is defined and editable.readOnly == true %}
|
||||
readonly
|
||||
{% endif %}
|
||||
|
||||
/>
|
||||
<input class="form-check-input" type="checkbox" name="{{ editable.key }}"
|
||||
id="{{ editable.key }}" value="1"
|
||||
|
||||
{% if editable.readOnly is defined and editable.readOnly == true %}
|
||||
readonly
|
||||
{% endif %}
|
||||
{% if editable.readOnly is defined and editable.readOnly == true %}
|
||||
readonly
|
||||
{% endif %}
|
||||
/>
|
||||
{% endif %}
|
||||
{% elseif editable.type == 'select' %}
|
||||
<select class="form-control form-control-sm" name="{{ editable.key }}" id="{{ editable.key }}"
|
||||
{% if editable.readOnly is defined and editable.readOnly == true %}
|
||||
readonly
|
||||
{% endif %}
|
||||
{% if editable.readOnly is defined and editable.readOnly == true %}
|
||||
readonly
|
||||
{% endif %}
|
||||
>
|
||||
|
||||
{% for k,v in editable.options %}
|
||||
|
@ -64,13 +64,13 @@
|
|||
{% else %}
|
||||
<input class="form-control form-control-sm" name="{{ editable.key }}" id="{{ editable.key }}"
|
||||
type="{{ editable.type }}"
|
||||
{% if editable.blank is defined and editable.blank == true %}
|
||||
{% else %}
|
||||
value="{{ item }}"
|
||||
{% endif %}
|
||||
{% if editable.readOnly is defined and editable.readOnly == true %}
|
||||
readonly
|
||||
{% endif %}
|
||||
{% if editable.blank is defined and editable.blank == true %}
|
||||
{% else %}
|
||||
value="{{ item }}"
|
||||
{% endif %}
|
||||
{% if editable.readOnly is defined and editable.readOnly == true %}
|
||||
readonly
|
||||
{% endif %}
|
||||
/>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -79,4 +79,4 @@
|
|||
type="submit">{{ editable.submit_label|raw }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
'uri': '/servergroups/create/' ~ sid,
|
||||
'uri_method': 'post',
|
||||
'fields': [
|
||||
{'type': 'text', 'key': 'name', 'label': 'groups.create.name'|trans},
|
||||
{'type': 'select', 'key': 'type', 'options': groupTypes, 'label': 'groups.create.type'|trans},
|
||||
{'type': 'checkbox', 'key': 'copy', 'label': 'groups.create.copy'|trans},
|
||||
{'type': 'select', 'key': 'template', 'options': serverGroupsTemplate, 'label': 'groups.create.template'|trans}
|
||||
]
|
||||
{'type': 'text', 'key': 'name', 'label': 'groups.create.name'|trans},
|
||||
{'type': 'select', 'key': 'type', 'options': groupTypes, 'label': 'groups.create.type'|trans},
|
||||
{'type': 'checkbox', 'key': 'copy', 'label': 'groups.create.copy'|trans},
|
||||
{'type': 'select', 'key': 'template', 'options': serverGroupsTemplate, 'label': 'groups.create.template'|trans}
|
||||
]
|
||||
},
|
||||
]
|
||||
} %}
|
||||
|
@ -104,4 +104,4 @@
|
|||
{% else %}
|
||||
{% include 'no_entities.twig' %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends 'layout.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<h3 >{{ title }}</h3>
|
||||
<h3>{{ title }}</h3>
|
||||
{% include 'keyvalue.twig' with {'data': data,
|
||||
'filters': [
|
||||
{'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>'},
|
||||
]
|
||||
} %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
{% if editable is not empty %}
|
||||
{% include 'form_inline.twig' with {'editable': editable} %}
|
||||
|
||||
<!-- cell is static or a link-->
|
||||
<!-- cell is static or a link-->
|
||||
{% else %}
|
||||
{{ item }}
|
||||
{% endif %}
|
||||
|
@ -43,4 +43,4 @@
|
|||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
</div>
|
||||
|
||||
<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"
|
||||
placeholder="{{ getenv('teamspeak_user') }}"
|
||||
value="{{ getenv('teamspeak_user') }}"
|
||||
|
@ -28,7 +29,8 @@
|
|||
</div>
|
||||
|
||||
<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"
|
||||
class="form-control form-control-sm"
|
||||
placeholder="{% trans %}login.form.password.placeholder{% endtrans %}" required>
|
||||
|
@ -44,4 +46,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
{% endif %}
|
||||
|
||||
{% if appLog|length > 0 %}
|
||||
<br />
|
||||
<br/>
|
||||
<h3>{% trans %}app_log.title{% endtrans %}</h3>
|
||||
<div class="small">
|
||||
{% for line in appLog %}
|
||||
|
@ -24,4 +24,4 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -9,49 +9,67 @@
|
|||
<ul class="navbar-nav ml-auto">
|
||||
|
||||
{% 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 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">
|
||||
|
||||
{% if session_exists('sid') %}
|
||||
{% if session_exists('sport') %}
|
||||
<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') %}
|
||||
<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 %}
|
||||
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownServer">
|
||||
<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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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/>
|
||||
<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/>
|
||||
<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>
|
||||
{% else %}
|
||||
|
||||
|
@ -61,7 +79,8 @@
|
|||
aria-expanded="false">{% trans %}menu.servers.select{% endtrans %}</a>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownServer">
|
||||
<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>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
@ -69,16 +88,20 @@
|
|||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="dropdownUser" data-toggle="dropdown"
|
||||
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">
|
||||
<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>
|
||||
</li>
|
||||
|
||||
{% 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 %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
</div>
|
||||
<div class="col-sm-9">
|
||||
{% else %}
|
||||
<div class="col-md-12">
|
||||
{% endif %}
|
||||
<div class="col-md-12">
|
||||
{% endif %}
|
||||
{% if data|length >0 %}
|
||||
{% include 'table.twig' with {'data': data,
|
||||
'hiddenDependingOnAttribute': [{'key': 'client_type', 'values': ['1']}],
|
||||
|
@ -49,4 +49,4 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
'uri': '/online/poke/' ~ sid ~ '/' ~ clid,
|
||||
'uri_method': 'post',
|
||||
'fields': [
|
||||
{'type': 'text', 'key': 'message', 'label': 'online.poke.message'|trans},
|
||||
]
|
||||
{'type': 'text', 'key': 'message', 'label': 'online.poke.message'|trans},
|
||||
]
|
||||
},
|
||||
{
|
||||
'header_label': 'online.kick'|trans,
|
||||
|
@ -21,8 +21,8 @@
|
|||
'uri': '/online/kick/' ~ sid ~ '/' ~ clid,
|
||||
'uri_method': 'post',
|
||||
'fields': [
|
||||
{'type': 'text', 'key': 'reason', 'label': 'online.kick.reason'|trans},
|
||||
]
|
||||
{'type': 'text', 'key': 'reason', 'label': 'online.kick.reason'|trans},
|
||||
]
|
||||
},
|
||||
{
|
||||
'header_label': 'online.ban'|trans,
|
||||
|
@ -30,9 +30,9 @@
|
|||
'uri': '/online/ban/' ~ sid ~ '/' ~ clid,
|
||||
'uri_method': 'post',
|
||||
'fields': [
|
||||
{'type': 'text', 'key': 'reason', 'label': 'online.ban.reason'|trans},
|
||||
{'type': 'number', 'key': 'time', 'label': 'online.ban.time'|trans}
|
||||
]
|
||||
{'type': 'text', 'key': 'reason', 'label': 'online.ban.reason'|trans},
|
||||
{'type': 'number', 'key': 'time', 'label': 'online.ban.time'|trans}
|
||||
]
|
||||
},
|
||||
{
|
||||
'header_label': 'online.move'|trans,
|
||||
|
@ -40,9 +40,9 @@
|
|||
'uri': '/online/move/' ~ sid ~ '/' ~ clid,
|
||||
'uri_method': 'post',
|
||||
'fields': [
|
||||
{'type': 'select', 'key': 'channel', 'options': channels,'label': 'online.move.channel'|trans},
|
||||
{'type': 'text', 'key': 'channel_password', 'label': 'online.move.channel_password'|trans}
|
||||
]
|
||||
{'type': 'select', 'key': 'channel', 'options': channels,'label': 'online.move.channel'|trans},
|
||||
{'type': 'text', 'key': 'channel_password', 'label': 'online.move.channel_password'|trans}
|
||||
]
|
||||
},
|
||||
{
|
||||
'header_label': 'online.send'|trans,
|
||||
|
@ -50,8 +50,8 @@
|
|||
'uri': '/online/send/' ~ sid ~ '/' ~ clid,
|
||||
'uri_method': 'post',
|
||||
'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_received_total', 'apply': 'file'},
|
||||
]} %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
'uri': '/passwords/add/' ~ sid,
|
||||
'uri_method': 'post',
|
||||
'fields': [
|
||||
{'type': 'text', 'key': 'password', 'label': 'passwords.add.password'|trans},
|
||||
{'type': 'number', 'key': 'duration', 'label': 'passwords.add.duration'|trans},
|
||||
{'type': 'text', 'key': 'description', 'label': 'passwords.add.description'|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': 'password', 'label': 'passwords.add.password'|trans},
|
||||
{'type': 'number', 'key': 'duration', 'label': 'passwords.add.duration'|trans},
|
||||
{'type': 'text', 'key': 'description', 'label': 'passwords.add.description'|trans},
|
||||
{'type': 'select', 'key': 'channel', 'options': channels,'label': 'passwords.add.channel'|trans},
|
||||
{'type': 'text', 'key': 'channel_password', 'label': 'passwords.add.channel_password'|trans},
|
||||
]
|
||||
},
|
||||
]
|
||||
} %}
|
||||
|
@ -49,4 +49,4 @@
|
|||
]
|
||||
} %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -11,15 +11,15 @@
|
|||
<h5>{% trans %}server_info.h.actions{% endtrans %}</h5>
|
||||
{% include 'form.twig' with {
|
||||
'fields': [
|
||||
{
|
||||
'header_label': 'server_info.send'|trans,
|
||||
'label': '<i class="fa fa-check"></i>',
|
||||
'uri': '/servers/send/' ~ sid,
|
||||
'uri_method': 'post',
|
||||
'fields': [
|
||||
{
|
||||
'header_label': 'server_info.send'|trans,
|
||||
'label': '<i class="fa fa-check"></i>',
|
||||
'uri': '/servers/send/' ~ sid,
|
||||
'uri_method': 'post',
|
||||
'fields': [
|
||||
{'type': 'text', 'key': 'message', 'label': 'server_info.send.message'|trans},
|
||||
]
|
||||
}]
|
||||
}]
|
||||
} %}
|
||||
|
||||
<h5>{% trans %}server_info.h.details{% endtrans %}</h5>
|
||||
|
@ -90,4 +90,4 @@
|
|||
]
|
||||
} %}
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
'uri': '/servergroups/add/' ~ sid ~ '/' ~ sgid,
|
||||
'uri_method': 'post',
|
||||
'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} %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -5,42 +5,42 @@
|
|||
|
||||
<h5>{% trans %}servers.h.details{% endtrans %}</h5>
|
||||
{% if data|length > 0 %}
|
||||
{% include 'table.twig' with {'data': data,
|
||||
'filters': [
|
||||
{'key': 'virtualserver_uptime', 'apply': 'timeInSeconds'},
|
||||
],
|
||||
'links': [
|
||||
{'key': 'virtualserver_port', 'uri': '/servers', 'uri_param': 'virtualserver_id'},
|
||||
{'key': 'virtualserver_clientsonline', 'uri': '/online', 'uri_param': 'virtualserver_id'}
|
||||
],
|
||||
'hiddenColumns': ['virtualserver_id', 'virtualserver_queryclientsonline', 'virtualserver_autostart', 'virtualserver_machine_id'],
|
||||
'additional_links': [
|
||||
{
|
||||
'header_label': 'servers.select'|trans,
|
||||
'label': '<i class="fa fa-check"></i>',
|
||||
'uri': '/servers/select',
|
||||
'uri_param': 'virtualserver_id'
|
||||
},
|
||||
{
|
||||
'header_label': 'servers.start'|trans,
|
||||
'label': '<i class="fa fa-play"></i>',
|
||||
'uri': '/servers/start',
|
||||
'uri_param': 'virtualserver_id'
|
||||
},
|
||||
{
|
||||
'header_label': 'servers.stop'|trans,
|
||||
'label': '<i class="fa fa-stop"></i>',
|
||||
'uri': '/servers/stop',
|
||||
'uri_param': 'virtualserver_id'
|
||||
},
|
||||
{
|
||||
'header_label': 'servers.delete'|trans,
|
||||
'label': '<i class="fa fa-trash"></i>',
|
||||
'uri': '/servers/delete',
|
||||
'uri_param': 'virtualserver_id'
|
||||
}
|
||||
],
|
||||
} %}
|
||||
{% include 'table.twig' with {'data': data,
|
||||
'filters': [
|
||||
{'key': 'virtualserver_uptime', 'apply': 'timeInSeconds'},
|
||||
],
|
||||
'links': [
|
||||
{'key': 'virtualserver_port', 'uri': '/servers', 'uri_param': 'virtualserver_id'},
|
||||
{'key': 'virtualserver_clientsonline', 'uri': '/online', 'uri_param': 'virtualserver_id'}
|
||||
],
|
||||
'hiddenColumns': ['virtualserver_id', 'virtualserver_queryclientsonline', 'virtualserver_autostart', 'virtualserver_machine_id'],
|
||||
'additional_links': [
|
||||
{
|
||||
'header_label': 'servers.select'|trans,
|
||||
'label': '<i class="fa fa-check"></i>',
|
||||
'uri': '/servers/select',
|
||||
'uri_param': 'virtualserver_id'
|
||||
},
|
||||
{
|
||||
'header_label': 'servers.start'|trans,
|
||||
'label': '<i class="fa fa-play"></i>',
|
||||
'uri': '/servers/start',
|
||||
'uri_param': 'virtualserver_id'
|
||||
},
|
||||
{
|
||||
'header_label': 'servers.stop'|trans,
|
||||
'label': '<i class="fa fa-stop"></i>',
|
||||
'uri': '/servers/stop',
|
||||
'uri_param': 'virtualserver_id'
|
||||
},
|
||||
{
|
||||
'header_label': 'servers.delete'|trans,
|
||||
'label': '<i class="fa fa-trash"></i>',
|
||||
'uri': '/servers/delete',
|
||||
'uri_param': 'virtualserver_id'
|
||||
}
|
||||
],
|
||||
} %}
|
||||
{% else %}
|
||||
{% include 'no_entities.twig' %}
|
||||
{% endif %}
|
||||
|
@ -54,11 +54,11 @@
|
|||
'uri': '/servers/create',
|
||||
'uri_method': 'post',
|
||||
'fields': [
|
||||
{'type': 'text', 'key': 'VIRTUALSERVER_NAME', 'label': 'server_create.VIRTUALSERVER_NAME'|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_MAXCLIENTS', 'label': 'server_create.VIRTUALSERVER_MAXCLIENTS'|trans},
|
||||
]
|
||||
{'type': 'text', 'key': 'VIRTUALSERVER_NAME', 'label': 'server_create.VIRTUALSERVER_NAME'|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_MAXCLIENTS', 'label': 'server_create.VIRTUALSERVER_MAXCLIENTS'|trans},
|
||||
]
|
||||
}]
|
||||
} %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -11,21 +11,21 @@
|
|||
|
||||
{% include 'table.twig' with {'data': data,
|
||||
|
||||
'additional_links': [
|
||||
{
|
||||
'header_label': 'snapshots.deploy'|trans,
|
||||
'label': '<i class="fa fa-check"></i>',
|
||||
'uri': '/snapshots/deploy/' ~ sid,
|
||||
'uri_param': 'name'
|
||||
},
|
||||
{
|
||||
'header_label': 'snapshots.delete'|trans,
|
||||
'label': '<i class="fa fa-trash"></i>',
|
||||
'uri': '/snapshots/delete/' ~ sid,
|
||||
'uri_param': 'name'
|
||||
}
|
||||
],
|
||||
'additional_links': [
|
||||
{
|
||||
'header_label': 'snapshots.deploy'|trans,
|
||||
'label': '<i class="fa fa-check"></i>',
|
||||
'uri': '/snapshots/deploy/' ~ sid,
|
||||
'uri_param': 'name'
|
||||
},
|
||||
{
|
||||
'header_label': 'snapshots.delete'|trans,
|
||||
'label': '<i class="fa fa-trash"></i>',
|
||||
'uri': '/snapshots/delete/' ~ sid,
|
||||
'uri_param': 'name'
|
||||
}
|
||||
],
|
||||
|
||||
} %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
Reference in a new issue