Enable WebUI by default.
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
**/.git
|
**/.git
|
||||||
**/vendor
|
**/vendor
|
||||||
./var/*
|
./var/*
|
||||||
|
**/screenshots
|
||||||
!./var/.gitignore
|
!./var/.gitignore
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
frontend/.nuxt
|
frontend/.nuxt
|
||||||
|
|||||||
21
NEWS.md
21
NEWS.md
@@ -1,5 +1,26 @@
|
|||||||
# Old Updates
|
# Old Updates
|
||||||
|
|
||||||
|
### 2024-05-05
|
||||||
|
|
||||||
|
**Edit** - We received requests that people are exposing watchstate externally, and there was concern that having open
|
||||||
|
webhook endpoints might lead to abuse. As such, we have added a new environment variable `WS_SECURE_API_ENDPOINTS`. Simply set
|
||||||
|
the environment variable to `1` to secure the webhook endpoint. This means you have to add `?apikey=yourapikey` to the end
|
||||||
|
of the webhook endpoint.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
We are deprecating the use of the following environment
|
||||||
|
variables `WS_DISABLE_HTTP`, `WS_DISABLE_CRON`, `WS_DISABLE_CACHE`,
|
||||||
|
and replacing them with `DISABLE_CACHE`, `DISABLE_CRON`, `DISABLE_HTTP`. The old environment variables will be removed
|
||||||
|
in the future versions.
|
||||||
|
It doesn't make sense to mark them as `WS_` since they are global and do not relate to the tool itself. And they must be
|
||||||
|
set from the `compose.yaml` file itself.
|
||||||
|
|
||||||
|
### 2024-05-04
|
||||||
|
|
||||||
|
The new webhook endpoint no longer requires a key, and it's now open to public you just need to specify the backend
|
||||||
|
name.
|
||||||
|
|
||||||
### 2024-04-30 - [BREAKING CHANGE]
|
### 2024-04-30 - [BREAKING CHANGE]
|
||||||
|
|
||||||
We are going to retire the old webhooks endpoint, please refer to the [FAQ](FAQ.md#how-to-add-webhooks) to know how to
|
We are going to retire the old webhooks endpoint, please refer to the [FAQ](FAQ.md#how-to-add-webhooks) to know how to
|
||||||
|
|||||||
74
README.md
74
README.md
@@ -9,6 +9,12 @@ out of the box, this tool support `Jellyfin`, `Plex` and `Emby` media servers.
|
|||||||
|
|
||||||
## updates
|
## 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
|
### 2024-05-13
|
||||||
|
|
||||||
In preparation for the beta testing of `WebUI` in two days, we have made little breaking change, we have changed the
|
In preparation for the beta testing of `WebUI` in two days, we have made little breaking change, we have changed the
|
||||||
@@ -19,33 +25,11 @@ 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.
|
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
|
|
||||||
|
|
||||||
**Edit** - We received requests that people are exposing watchstate externally, and there was concern that having open
|
|
||||||
webhook
|
|
||||||
endpoints might lead to abuse. As such, we have added a new environment variable `WS_SECURE_API_ENDPOINTS`. Simply set
|
|
||||||
the environment variable to `1` to secure the webhook endpoint. This means you have to add `?apikey=yourapikey` to the
|
|
||||||
end
|
|
||||||
of the webhook endpoint.
|
|
||||||
|
|
||||||
-----
|
|
||||||
|
|
||||||
We are deprecating the use of the following environment
|
|
||||||
variables `WS_DISABLE_HTTP`, `WS_DISABLE_CRON`, `WS_DISABLE_CACHE`,
|
|
||||||
and replacing them with `DISABLE_CACHE`, `DISABLE_CRON`, `DISABLE_HTTP`. The old environment variables will be removed
|
|
||||||
in the future versions.
|
|
||||||
It doesn't make sense to mark them as `WS_` since they are global and do not relate to the tool itself. And they must be
|
|
||||||
set from the `compose.yaml` file itself.
|
|
||||||
|
|
||||||
### 2024-05-04
|
|
||||||
|
|
||||||
The new webhook endpoint no longer requires a key, and it's now open to public you just need to specify the backend
|
|
||||||
name.
|
|
||||||
|
|
||||||
Refer to [NEWS](NEWS.md) for old updates.
|
Refer to [NEWS](NEWS.md) for old updates.
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
|
* **NEW** WebUI. (Preview).
|
||||||
* 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.
|
||||||
@@ -74,7 +58,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- WS_TZ=UTC # Set timezone.
|
- WS_TZ=UTC # Set timezone.
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080" # webhook listener port.
|
- "8080:8080" # The port which will serve WebUI + API + Webhooks
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/config:rw # mount current directory to container /config directory.
|
- ./data:/config:rw # mount current directory to container /config directory.
|
||||||
```
|
```
|
||||||
@@ -104,16 +88,48 @@ $ mkdir -p ./data && docker-compose pull && docker-compose up -d
|
|||||||
> To use this container with `podman` set `compose.yaml` `user` to `0:0`. it will appear to be working as root
|
> To use this container with `podman` set `compose.yaml` `user` to `0:0`. it will appear to be working as root
|
||||||
> inside the container, but it will be mapped to the user in which the command was run under.
|
> inside the container, but it will be mapped to the user in which the command was run under.
|
||||||
|
|
||||||
|
# Management via WebUI
|
||||||
|
|
||||||
|
After starting the container, you can access the WebUI by visiting `http://localhost:8080` in your browser.
|
||||||
|
|
||||||
|
At the start you won't see anything as the `WebUI` is decoupled from the WatchState and need to be configured to be able to access the API.
|
||||||
|
In the top right corner, you will see a cogwheel icon, click on it and then Configure the connection settings.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
As shown in the screenshot, to get your `API Token`, run the following command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ docker exec -ti watchstate console system:apikey
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy the random string in dark yellow, into the `API Token` field Make sure to set the `API URL` or click the `current page URL` link. If everything is set, then the Status field will turn
|
||||||
|
green. and `Status: OK` will be shown, and the reset of the navbar will show up. Which hopefully means everything is ok.
|
||||||
|
|
||||||
|
To add a backend, click on the `Backends` link in the navbar, then `+` button. as showing in the following screenshot
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Fill the required information, if you get a green notification, then the backend is added successfully. If you get a red/yellow notification, Then most likely incorrect information was provided.
|
||||||
|
You can check the message in the notification itself to know what went wrong. Or check the logs page, Most likely an error has been logged to a file named `app.YYYYMMDD.log`.
|
||||||
|
|
||||||
|
If everything went ok, you should see the backend shows up in the same page. You can then go to the Tasks page and click on `Qeueu Task`, for first time import we recommand letting
|
||||||
|
the task run in the background, as it might take a while to import all the data.
|
||||||
|
|
||||||
|
Once you have done all for your backends, You should go back again to `Tasks` page and enable the `Import` and `Export` tasks. This will make sure your data is always in sync.
|
||||||
|
To enable/disable the task, simply click on the slider next to the task name if it's green then it's enabled, if it's gray then it's disabled.
|
||||||
|
|
||||||
|
Once that is done, you can let the tool do its job, and you can start using the tool to track your play state.
|
||||||
|
|
||||||
|
# Management via CLI.
|
||||||
|
|
||||||
# Adding backend
|
# Adding backend
|
||||||
|
|
||||||
After starting the container you should start adding your backends and to do so run the following command:
|
After starting the container you should start adding your backends and to do so run the following command:
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> to get your plex token, please
|
> to get your plex token, please visit [this plex page](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/) to
|
||||||
> visit [this plex page](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/) to
|
> know how to extract your plex token. For jellyfin & emby. Go to Dashboard > Advanced > API keys > then create new api keys.
|
||||||
> know
|
|
||||||
> how to extract your plex token.
|
|
||||||
> For jellyfin & emby. Go to Dashboard > Advanced > API keys > then create new api keys.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker exec -ti watchstate console config:add
|
$ docker exec -ti watchstate console config:add
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ return (function () {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'webui' => [
|
'webui' => [
|
||||||
'enabled' => (bool)env('WEBUI_ENABLED', env('WS_WEBUI_ENABLED', false)),
|
'enabled' => (bool)env('WEBUI_ENABLED', env('WS_WEBUI_ENABLED', true)),
|
||||||
'path' => fixPath(env('WS_WEBUI_PATH', __DIR__ . '/../public/exported')),
|
'path' => fixPath(env('WS_WEBUI_PATH', __DIR__ . '/../public/exported')),
|
||||||
],
|
],
|
||||||
'database' => [
|
'database' => [
|
||||||
|
|||||||
147
config/servers.spec.php
Normal file
147
config/servers.spec.php
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last update: 2024-05-14
|
||||||
|
*
|
||||||
|
* servers.yaml backend spec.
|
||||||
|
*
|
||||||
|
* This file defines the backend spec.
|
||||||
|
* The dot (.) means the string past the dot is sub key of the string preceding it.
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'key' => 'name',
|
||||||
|
'type' => 'string',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'The name of the backend.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'type',
|
||||||
|
'type' => 'string',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'The type of the backend.',
|
||||||
|
'choices' => ['plex', 'emby', 'jellyfin']
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'url',
|
||||||
|
'type' => 'string',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'The URL of the backend.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'token',
|
||||||
|
'type' => 'string',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'The API token of the backend.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'uuid',
|
||||||
|
'type' => 'string',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'The unique identifier of the backend.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'user',
|
||||||
|
'type' => 'string',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'The user ID of the backend.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'export.enabled',
|
||||||
|
'type' => 'bool',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'Whether enable export function to the backend.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'export.lastSync',
|
||||||
|
'type' => 'integer',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'The last time data was exported to the backend.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'import.enabled',
|
||||||
|
'type' => 'bool',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'Whether to enable import function to the backend.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'import.lastSync',
|
||||||
|
'type' => 'integer',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'The last time data was imported from the backend.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'webhook.token',
|
||||||
|
'type' => 'string',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'Webhook token for the backend.',
|
||||||
|
'deprecated' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'webhook.match.user',
|
||||||
|
'type' => 'bool',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'Whether to strictly match the user ID of the backend When receiving webhook events.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'webhook.match.uuid',
|
||||||
|
'type' => 'bool',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'Whether to strictly match the unique identifier of the backend When receiving webhook events.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'options.ignore',
|
||||||
|
'type' => 'string',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'The list of libraries ids to ignore when syncing.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'options.LIBRARY_SEGMENT',
|
||||||
|
'type' => 'string',
|
||||||
|
'visible' => true,
|
||||||
|
'description' => 'How many items to per request.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'options.ADMIN_TOKEN',
|
||||||
|
'type' => 'string',
|
||||||
|
'visible' => false,
|
||||||
|
'description' => 'Plex admin token to use to manage limited users.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'options.DUMP_PAYLOAD',
|
||||||
|
'type' => 'bool',
|
||||||
|
'visible' => false,
|
||||||
|
'description' => 'Whether to dump the webhook payload into json file.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'options.DEBUG_TRACE',
|
||||||
|
'type' => 'bool',
|
||||||
|
'visible' => false,
|
||||||
|
'description' => 'Whether to enable debug tracing when operations are running.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'options.IMPORT_METADATA_ONLY',
|
||||||
|
'type' => 'bool',
|
||||||
|
'visible' => false,
|
||||||
|
'description' => 'Whether to import metadata only when syncing.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'options.DRY_RUN',
|
||||||
|
'type' => 'bool',
|
||||||
|
'visible' => false,
|
||||||
|
'description' => 'Enable dry-run changes will not be committed in supported context.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'options.client.timeout',
|
||||||
|
'type' => 'integer',
|
||||||
|
'visible' => false,
|
||||||
|
'description' => 'The http timeout per request to the backend.',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'options.use_old_progress_endpoint',
|
||||||
|
'type' => 'bool',
|
||||||
|
'visible' => false,
|
||||||
|
'description' => 'Whether to use the old progress endpoint for plex progress sync.',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
@@ -1,18 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<Message>
|
<Message title="Important Information">
|
||||||
<span class="icon-text">
|
<div class="content">
|
||||||
<span class="icon">
|
<ul>
|
||||||
<i class="fas fa-info"></i>
|
<li>
|
||||||
</span>
|
|
||||||
<span>
|
Please, Beware <code>WatchState</code> is single user tool. It doesn't support syncing multiple users
|
||||||
Please, Beware <code>WatchState</code> is single user tool. It doesn't support syncing multiple users
|
play state. Please read
|
||||||
play state. Please read
|
<NuxtLink target="_blank" v-text="'this link'"
|
||||||
<NuxtLink
|
href="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#is-there-support-for-multi-user-setup"/>
|
||||||
href="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#is-there-support-for-multi-user-setup"
|
for more information.
|
||||||
target="_blank" v-text="'this link'"></NuxtLink>
|
</li>
|
||||||
for more information.
|
<li>
|
||||||
</span>
|
If you are adding new backend that is fresh and doesn't have your play state state, you should turn off import
|
||||||
</span>
|
and enable only metadata import at the start to prevent overriding your current play state. Please
|
||||||
|
<NuxtLink
|
||||||
|
href="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#my-new-backend-overriding-my-old-backend-state--my-watch-state-is-not-correct"
|
||||||
|
target="_blank" v-text="'Read this link'"/>
|
||||||
|
for more information.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</Message>
|
</Message>
|
||||||
<form id="backend_add_form" @submit.prevent="addBackend" @change="changeStage">
|
<form id="backend_add_form" @submit.prevent="addBackend" @change="changeStage">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
@@ -176,6 +183,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field" v-if="backend.import && !backend.import.enabled">
|
||||||
|
<label class="label" for="backend_import_metadata">Import metadata only from from this backend?</label>
|
||||||
|
<div class="control">
|
||||||
|
<input id="backend_import_metadata" type="checkbox" class="switch is-success"
|
||||||
|
v-model="backend.options.IMPORT_METADATA_ONLY">
|
||||||
|
<label for="backend_import_metadata">Enable</label>
|
||||||
|
<p class="help has-text-danger">
|
||||||
|
To efficiently push changes to the backend we need relation map and this require
|
||||||
|
us to get metadata from the backend. You have Importing disabled, as such this option
|
||||||
|
allow us to import this backend metadata without altering your play state.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="field" v-if="backend.export">
|
<div class="field" v-if="backend.export">
|
||||||
<label class="label" for="backend_export">Export data to this backend</label>
|
<label class="label" for="backend_export">Export data to this backend</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
@@ -406,9 +427,7 @@ const addBackend = async () => {
|
|||||||
if ('plex' === backend.value.type) {
|
if ('plex' === backend.value.type) {
|
||||||
let token = users.value.find(u => u.id === backend.value.user).token
|
let token = users.value.find(u => u.id === backend.value.user).token
|
||||||
if (token !== backend.value.token) {
|
if (token !== backend.value.token) {
|
||||||
backend.value.options = {
|
backend.value.options.ADMIN_TOKEN = backend.value.token;
|
||||||
ADMIN_TOKEN: backend.value.token
|
|
||||||
}
|
|
||||||
backend.value.token = token
|
backend.value.token = token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="navbar-menu" :class="{'is-active':showMenu}">
|
<div class="navbar-menu" :class="{'is-active':showMenu}">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start" v-if="hasAPISettings">
|
||||||
<NuxtLink class="navbar-item" href="/backends" @click.native="showMenu=false">
|
<NuxtLink class="navbar-item" href="/backends" @click.native="showMenu=false">
|
||||||
<span class="icon-text">
|
<span class="icon-text">
|
||||||
<span class="icon"><i class="fas fa-server"></i></span>
|
<span class="icon"><i class="fas fa-server"></i></span>
|
||||||
@@ -96,109 +96,121 @@
|
|||||||
|
|
||||||
<div class="columns is-multiline" v-if="showConnection">
|
<div class="columns is-multiline" v-if="showConnection">
|
||||||
<div class="column is-12 mt-2">
|
<div class="column is-12 mt-2">
|
||||||
<div class="box">
|
<div class="card">
|
||||||
|
<header class="card-header">
|
||||||
<div class="field">
|
<p class="card-header-title">
|
||||||
<label class="label" for="api_token">
|
Configure API Connection
|
||||||
<span class="icon-text">
|
</p>
|
||||||
<span class="icon"><i class="fas fa-key"></i></span>
|
<span class="card-header-icon">
|
||||||
<span>API Token</span>
|
<span class="icon"><i class="fas fa-cog"></i></span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</header>
|
||||||
<div class="field-body">
|
<div class="card-content">
|
||||||
|
<form @submit.prevent="testApi">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="field has-addons">
|
<label class="label" for="api_token">
|
||||||
<div class="control is-expanded">
|
|
||||||
<input class="input" id="api_token" v-model="api_token" required placeholder="API Token..."
|
|
||||||
@keyup="api_status = false; api_response = ''"
|
|
||||||
:type="false === exposeToken ? 'password' : 'text'">
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<button class="button is-primary" @click="exposeToken = !exposeToken"
|
|
||||||
v-tooltip="'Show/Hide token'">
|
|
||||||
<span class="icon" v-if="!exposeToken"><i class="fas fa-eye"></i></span>
|
|
||||||
<span class="icon" v-else><i class="fas fa-eye-slash"></i></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="help">
|
|
||||||
You can obtain the <code>API TOKEN</code> by using the <code>system:apikey</code> command or by
|
|
||||||
viewing the <code>/config/.env</code> inside <code>WS_DATA_PATH</code> variable and looking for the
|
|
||||||
<code>WS_API_KEY=</code> key.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="api_url">
|
|
||||||
<span class="icon-text">
|
|
||||||
<span class="icon"><i class="fas fa-link"></i></span>
|
|
||||||
<span>API URL</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div class="field-body">
|
|
||||||
<div class="field">
|
|
||||||
<div class="control">
|
|
||||||
<input class="input" id="api_url" type="url" v-model="api_url" required
|
|
||||||
placeholder="API URL... http://localhost:8081"
|
|
||||||
@keyup="api_status = false; api_response = ''">
|
|
||||||
<p class="help">
|
|
||||||
Use <a href="javascript:void(0)" @click="setOrigin">current page URL</a>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="api_path">
|
|
||||||
<span class="icon-text">
|
|
||||||
<span class="icon"><i class="fas fa-folder"></i></span>
|
|
||||||
<span>API Path</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div class="field-body">
|
|
||||||
<div class="field">
|
|
||||||
<div class="control">
|
|
||||||
<input class="input" id="api_path" type="text" v-model="api_path" required
|
|
||||||
placeholder="API Path... /v1/api"
|
|
||||||
@keyup="api_status = false; api_response = ''">
|
|
||||||
<p class="help">
|
|
||||||
Use <a href="javascript:void(0)" @click="api_path = '/v1/api'">Set default API Path</a>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<div class="field-body">
|
|
||||||
<div class="field">
|
|
||||||
<div class="field has-addons">
|
|
||||||
<div class="control is-expanded">
|
|
||||||
<input class="input" type="text" v-model="api_response" readonly disabled
|
|
||||||
:class="{'has-background-success': true===api_status}">
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<button type="submit" class="button is-primary" :disabled="!api_url || !api_token"
|
|
||||||
@click="testApi">
|
|
||||||
<span class="icon-text">
|
|
||||||
<span class="icon"><i class="fas fa-save"></i></span>
|
|
||||||
<span>Save</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="help">
|
|
||||||
<span class="icon-text">
|
<span class="icon-text">
|
||||||
<span class="icon has-text-danger"><i class="fas fa-info"></i></span>
|
<span class="icon"><i class="fas fa-key"></i></span>
|
||||||
<span>These settings are stored locally in your browser. You need to re-add them if you access the
|
<span>API Token</span>
|
||||||
<code>WebUI</code> from different browser.</span>
|
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</label>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control is-expanded">
|
||||||
|
<input class="input" id="api_token" v-model="api_token" required placeholder="API Token..."
|
||||||
|
@keyup="api_status = false; api_response = ''"
|
||||||
|
:type="false === exposeToken ? 'password' : 'text'">
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-primary" @click="exposeToken = !exposeToken"
|
||||||
|
v-tooltip="'Show/Hide token'">
|
||||||
|
<span class="icon" v-if="!exposeToken"><i class="fas fa-eye"></i></span>
|
||||||
|
<span class="icon" v-else><i class="fas fa-eye-slash"></i></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="help">
|
||||||
|
You can obtain the <code>API TOKEN</code> by using the <code>system:apikey</code> command or by
|
||||||
|
viewing the <code>/config/.env</code> inside <code>WS_DATA_PATH</code> variable and looking for
|
||||||
|
the
|
||||||
|
<code>WS_API_KEY=</code> key.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="api_url">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon"><i class="fas fa-link"></i></span>
|
||||||
|
<span>API URL</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" id="api_url" type="url" v-model="api_url" required
|
||||||
|
placeholder="API URL... http://localhost:8081"
|
||||||
|
@keyup="api_status = false; api_response = ''">
|
||||||
|
<p class="help">
|
||||||
|
Use <a href="javascript:void(0)" @click="setOrigin">current page URL</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="api_path">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon"><i class="fas fa-folder"></i></span>
|
||||||
|
<span>API Path</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" id="api_path" type="text" v-model="api_path" required
|
||||||
|
placeholder="API Path... /v1/api"
|
||||||
|
@keyup="api_status = false; api_response = ''">
|
||||||
|
<p class="help">
|
||||||
|
Use <a href="javascript:void(0)" @click="api_path = '/v1/api'">Set default API Path</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control is-expanded">
|
||||||
|
<input class="input" type="text" v-model="api_response" readonly disabled
|
||||||
|
:class="{'has-background-success': true===api_status,'has-background-warning': true!==api_status}">
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button type="submit" class="button is-primary" :disabled="!api_url || !api_token">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon"><i class="fas fa-save"></i></span>
|
||||||
|
<span>Save</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="help">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon has-text-danger"><i class="fas fa-info"></i></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>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -317,7 +329,13 @@ const applyPreferredColorScheme = scheme => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
applyPreferredColorScheme(selectedTheme.value)
|
applyPreferredColorScheme(selectedTheme.value)
|
||||||
|
|
||||||
|
if ('' === api_token.value) {
|
||||||
|
showConnection.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
await getVersion()
|
await getVersion()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
@@ -330,6 +348,14 @@ watch(selectedTheme, value => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(api_token, value => {
|
||||||
|
if ('' === value) {
|
||||||
|
api_status.value = false;
|
||||||
|
api_response.value = 'Status: Unknown'
|
||||||
|
showConnection.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const testApi = async () => {
|
const testApi = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await request('/backends')
|
const response = await request('/backends')
|
||||||
@@ -367,4 +393,6 @@ const getVersion = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setOrigin = () => api_url.value = window.location.origin;
|
const setOrigin = () => api_url.value = window.location.origin;
|
||||||
|
|
||||||
|
const hasAPISettings = computed(() => '' !== api_token.value && '' !== api_url.value)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -144,7 +144,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field" v-if="backend.import">
|
<div class="field" v-if="backend.import">
|
||||||
<label class="label" for="backend_import">Import data from this backend</label>
|
<label class="label" for="backend_import">Import data from this backend?</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input id="backend_import" type="checkbox" class="switch is-success" v-model="backend.import.enabled">
|
<input id="backend_import" type="checkbox" class="switch is-success" v-model="backend.import.enabled">
|
||||||
<label for="backend_import">Enable</label>
|
<label for="backend_import">Enable</label>
|
||||||
@@ -154,8 +154,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field" v-if="backend.import && !backend.import.enabled">
|
||||||
|
<label class="label" for="backend_import_metadata">Import metadata only from from this backend?</label>
|
||||||
|
<div class="control">
|
||||||
|
<input id="backend_import_metadata" type="checkbox" class="switch is-success"
|
||||||
|
v-model="backend.options.IMPORT_METADATA_ONLY">
|
||||||
|
<label for="backend_import_metadata">Enable</label>
|
||||||
|
<p class="help has-text-danger">
|
||||||
|
To efficiently push changes to the backend we need relation map and this require
|
||||||
|
us to get metadata from the backend. You have Importing disabled, as such this option
|
||||||
|
allow us to import this backend metadata without altering your play state.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="field" v-if="backend.export">
|
<div class="field" v-if="backend.export">
|
||||||
<label class="label" for="backend_export">Export data to this backend</label>
|
<label class="label" for="backend_export">Export data to this backend?</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input id="backend_export" type="checkbox" class="switch is-success" v-model="backend.export.enabled">
|
<input id="backend_export" type="checkbox" class="switch is-success" v-model="backend.export.enabled">
|
||||||
<label for="backend_export">Enable</label>
|
<label for="backend_export">Enable</label>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column is-12" v-if="backends.length<1 && !toggleForm">
|
<div class="column is-12" v-if="backends.length<1 && !toggleForm">
|
||||||
<Message class="is-warning" title="Warning">
|
<Message message_class="is-warning" title="Warning">
|
||||||
<span class="icon-text">
|
<span class="icon-text">
|
||||||
<span class="icon"><i class="fas fa-exclamation"></i></span>
|
<span class="icon"><i class="fas fa-exclamation"></i></span>
|
||||||
<span>
|
<span>
|
||||||
@@ -70,9 +70,7 @@
|
|||||||
<input :id="backend.name+'_export'" type="checkbox" class="switch is-success"
|
<input :id="backend.name+'_export'" type="checkbox" class="switch is-success"
|
||||||
:checked="backend.export.enabled"
|
:checked="backend.export.enabled"
|
||||||
@change="updateValue(backend, 'export.enabled', !backend.export.enabled)">
|
@change="updateValue(backend, 'export.enabled', !backend.export.enabled)">
|
||||||
<label :for="backend.name+'_export'">
|
<label :for="backend.name+'_export'">Export</label>
|
||||||
Export <span class="is-hidden-mobile"> {{ backend.export.enabled ? 'Enabled' : 'Disabled' }}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer-item">
|
<div class="card-footer-item">
|
||||||
@@ -80,14 +78,32 @@
|
|||||||
<input :id="backend.name+'_import'" type="checkbox" class="switch is-success"
|
<input :id="backend.name+'_import'" type="checkbox" class="switch is-success"
|
||||||
:checked="backend.import.enabled"
|
:checked="backend.import.enabled"
|
||||||
@change="updateValue(backend, 'import.enabled',!backend.import.enabled)">
|
@change="updateValue(backend, 'import.enabled',!backend.import.enabled)">
|
||||||
<label :for="backend.name+'_import'">
|
<label :for="backend.name+'_import'">Import</label>
|
||||||
Import <span class="is-hidden-mobile"> {{ backend.import.enabled ? 'Enabled' : 'Disabled' }}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-footer-item">
|
||||||
|
<a :href="api_url + backend.urls.webhook" class="is-info is-light" @click.prevent="copyUrl(backend)">
|
||||||
|
<span class="icon"><i class="fas fa-copy"></i></span>
|
||||||
|
<span class="is-hidden-mobile">Copy Webhook URL</span>
|
||||||
|
<span class="is-hidden-tablet">Webhook</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12" v-if="backends.length>0">
|
||||||
|
<div class="content">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Import In context of <code>WatchState</code> means pulling data from the backends into the local database.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Export in context of <code>WatchState</code> means pushing data from the local database to the backends.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -96,11 +112,14 @@ import 'assets/css/bulma-switch.css'
|
|||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import request from '~/utils/request.js'
|
import request from '~/utils/request.js'
|
||||||
import BackendAdd from '~/components/BackendAdd.vue'
|
import BackendAdd from '~/components/BackendAdd.vue'
|
||||||
|
import {copyText} from '~/utils/index.js'
|
||||||
|
import {useStorage} from "@vueuse/core";
|
||||||
|
|
||||||
useHead({title: 'Backends'})
|
useHead({title: 'Backends'})
|
||||||
|
|
||||||
const backends = ref([])
|
const backends = ref([])
|
||||||
const toggleForm = ref(false)
|
const toggleForm = ref(false)
|
||||||
|
const api_url = useStorage('api_url', '')
|
||||||
|
|
||||||
const loadContent = async () => {
|
const loadContent = async () => {
|
||||||
backends.value = []
|
backends.value = []
|
||||||
@@ -110,6 +129,8 @@ const loadContent = async () => {
|
|||||||
|
|
||||||
onMounted(() => loadContent())
|
onMounted(() => loadContent())
|
||||||
|
|
||||||
|
const copyUrl = (backend) => copyText(api_url.value + backend.urls.webhook)
|
||||||
|
|
||||||
const updateValue = async (backend, key, newValue) => {
|
const updateValue = async (backend, key, newValue) => {
|
||||||
const response = await request(`/backend/${backend.name}`, {
|
const response = await request(`/backend/${backend.name}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<div class="is-pulled-right" v-if="false === show_report_warning">
|
<div class="is-pulled-right" v-if="false === show_report_warning">
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<button class="button is-primary" @click="copyContent" v-tooltip="'Copy Report'">
|
<button class="button is-primary" @click="copyText(data.join('\n'))" v-tooltip="'Copy Report'">
|
||||||
<span class="icon"><i class="fas fa-copy"></i></span>
|
<span class="icon"><i class="fas fa-copy"></i></span>
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {notification} from '~/utils/index.js'
|
import {copyText} from '~/utils/index.js'
|
||||||
|
|
||||||
useHead({title: `System Report`})
|
useHead({title: `System Report`})
|
||||||
|
|
||||||
@@ -59,30 +59,4 @@ watch(show_report_warning, async (value) => {
|
|||||||
data.value = await response.json()
|
data.value = await response.json()
|
||||||
})
|
})
|
||||||
|
|
||||||
const copyContent = () => {
|
|
||||||
if (navigator.clipboard) {
|
|
||||||
navigator.clipboard.writeText(data.value.join('\n')).then(() => {
|
|
||||||
notification('success', 'Success', 'Report has been copied to clipboard.')
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error('Failed to copy: ', error)
|
|
||||||
notification('error', 'Error', 'Failed to copy the report.')
|
|
||||||
});
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const node = document.querySelector('#report-content')
|
|
||||||
const selection = window.getSelection()
|
|
||||||
const range = document.createRange()
|
|
||||||
range.selectNodeContents(node)
|
|
||||||
selection.removeAllRanges()
|
|
||||||
selection.addRange(range)
|
|
||||||
|
|
||||||
if (('execCommand' in document) && document.execCommand('copy')) {
|
|
||||||
selection.removeAllRanges()
|
|
||||||
notification('success', 'Success', 'Report has been copied to clipboard.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
notification('warning', 'Warning', 'Clipboard API only works on secure context. The report is selected, please use Ctrl+C to copy.')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
<div class="card-footer-item">
|
<div class="card-footer-item">
|
||||||
<button class="button is-info" @click="queueTask(task)" :disabled="task.queued || !task.enabled">
|
<button class="button is-info" @click="queueTask(task)" :disabled="task.queued">
|
||||||
<span class="icon-text">
|
<span class="icon-text">
|
||||||
<span class="icon"><i class="fas fa-clock"></i></span>
|
<span class="icon"><i class="fas fa-clock"></i></span>
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -266,4 +266,25 @@ const formatDuration = (milliseconds) => {
|
|||||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ag_set, ag, humanFileSize, awaitElement, ucFirst, notification, makeGUIDLink, formatDuration}
|
const copyText = (str) => {
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
navigator.clipboard.writeText(data.value.join('\n')).then(() => {
|
||||||
|
notification('success', 'Success', 'Report has been copied to clipboard.')
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error('Failed to copy: ', error)
|
||||||
|
notification('error', 'Error', 'Failed to copy to clipboard.')
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const el = document.createElement('textarea')
|
||||||
|
el.value = str
|
||||||
|
document.body.appendChild(el)
|
||||||
|
el.select()
|
||||||
|
document.execCommand('copy')
|
||||||
|
document.body.removeChild(el)
|
||||||
|
|
||||||
|
notification('success', 'Success', 'Text copied to clipboard.')
|
||||||
|
}
|
||||||
|
|
||||||
|
export {ag_set, ag, humanFileSize, awaitElement, ucFirst, notification, makeGUIDLink, formatDuration, copyText}
|
||||||
|
|||||||
BIN
screenshots/add_backend.png
Normal file
BIN
screenshots/add_backend.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
screenshots/api_settings.png
Normal file
BIN
screenshots/api_settings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -11,7 +11,6 @@ use App\Libs\Config;
|
|||||||
use App\Libs\ConfigFile;
|
use App\Libs\ConfigFile;
|
||||||
use App\Libs\DataUtil;
|
use App\Libs\DataUtil;
|
||||||
use App\Libs\HTTP_STATUS;
|
use App\Libs\HTTP_STATUS;
|
||||||
use App\Libs\Options;
|
|
||||||
use App\Libs\Traits\APITraits;
|
use App\Libs\Traits\APITraits;
|
||||||
use JsonException;
|
use JsonException;
|
||||||
use Psr\Http\Message\ResponseInterface as iResponse;
|
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||||
@@ -126,17 +125,14 @@ final class Update
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$optionals = [
|
$spec = require __DIR__ . '/../../../config/backend.spec.php';
|
||||||
Options::DUMP_PAYLOAD => 'bool',
|
|
||||||
Options::LIBRARY_SEGMENT => 'int',
|
|
||||||
Options::IGNORE => 'string',
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($optionals as $key => $type) {
|
foreach ($data->get('options', []) as $key => $value) {
|
||||||
if (null !== ($value = $data->get('options.' . $key))) {
|
if (false === ag_exists($spec, "options.{$key}") || null === $value) {
|
||||||
settype($value, $type);
|
continue;
|
||||||
$newData = ag_set($newData, "options.{$key}", $value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$newData = ag_set($newData, "options.{$key}", $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return deepArrayMerge([$config, $client->fromRequest($newData, $request)]);
|
return deepArrayMerge([$config, $client->fromRequest($newData, $request)]);
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ use App\Libs\DataUtil;
|
|||||||
use App\Libs\Exceptions\Backends\InvalidContextException;
|
use App\Libs\Exceptions\Backends\InvalidContextException;
|
||||||
use App\Libs\Exceptions\RuntimeException;
|
use App\Libs\Exceptions\RuntimeException;
|
||||||
use App\Libs\HTTP_STATUS;
|
use App\Libs\HTTP_STATUS;
|
||||||
use App\Libs\Options;
|
|
||||||
use App\Libs\Traits\APITraits;
|
use App\Libs\Traits\APITraits;
|
||||||
use App\Libs\Uri;
|
use App\Libs\Uri;
|
||||||
use Psr\Http\Message\ResponseInterface as iResponse;
|
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||||
@@ -149,17 +148,14 @@ final class Add
|
|||||||
'options' => [],
|
'options' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
$optionals = [
|
$spec = require __DIR__ . '/../../../config/backend.spec.php';
|
||||||
Options::DUMP_PAYLOAD => 'bool',
|
|
||||||
Options::LIBRARY_SEGMENT => 'int',
|
|
||||||
Options::IGNORE => 'string',
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($optionals as $key => $type) {
|
foreach ($data->get('options', []) as $key => $value) {
|
||||||
if (null !== ($value = $data->get('options.' . $key))) {
|
if (false === ag_exists($spec, "options.{$key}") || null === $value) {
|
||||||
settype($value, $type);
|
continue;
|
||||||
$config = ag_set($config, "options.{$key}", $value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$config = ag_set($config, "options.{$key}", $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $client->fromRequest($config, $request);
|
return $client->fromRequest($config, $request);
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ final class ManageCommand extends Command
|
|||||||
$output->writeln('');
|
$output->writeln('');
|
||||||
|
|
||||||
// -- $name.options.IMPORT_METADATA_ONLY
|
// -- $name.options.IMPORT_METADATA_ONLY
|
||||||
(function () use ($input, $output, &$u) {
|
(function () use ($input, $output, &$u, $name) {
|
||||||
if (true === (bool)ag($u, 'import.enabled')) {
|
if (true === (bool)ag($u, 'import.enabled')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -265,6 +265,7 @@ final class ManageCommand extends Command
|
|||||||
<notice>This option will not alter your play state or add new items to the database.</notice>
|
<notice>This option will not alter your play state or add new items to the database.</notice>
|
||||||
HELP. PHP_EOL . '> ',
|
HELP. PHP_EOL . '> ',
|
||||||
[
|
[
|
||||||
|
'name' => $name,
|
||||||
'default' => '[<value>Y|N</value>] [<value>Default: ' . ($chosen ? 'Yes' : 'No') . '</value>]',
|
'default' => '[<value>Y|N</value>] [<value>Default: ' . ($chosen ? 'Yes' : 'No') . '</value>]',
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Libs\Traits;
|
namespace App\Libs\Traits;
|
||||||
|
|
||||||
|
use App\API\Backend\Index;
|
||||||
use App\Backends\Common\Cache as BackendCache;
|
use App\Backends\Common\Cache as BackendCache;
|
||||||
use App\Backends\Common\ClientInterface;
|
use App\Backends\Common\ClientInterface;
|
||||||
use App\Backends\Common\ClientInterface as iClient;
|
use App\Backends\Common\ClientInterface as iClient;
|
||||||
@@ -65,6 +66,16 @@ trait APITraits
|
|||||||
$backend = ag_set($backend, 'export.lastSync', $export ? makeDate($export) : null);
|
$backend = ag_set($backend, 'export.lastSync', $export ? makeDate($export) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$webhookUrl = parseConfigValue(Index::URL) . "/{$backendName}/webhook";
|
||||||
|
|
||||||
|
if (true === (bool)Config::get('api.secure')) {
|
||||||
|
$webhookUrl .= '?apikey=' . Config::get('api.key');
|
||||||
|
}
|
||||||
|
|
||||||
|
$backend['urls'] = [
|
||||||
|
'webhook' => $webhookUrl,
|
||||||
|
];
|
||||||
|
|
||||||
$backends[] = $backend;
|
$backends[] = $backend;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user