Make the first time webui access more user-friendly.
This commit is contained in:
@@ -151,36 +151,20 @@
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="has-text-left">
|
||||
<span class="icon has-text-danger"><i class="fas fa-info"/></span>
|
||||
<span>These settings are stored locally in your browser. You need to re-add them if you access the
|
||||
<code>WebUI</code> from different browser.
|
||||
</span>
|
||||
</p>
|
||||
</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"/>
|
||||
</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>
|
||||
<ul>
|
||||
<li>
|
||||
These settings are stored locally in your browser. You need to re-add them if you access the
|
||||
<strong>WebUI</strong> from different browser.
|
||||
</li>
|
||||
<li>
|
||||
The very first time you access the <strong>WebUI</strong>, it will auto configure the connection if
|
||||
possible.
|
||||
</li>
|
||||
</ul>
|
||||
</Message>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<div class="column is-12 is-clearfix is-unselectable">
|
||||
<span class="title is-4">
|
||||
<span class="icon"><i class="fas fa-server"/></span>
|
||||
<span v-if="api_user"> {{ ucFirst(api_user) }} @</span>
|
||||
Backends
|
||||
</span>
|
||||
<div class="is-pulled-right">
|
||||
@@ -23,7 +24,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="is-hidden-mobile">
|
||||
<span class="subtitle">This page contains all the backends that are currently configured.</span>
|
||||
<span class="subtitle">Backends that are configured for the specific user.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,22 +47,6 @@
|
||||
</Message>
|
||||
</div>
|
||||
|
||||
<div class="column is-12">
|
||||
<div class="content">
|
||||
<h1 class="title is-4">
|
||||
<span class="icon"><i class="fas fa-tools"/></span> Tools
|
||||
</h1>
|
||||
<ul>
|
||||
<li>
|
||||
<NuxtLink :to="`/tools/plex_token`" v-text="'Validate plex token'"/>
|
||||
</li>
|
||||
<li v-if="backends && backends.length>0 && 'main' === api_user">
|
||||
<NuxtLink :to="`/tools/sub_users`" v-text="'Create sub-users'"/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="backend in backends" :key="backend.name" class="column is-6-tablet is-12-mobile">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
@@ -76,11 +61,13 @@
|
||||
<NuxtLink :to="`/backend/${backend.name}/edit?redirect=/backends`"
|
||||
v-tooltip="'Edit backend settings'">
|
||||
<span class="icon has-text-warning"><i class="fas fa-cog"/></span>
|
||||
<span class="is-hidden-mobile">Edit</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="control">
|
||||
<NuxtLink :to="`/backend/${backend.name}/delete?redirect=/backends`" v-tooltip="'Delete backend'">
|
||||
<span class="icon has-text-danger"><i class="fas fa-trash"/></span>
|
||||
<span class="is-hidden-mobile">Delete</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,7 +118,9 @@
|
||||
<input :id="backend.name+'_export'" type="checkbox" class="switch is-success"
|
||||
:checked="backend.export.enabled"
|
||||
@change="updateValue(backend, 'export.enabled', !backend.export.enabled)">
|
||||
<label :for="backend.name+'_export'">Export</label>
|
||||
<label class="has-tooltip" :for="backend.name+'_export'"
|
||||
v-tooltip="'Send data from watchstate to this backend.'"
|
||||
>Export</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer-item">
|
||||
@@ -139,7 +128,9 @@
|
||||
<input :id="backend.name+'_import'" type="checkbox" class="switch is-success"
|
||||
:checked="backend.import.enabled"
|
||||
@change="updateValue(backend, 'import.enabled',!backend.import.enabled)">
|
||||
<label :for="backend.name+'_import'">Import</label>
|
||||
<label class="has-tooltip" :for="backend.name+'_import'"
|
||||
v-tooltip="'Get data from this backend into watchstate.'">
|
||||
Import</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer-item">
|
||||
@@ -177,10 +168,15 @@
|
||||
@toggle="show_page_tips = !show_page_tips" :use-toggle="true" title="Tips" icon="fas fa-info-circle">
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Import</strong> means pulling data from the backends into the local database.
|
||||
Think of the WatchState as a <strong>Central Hub</strong> your backends aren't aware of each other.
|
||||
<strong>WatchState</strong> is the facilitator of the data flow. If you think like that, then <strong>import</strong>
|
||||
and <strong>export</strong> would make sense.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Export</strong> means pushing data from the local database to the backends.
|
||||
<strong>Import</strong>: Means getting data from the backend into watchstate.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Export</strong>: Means sending data from watchstate to the backend.
|
||||
</li>
|
||||
</ul>
|
||||
</Message>
|
||||
@@ -278,6 +274,7 @@ const loadContent = async () => {
|
||||
return
|
||||
}
|
||||
backends.value = json
|
||||
useHead({title: `${ucFirst(api_user.value)} @ Backends`})
|
||||
} catch (e) {
|
||||
notification('error', 'Error', `Failed to load backends. ${e.message}`)
|
||||
} finally {
|
||||
|
||||
@@ -8,17 +8,44 @@ use App\Libs\Attributes\Route\Post;
|
||||
use App\Libs\Config;
|
||||
use App\Libs\DataUtil;
|
||||
use App\Libs\Enums\Http\Status;
|
||||
use App\Libs\Mappers\ImportInterface as iImport;
|
||||
use App\Libs\Stream;
|
||||
use App\Libs\Traits\APITraits;
|
||||
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||
use Psr\Http\Message\ServerRequestInterface as iRequest;
|
||||
use Psr\Log\LoggerInterface as iLogger;
|
||||
use Throwable;
|
||||
|
||||
final class AutoConfig
|
||||
{
|
||||
use APITraits;
|
||||
|
||||
public const string URL = '%{api.prefix}/system/auto';
|
||||
|
||||
public function __construct(private readonly iImport $mapper, private readonly iLogger $logger)
|
||||
{
|
||||
}
|
||||
|
||||
#[Post(self::URL . '[/]', name: 'system.autoconfig')]
|
||||
public function __invoke(iRequest $request): iResponse
|
||||
{
|
||||
if (false === (bool)Config::get('api.auto', false)) {
|
||||
$isEnabled = false;
|
||||
try {
|
||||
$initial_file = Config::get('path') . '/config/disable_auto_config.txt';
|
||||
if (false === file_exists($initial_file)) {
|
||||
$uc = $this->getUserContext($request, $this->mapper, $this->logger);
|
||||
$isEnabled = 'main' === $uc->name && count($uc->config) < 1;
|
||||
$stream = Stream::make($initial_file, 'w+');
|
||||
$stream->write(r('Auto configure was called and disabled at {time}', [
|
||||
'time' => makeDate('now'),
|
||||
]));
|
||||
$stream->close();
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
syslog(LOG_ERR, __METHOD__ . ' Exception: ' . $e->getMessage() . ' ' . $e->getTraceAsString());
|
||||
}
|
||||
|
||||
if (false === $isEnabled && false === (bool)Config::get('api.auto', false)) {
|
||||
return api_error('auto configuration is disabled.', Status::FORBIDDEN);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,17 +9,31 @@ use App\Libs\Attributes\Route\Get;
|
||||
use App\Libs\Database\DBLayer;
|
||||
use App\Libs\Enums\Http\Method;
|
||||
use App\Libs\Enums\Http\Status;
|
||||
use App\Libs\Mappers\ImportInterface as iImport;
|
||||
use App\Libs\Traits\APITraits;
|
||||
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||
use Psr\Http\Message\ServerRequestInterface as iRequest;
|
||||
use Psr\Log\LoggerInterface as iLogger;
|
||||
use RuntimeException;
|
||||
|
||||
final class Images
|
||||
{
|
||||
use APITraits;
|
||||
|
||||
public const string URL = '%{api.prefix}/system/images';
|
||||
|
||||
public function __construct(private readonly iImport $mapper, private readonly iLogger $logger)
|
||||
{
|
||||
}
|
||||
|
||||
#[Get(self::URL . '/{type:poster|background}[/]', name: 'system.images')]
|
||||
public function __invoke(DBLayer $db, string $type): iResponse
|
||||
public function __invoke(iRequest $request, DBLayer $db, string $type): iResponse
|
||||
{
|
||||
try {
|
||||
$uc = $this->getUserContext($request, $this->mapper, $this->logger);
|
||||
if (count($uc->config) < 1) {
|
||||
return api_response(Status::NO_CONTENT);
|
||||
}
|
||||
$resp = $this->getImage($db, $type);
|
||||
} catch (RuntimeException) {
|
||||
return api_response(Status::NO_CONTENT);
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Libs;
|
||||
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -16,7 +17,7 @@ use Symfony\Component\Yaml\Yaml;
|
||||
*
|
||||
* The ConfigFile class represents a configuration file.
|
||||
*/
|
||||
final class ConfigFile implements ArrayAccess, LoggerAwareInterface
|
||||
final class ConfigFile implements ArrayAccess, LoggerAwareInterface, Countable
|
||||
{
|
||||
private const array CONTENT_TYPES = ['yaml', 'json', 'yml'];
|
||||
private array $data = [];
|
||||
@@ -368,4 +369,9 @@ final class ConfigFile implements ArrayAccess, LoggerAwareInterface
|
||||
{
|
||||
return hash_file('sha256', $this->file);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1275,7 +1275,7 @@ if (!function_exists('getUsersContext')) {
|
||||
$configs = [
|
||||
'main' => new UserContext(
|
||||
name: 'main',
|
||||
config: ConfigFile::open(Config::get('backends_file'), 'yaml'),
|
||||
config: ConfigFile::open(Config::get('backends_file'), 'yaml', autoCreate: true),
|
||||
mapper: $mapper,
|
||||
cache: Container::get(iCache::class),
|
||||
db: Container::get(iDB::class)->setOptions($dbOpts),
|
||||
|
||||
Reference in New Issue
Block a user