Archived
1
0
Fork 0

Use 4 spaces as tabs and reformat

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

6
.editorconfig Normal file
View file

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

View file

@ -3,18 +3,22 @@
## 2.2.4 - UNRELEASED
## 2.2.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
View file

@ -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`.

View file

@ -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'),
]);
}
}
}

View file

@ -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');
}
}
}

View file

@ -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')
]);
}
}
}

View file

@ -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)
]);
}
}
}

View file

@ -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')
]);
}
}
}

View file

@ -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')
]);
}
}
}

View file

@ -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')
]);
}
}
}

View file

@ -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),
]);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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
);
}
}
}
}

View file

@ -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';
}
}
}

View file

@ -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;
}
}
}

View file

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

View file

@ -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;
}
}

View file

@ -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 %}

View file

@ -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>

View file

@ -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 %}

View file

@ -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>

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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>

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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>

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}