Added WS_API_AUTO environment variable which can autoconfigure the WebUI
This commit is contained in:
29
FAQ.md
29
FAQ.md
@@ -582,6 +582,31 @@ https://watchstate.example.org {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### WS_API_AUTO
|
||||||
|
|
||||||
|
The purpose of this environment variable is to automate the configuration process. It's mainly used for people who uses many browsers
|
||||||
|
to access the `WebUI` and want to automate the configuration process. as it's requires the API settings to be configured before it
|
||||||
|
can be used. This environment variable can be enabled by setting `WS_API_AUTO=true` in `${WS_DATA_PATH}/config/.env`.
|
||||||
|
|
||||||
|
#### Why you should use it?
|
||||||
|
|
||||||
|
You normally should not use it, as it's a **GREAT SECURITY RISK**. However, if you are using the tool in a secure environment
|
||||||
|
and not worried about exposing your API key, you can use it to automate the configuration process.
|
||||||
|
|
||||||
|
#### Why you should not use it?
|
||||||
|
|
||||||
|
Because, by exposing your API key, you are also exposing every data you have in the tool. This is a **GREAT SECURITY RISK**,
|
||||||
|
any person or bot that are able to access the `WebUI` will also be able to visit `/v1/api/system/auto` and get your API key. And with this key
|
||||||
|
they can do anything they want with your data. including viewing your media servers api keys.
|
||||||
|
|
||||||
|
So, please while we have this option available, we strongly recommend not to use it if `WatchState` is exposed to the internet.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> This environment variable is **GREAT SECURITY RISK**, and we strongly recommend not to use it if `WatchState` is exposed to the internet.
|
||||||
|
> I cannot stress this enough, please do not use it unless you are in a secure environment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### How to disable the included cache server and use external cache server?
|
### How to disable the included cache server and use external cache server?
|
||||||
|
|
||||||
Set this environment variable in your `compose.yaml` file `DISABLE_CACHE` with value of `1`. to use external redis server
|
Set this environment variable in your `compose.yaml` file `DISABLE_CACHE` with value of `1`. to use external redis server
|
||||||
@@ -745,7 +770,7 @@ Once that is done you are ready to compile the `WebUI`.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cd frontend
|
$ cd frontend
|
||||||
$ yarn install --production --prefer-offline --frozen-lockfile
|
$ yarn install --production --prefer-offline --frozen-lockfile && yarn run generate
|
||||||
```
|
```
|
||||||
|
|
||||||
There should be a new directory called `exported`, you need to move that folder to the `public` directory.
|
There should be a new directory called `exported`, you need to move that folder to the `public` directory.
|
||||||
@@ -761,7 +786,7 @@ ws:/opt/app/public$ ls
|
|||||||
exported index.php
|
exported index.php
|
||||||
```
|
```
|
||||||
|
|
||||||
There must be exactly one `index.php` file and one `exported` directory. inside that directory.
|
There must be exactly one `index.php` file and one `exported` directory. inside that directory, or if you prefer, you can add `WS_WEBUI_PATH` environment variable to point to the `exported` directory.
|
||||||
|
|
||||||
* link the app to the frontend proxy. For caddy, you can use the following configuration.
|
* link the app to the frontend proxy. For caddy, you can use the following configuration.
|
||||||
|
|
||||||
|
|||||||
16
NEWS.md
16
NEWS.md
@@ -1,5 +1,21 @@
|
|||||||
# Old Updates
|
# Old Updates
|
||||||
|
|
||||||
|
### 2024-05-14
|
||||||
|
|
||||||
|
We are happy to announce the beta testing of the `WebUI`. To get started on using it you just need to visit the url `http://localhost:8080` We are supposed to
|
||||||
|
enabled it by default tomorrow, but we decided to give you a head start. We are looking forward to your feedback. If you don't use the `WebUI` then you need to
|
||||||
|
add the environment variable `WEBUI_ENABLED=0` in your `compose.yaml` file. and restart the container.
|
||||||
|
|
||||||
|
### 2024-05-13
|
||||||
|
|
||||||
|
In preparation for the beta testing of `WebUI` in two days, we have made little breaking change, we have changed the
|
||||||
|
environment variable `WS_WEBUI_ENABLED` to just `WEBUI_ENABLED`, We made this change to make sure people don't disable
|
||||||
|
the `WebUI`by mistake via the environment page in the `WebUI`. The `WebUI` will be enabled by default, in two days from
|
||||||
|
now, to disable it from now add `WEBUI_ENABLED=false` to your `compose.yaml` file. As this environment variable is
|
||||||
|
system level, it cannot be set via `.env` file.
|
||||||
|
|
||||||
|
Note: `WS_WEBUI_ENABLED` will be gone in few weeks, However it will still work for now, if `WEBUI_ENABLED` is not set.
|
||||||
|
|
||||||
### 2024-05-05
|
### 2024-05-05
|
||||||
|
|
||||||
**Edit** - We received requests that people are exposing watchstate externally, and there was concern that having open
|
**Edit** - We received requests that people are exposing watchstate externally, and there was concern that having open
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -7,29 +7,29 @@
|
|||||||
This tool primary goal is to sync your backends play state without relying on third party services,
|
This tool primary goal is to sync your backends play state without relying on third party services,
|
||||||
out of the box, this tool support `Jellyfin`, `Plex` and `Emby` media servers.
|
out of the box, this tool support `Jellyfin`, `Plex` and `Emby` media servers.
|
||||||
|
|
||||||
## updates
|
## Updates
|
||||||
|
|
||||||
### 2024-05-14
|
### 2024-06-23
|
||||||
|
|
||||||
We are happy to announce the beta testing of the `WebUI`. To get started on using it you just need to visit the url `http://localhost:8080` We are supposed to
|
WE are happy to announce that the `WebUI` is ready for wider usage and we are planning to release it in the next few months.
|
||||||
enabled it by default tomorrow, but we decided to give you a head start. We are looking forward to your feedback. If you don't use the `WebUI` then you need to
|
We are actively working on it to improve it. If you have any feedback or suggestions, please let us know. We feel it's almost future complete
|
||||||
add the environment variable `WEBUI_ENABLED=0` in your `compose.yaml` file. and restart the container.
|
for the things that we want.
|
||||||
|
|
||||||
### 2024-05-13
|
On another related news, we have added new environment variable `WS_API_AUTO` "disabled by default" which can be used
|
||||||
|
to automatically expose your **API KEY/TOKEN**. This is useful for users who are using the `WebUI` from many different browsers
|
||||||
|
and want to automate the configuration process.
|
||||||
|
|
||||||
In preparation for the beta testing of `WebUI` in two days, we have made little breaking change, we have changed the
|
While the `WebUI` is included in the main project, it's a standalone feature and requires the API settings to be configured before it
|
||||||
environment variable `WS_WEBUI_ENABLED` to just `WEBUI_ENABLED`, We made this change to make sure people don't disable
|
can be used. This environment variable can be enabled by setting `WS_API_AUTO=true` in `${WS_DATA_PATH}/config/.env`.
|
||||||
the `WebUI`by mistake via the environment page in the `WebUI`. The `WebUI` will be enabled by default, in two days from
|
|
||||||
now, to disable it from now add `WEBUI_ENABLED=false` to your `compose.yaml` file. As this environment variable is
|
|
||||||
system level, it cannot be set via `.env` file.
|
|
||||||
|
|
||||||
Note: `WS_WEBUI_ENABLED` will be gone in few weeks, However it will still work for now, if `WEBUI_ENABLED` is not set.
|
> [!IMPORTANT]
|
||||||
|
> This environment variable is **GREAT SECURITY RISK**, and we strongly recommend not to use it if `WatchState` is exposed to the internet.
|
||||||
|
|
||||||
Refer to [NEWS](NEWS.md) for old updates.
|
Refer to [NEWS](NEWS.md) for old updates.
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
* **NEW** WebUI. (Preview).
|
* WebUI.
|
||||||
* Sync backends play state (from many to many).
|
* Sync backends play state (from many to many).
|
||||||
* Backup your backends play state into `portable` format.
|
* Backup your backends play state into `portable` format.
|
||||||
* Receive Webhook events from media backends.
|
* Receive Webhook events from media backends.
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ return (function () {
|
|||||||
'prefix' => '/v1/api',
|
'prefix' => '/v1/api',
|
||||||
'key' => env('WS_API_KEY', null),
|
'key' => env('WS_API_KEY', null),
|
||||||
'secure' => (bool)env('WS_SECURE_API_ENDPOINTS', false),
|
'secure' => (bool)env('WS_SECURE_API_ENDPOINTS', false),
|
||||||
|
'auto' => (bool)env('WS_API_AUTO', false),
|
||||||
'pattern_match' => [
|
'pattern_match' => [
|
||||||
'backend' => '[a-zA-Z0-9_-]+',
|
'backend' => '[a-zA-Z0-9_-]+',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -150,6 +150,12 @@ return (function () {
|
|||||||
'description' => 'Expose debug information in the API when an error occurs.',
|
'description' => 'Expose debug information in the API when an error occurs.',
|
||||||
'type' => 'bool',
|
'type' => 'bool',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'key' => 'WS_API_AUTO',
|
||||||
|
'description' => 'PUBLICLY EXPOSE the api token for automated WebUI configuration. This should NEVER be enabled if WatchState is exposed to the internet.',
|
||||||
|
'danger' => true,
|
||||||
|
'type' => 'bool',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$validateCronExpression = function (string $value): string {
|
$validateCronExpression = function (string $value): string {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ onMounted(() => fetch(`${api_url.value}${props.file}`).then(response => response
|
|||||||
text = text.replace(/\[!IMPORTANT\]/g, `
|
text = text.replace(/\[!IMPORTANT\]/g, `
|
||||||
<span class="is-block title is-4">
|
<span class="is-block title is-4">
|
||||||
<span class="icon-text">
|
<span class="icon-text">
|
||||||
<span class="icon"><i class="fas fa-exclamation-triangle has-text-danger"></i></span>
|
<span class="icon"><i class="fas fa-exclamation-triangle has-text-danger fa-fade"></i></span>
|
||||||
<span>Important</span>
|
<span>Important</span>
|
||||||
</span>
|
</span>
|
||||||
</span>`)
|
</span>`)
|
||||||
|
|||||||
@@ -252,13 +252,38 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12 mt-2">
|
||||||
|
<Message title="Information" message_class="has-background-info-90 has-text-dark"
|
||||||
|
icon="fas fa-info-circle">
|
||||||
|
<p>
|
||||||
|
It's possible to automatically setup the API connection for this client and <strong
|
||||||
|
class="has-text-danger">ALL VISITORS</strong> by setting the following environment variable <code>WS_API_AUTO=true</code>
|
||||||
|
in <code>/config/.env</code> file. Understand that this option <strong class="has-text-danger">PUBLICLY
|
||||||
|
EXPOSES YOUR API TOKEN</strong> to <u>ALL VISITORS</u>. Anyone who is able to reach this page will be
|
||||||
|
granted access to your <code>WatchState API</code> which exposes your other media backends data including
|
||||||
|
their secrets. <strong>this option is great security risk and SHOULD NEVER be used if
|
||||||
|
<code>WatchState</code> is exposed to the internet.</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Please visit
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fab fa-github"></i>
|
||||||
|
</span>
|
||||||
|
<NuxtLink target="_blank" to="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#ws_api_auto">
|
||||||
|
This link
|
||||||
|
</NuxtLink>
|
||||||
|
. to learn more, this environment variable is important enough to have its own section entry in the FAQ.
|
||||||
|
</p>
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="!api_url || !api_token">
|
<template v-if="!hasAPISettings">
|
||||||
<no-api/>
|
<no-api/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<slot v-if="!showConnection"/>
|
<slot v-if="!showConnection && hasAPISettings"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="columns is-multiline is-mobile mt-3">
|
<div class="columns is-multiline is-mobile mt-3">
|
||||||
@@ -297,7 +322,7 @@ import {dEvent} from '~/utils/index.js'
|
|||||||
const selectedTheme = useStorage('theme', (() => window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')())
|
const selectedTheme = useStorage('theme', (() => window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')())
|
||||||
const showConnection = ref(false)
|
const showConnection = ref(false)
|
||||||
|
|
||||||
const real_api_url = useStorage('api_url', '')
|
const real_api_url = useStorage('api_url', window.location.origin)
|
||||||
const real_api_path = useStorage('api_path', '/v1/api')
|
const real_api_path = useStorage('api_path', '/v1/api')
|
||||||
const real_api_token = useStorage('api_token', '')
|
const real_api_token = useStorage('api_token', '')
|
||||||
|
|
||||||
@@ -357,9 +382,12 @@ const applyPreferredColorScheme = scheme => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
applyPreferredColorScheme(selectedTheme.value)
|
applyPreferredColorScheme(selectedTheme.value)
|
||||||
|
|
||||||
|
if ('' === api_token.value) {
|
||||||
|
await autoConfig()
|
||||||
|
}
|
||||||
|
|
||||||
if ('' === api_token.value || '' === api_url.value) {
|
if ('' === api_token.value || '' === api_url.value) {
|
||||||
showConnection.value = true
|
showConnection.value = true
|
||||||
return
|
return
|
||||||
@@ -430,18 +458,47 @@ const getVersion = async (updateStatus = true) => {
|
|||||||
api_status.value = true
|
api_status.value = true
|
||||||
api_response.value = 'Status: OK'
|
api_response.value = 'Status: OK'
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 'Unknown'
|
return 'Unknown'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const autoConfig = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${api_url.value}${api_path.value}/system/auto`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({'origin': window.location.origin})
|
||||||
|
})
|
||||||
|
|
||||||
|
const json = await response.json()
|
||||||
|
|
||||||
|
if (200 !== response.status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!api_url.value) {
|
||||||
|
api_url.value = json.url
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!api_path.value) {
|
||||||
|
api_path.value = json.path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!api_token.value) {
|
||||||
|
api_token.value = json.token
|
||||||
|
}
|
||||||
|
|
||||||
|
await testApi();
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const setOrigin = () => api_url.value = window.location.origin;
|
const setOrigin = () => api_url.value = window.location.origin;
|
||||||
|
|
||||||
const hasAPISettings = computed(() => '' !== real_api_token.value && '' !== real_api_url.value)
|
const hasAPISettings = computed(() => '' !== real_api_token.value && '' !== real_api_url.value)
|
||||||
|
|
||||||
const closeOverlay = () => {
|
const closeOverlay = () => loadFile.value = ''
|
||||||
loadFile.value = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -88,7 +88,9 @@
|
|||||||
<input class="input" id="form_value" type="text" placeholder="Value" v-model="form_value">
|
<input class="input" id="form_value" type="text" placeholder="Value" v-model="form_value">
|
||||||
<div class="icon is-small is-left"><i class="fas fa-font"></i></div>
|
<div class="icon is-small is-left"><i class="fas fa-font"></i></div>
|
||||||
</template>
|
</template>
|
||||||
<p class="help" v-html="getHelp(form_key)"></p>
|
<div>
|
||||||
|
<p class="help" v-html="getHelp(form_key)"></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,11 +117,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="column is-12" v-if="items">
|
<div v-else class="column is-12" v-if="items">
|
||||||
<div class="columns is-multiline">
|
<div class="columns is-multiline">
|
||||||
<div class="column is-4" v-for="item in filteredRows(items)" :key="item.key">
|
<div class="column" v-for="item in filteredRows(items)" :key="item.key"
|
||||||
<div class="card">
|
:class="{'is-4':!item?.danger,'is-12':item.danger}">
|
||||||
|
<div class="card" :class="{ 'is-danger': item?.danger }">
|
||||||
<header class="card-header">
|
<header class="card-header">
|
||||||
<p class="card-header-title is-unselectable">
|
<p class="card-header-title is-unselectable">
|
||||||
<span class="has-tooltip is-clickable" v-tooltip="item.description">{{ item.key }}</span>
|
<template v-if="item?.danger">
|
||||||
|
<span class="title is-5 ">
|
||||||
|
<span class="icon" v-tooltip="'This option is considered dangerous.'">
|
||||||
|
<i class="has-text-danger fas fa-exclamation-triangle"></i>
|
||||||
|
</span> {{ item.key }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="has-tooltip is-clickable" v-tooltip="item.description">
|
||||||
|
{{ item.key }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
</p>
|
</p>
|
||||||
<span class="card-header-icon" v-if="item.mask" @click="item.mask = false" v-tooltip="'Unmask the value'">
|
<span class="card-header-icon" v-if="item.mask" @click="item.mask = false" v-tooltip="'Unmask the value'">
|
||||||
<span class="icon"><i class="fas fa-unlock"></i></span>
|
<span class="icon"><i class="fas fa-unlock"></i></span>
|
||||||
@@ -140,6 +154,10 @@
|
|||||||
:class="{ 'is-masked': item.mask, 'is-unselectable': item.mask }"
|
:class="{ 'is-masked': item.mask, 'is-unselectable': item.mask }"
|
||||||
@click="(e) => e.target.classList.toggle('is-text-overflow')">
|
@click="(e) => e.target.classList.toggle('is-text-overflow')">
|
||||||
{{ item.value }}</p>
|
{{ item.value }}</p>
|
||||||
|
|
||||||
|
<p v-if="item?.danger" class="title is-5 has-text-danger">
|
||||||
|
{{ item.description }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
@@ -181,6 +199,10 @@
|
|||||||
<li>Some values are too large to fit into the view, clicking on the value will show the full value.</li>
|
<li>Some values are too large to fit into the view, clicking on the value will show the full value.</li>
|
||||||
<li>These values are loaded from the <code>{{ file }}</code> file.</li>
|
<li>These values are loaded from the <code>{{ file }}</code> file.</li>
|
||||||
<li>To add a new variable click on the <i class="fa fa-add"></i> button.</li>
|
<li>To add a new variable click on the <i class="fa fa-add"></i> button.</li>
|
||||||
|
<li>Environment variables with <span class="has-text-danger">red borders</span> and <i
|
||||||
|
class="fas fa-exclamation-triangle"></i> icon are considered
|
||||||
|
dangerous. Please be careful when editing them.
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Message>
|
</Message>
|
||||||
</div>
|
</div>
|
||||||
@@ -359,6 +381,10 @@ const getHelp = key => {
|
|||||||
|
|
||||||
let text = `${data[0].description}`
|
let text = `${data[0].description}`
|
||||||
|
|
||||||
|
if (data[0]?.danger) {
|
||||||
|
text = `<span class="has-text-danger title is-5"> <i class="has-text-warning fas fa-exclamation-triangle fa-bounce"></i> ${text}</span>`
|
||||||
|
}
|
||||||
|
|
||||||
if (data[0]?.type) {
|
if (data[0]?.type) {
|
||||||
text += ` Expects: <code>${data[0].type}</code>`
|
text += ` Expects: <code>${data[0].type}</code>`
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/API/System/AutoConfig.php
Normal file
33
src/API/System/AutoConfig.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\API\System;
|
||||||
|
|
||||||
|
use App\Libs\Attributes\Route\Post;
|
||||||
|
use App\Libs\Config;
|
||||||
|
use App\Libs\DataUtil;
|
||||||
|
use App\Libs\HTTP_STATUS;
|
||||||
|
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface as iRequest;
|
||||||
|
|
||||||
|
final class AutoConfig
|
||||||
|
{
|
||||||
|
public const string URL = '%{api.prefix}/system/auto';
|
||||||
|
|
||||||
|
#[Post(self::URL . '[/]', name: 'system.autoconfig')]
|
||||||
|
public function __invoke(iRequest $request): iResponse
|
||||||
|
{
|
||||||
|
if (false === (bool)Config::get('api.auto', false)) {
|
||||||
|
return api_error('auto configuration is disabled.', HTTP_STATUS::HTTP_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = DataUtil::fromRequest($request);
|
||||||
|
|
||||||
|
return api_response(HTTP_STATUS::HTTP_OK, [
|
||||||
|
'url' => $data->get('origin', ag($_SERVER, 'HTTP_ORIGIN', 'localhost')),
|
||||||
|
'path' => Config::get('api.prefix'),
|
||||||
|
'token' => Config::get('api.key'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Libs\Middlewares;
|
namespace App\Libs\Middlewares;
|
||||||
|
|
||||||
|
use App\API\System\AutoConfig;
|
||||||
use App\API\System\HealthCheck;
|
use App\API\System\HealthCheck;
|
||||||
use App\Libs\Config;
|
use App\Libs\Config;
|
||||||
use App\Libs\HTTP_STATUS;
|
use App\Libs\HTTP_STATUS;
|
||||||
@@ -22,6 +23,7 @@ final class APIKeyRequiredMiddleware implements MiddlewareInterface
|
|||||||
*/
|
*/
|
||||||
private const array PUBLIC_ROUTES = [
|
private const array PUBLIC_ROUTES = [
|
||||||
HealthCheck::URL,
|
HealthCheck::URL,
|
||||||
|
AutoConfig::URL,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user