Enable WebUI by default.

This commit is contained in:
Abdulmhsen B. A. A
2024-05-14 18:07:44 +03:00
parent ebb85fdd68
commit b119601a48
18 changed files with 473 additions and 207 deletions

View File

@@ -2,6 +2,7 @@
**/.git
**/vendor
./var/*
**/screenshots
!./var/.gitignore
.phpunit.result.cache
frontend/.nuxt

21
NEWS.md
View File

@@ -1,5 +1,26 @@
# 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]
We are going to retire the old webhooks endpoint, please refer to the [FAQ](FAQ.md#how-to-add-webhooks) to know how to

View File

@@ -9,6 +9,12 @@ out of the box, this tool support `Jellyfin`, `Plex` and `Emby` media servers.
## 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
@@ -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.
### 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.
# Features
* **NEW** WebUI. (Preview).
* Sync backends play state (from many to many).
* Backup your backends play state into `portable` format.
* Receive Webhook events from media backends.
@@ -74,7 +58,7 @@ services:
environment:
- WS_TZ=UTC # Set timezone.
ports:
- "8080:8080" # webhook listener port.
- "8080:8080" # The port which will serve WebUI + API + Webhooks
volumes:
- ./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
> 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.
![Connection settings](screenshots/api_settings.png)
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
![Add backend](screenshots/add_backend.png)
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
After starting the container you should start adding your backends and to do so run the following command:
> [!NOTE]
> to get your plex token, please
> 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.
> to get your plex token, please 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.
```bash
$ docker exec -ti watchstate console config:add

View File

@@ -38,7 +38,7 @@ return (function () {
],
],
'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')),
],
'database' => [

147
config/servers.spec.php Normal file
View 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.',
],
];

View File

@@ -1,18 +1,25 @@
<template>
<Message>
<span class="icon-text">
<span class="icon">
<i class="fas fa-info"></i>
</span>
<span>
<Message title="Important Information">
<div class="content">
<ul>
<li>
Please, Beware <code>WatchState</code> is single user tool. It doesn't support syncing multiple users
play state. Please read
<NuxtLink
href="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#is-there-support-for-multi-user-setup"
target="_blank" v-text="'this link'"></NuxtLink>
<NuxtLink target="_blank" v-text="'this link'"
href="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#is-there-support-for-multi-user-setup"/>
for more information.
</span>
</span>
</li>
<li>
If you are adding new backend that is fresh and doesn't have your play state state, you should turn off import
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>
<form id="backend_add_form" @submit.prevent="addBackend" @change="changeStage">
<div class="box">
@@ -176,6 +183,20 @@
</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">
<label class="label" for="backend_export">Export data to this backend</label>
<div class="control">
@@ -406,9 +427,7 @@ const addBackend = async () => {
if ('plex' === backend.value.type) {
let token = users.value.find(u => u.id === backend.value.user).token
if (token !== backend.value.token) {
backend.value.options = {
ADMIN_TOKEN: backend.value.token
}
backend.value.options.ADMIN_TOKEN = backend.value.token;
backend.value.token = token
}
}

View File

@@ -17,7 +17,7 @@
</div>
<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">
<span class="icon-text">
<span class="icon"><i class="fas fa-server"></i></span>
@@ -96,8 +96,17 @@
<div class="columns is-multiline" v-if="showConnection">
<div class="column is-12 mt-2">
<div class="box">
<div class="card">
<header class="card-header">
<p class="card-header-title">
Configure API Connection
</p>
<span class="card-header-icon">
<span class="icon"><i class="fas fa-cog"></i></span>
</span>
</header>
<div class="card-content">
<form @submit.prevent="testApi">
<div class="field">
<label class="label" for="api_token">
<span class="icon-text">
@@ -123,7 +132,8 @@
</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
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>
@@ -178,11 +188,10 @@
<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}">
: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"
@click="testApi">
<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>
@@ -193,13 +202,16 @@
<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
<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>
@@ -317,7 +329,13 @@ const applyPreferredColorScheme = scheme => {
onMounted(async () => {
try {
applyPreferredColorScheme(selectedTheme.value)
if ('' === api_token.value) {
showConnection.value = true
return
}
await getVersion()
} 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 () => {
try {
const response = await request('/backends')
@@ -367,4 +393,6 @@ const getVersion = async () => {
}
const setOrigin = () => api_url.value = window.location.origin;
const hasAPISettings = computed(() => '' !== api_token.value && '' !== api_url.value)
</script>

View File

@@ -144,7 +144,7 @@
</div>
<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">
<input id="backend_import" type="checkbox" class="switch is-success" v-model="backend.import.enabled">
<label for="backend_import">Enable</label>
@@ -154,8 +154,22 @@
</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">
<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">
<input id="backend_export" type="checkbox" class="switch is-success" v-model="backend.export.enabled">
<label for="backend_export">Enable</label>

View File

@@ -26,7 +26,7 @@
</div>
<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"><i class="fas fa-exclamation"></i></span>
<span>
@@ -70,9 +70,7 @@
<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 <span class="is-hidden-mobile">&nbsp;{{ backend.export.enabled ? 'Enabled' : 'Disabled' }}</span>
</label>
<label :for="backend.name+'_export'">Export</label>
</div>
</div>
<div class="card-footer-item">
@@ -80,14 +78,32 @@
<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 <span class="is-hidden-mobile">&nbsp;{{ backend.import.enabled ? 'Enabled' : 'Disabled' }}</span>
</label>
<label :for="backend.name+'_import'">Import</label>
</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>
</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>
</template>
@@ -96,11 +112,14 @@ import 'assets/css/bulma-switch.css'
import moment from 'moment'
import request from '~/utils/request.js'
import BackendAdd from '~/components/BackendAdd.vue'
import {copyText} from '~/utils/index.js'
import {useStorage} from "@vueuse/core";
useHead({title: 'Backends'})
const backends = ref([])
const toggleForm = ref(false)
const api_url = useStorage('api_url', '')
const loadContent = async () => {
backends.value = []
@@ -110,6 +129,8 @@ const loadContent = async () => {
onMounted(() => loadContent())
const copyUrl = (backend) => copyText(api_url.value + backend.urls.webhook)
const updateValue = async (backend, key, newValue) => {
const response = await request(`/backend/${backend.name}`, {
method: 'PATCH',

View File

@@ -5,7 +5,7 @@
<div class="is-pulled-right" v-if="false === show_report_warning">
<div class="field is-grouped">
<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>
</button>
</p>
@@ -44,7 +44,7 @@
</template>
<script setup>
import {notification} from '~/utils/index.js'
import {copyText} from '~/utils/index.js'
useHead({title: `System Report`})
@@ -59,30 +59,4 @@ watch(show_report_warning, async (value) => {
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>

View File

@@ -74,7 +74,7 @@
</div>
<footer class="card-footer">
<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"><i class="fas fa-clock"></i></span>
<span>

View File

@@ -266,4 +266,25 @@ const formatDuration = (milliseconds) => {
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -11,7 +11,6 @@ use App\Libs\Config;
use App\Libs\ConfigFile;
use App\Libs\DataUtil;
use App\Libs\HTTP_STATUS;
use App\Libs\Options;
use App\Libs\Traits\APITraits;
use JsonException;
use Psr\Http\Message\ResponseInterface as iResponse;
@@ -126,17 +125,14 @@ final class Update
],
];
$optionals = [
Options::DUMP_PAYLOAD => 'bool',
Options::LIBRARY_SEGMENT => 'int',
Options::IGNORE => 'string',
];
$spec = require __DIR__ . '/../../../config/backend.spec.php';
foreach ($optionals as $key => $type) {
if (null !== ($value = $data->get('options.' . $key))) {
settype($value, $type);
$newData = ag_set($newData, "options.{$key}", $value);
foreach ($data->get('options', []) as $key => $value) {
if (false === ag_exists($spec, "options.{$key}") || null === $value) {
continue;
}
$newData = ag_set($newData, "options.{$key}", $value);
}
return deepArrayMerge([$config, $client->fromRequest($newData, $request)]);

View File

@@ -15,7 +15,6 @@ use App\Libs\DataUtil;
use App\Libs\Exceptions\Backends\InvalidContextException;
use App\Libs\Exceptions\RuntimeException;
use App\Libs\HTTP_STATUS;
use App\Libs\Options;
use App\Libs\Traits\APITraits;
use App\Libs\Uri;
use Psr\Http\Message\ResponseInterface as iResponse;
@@ -149,17 +148,14 @@ final class Add
'options' => [],
];
$optionals = [
Options::DUMP_PAYLOAD => 'bool',
Options::LIBRARY_SEGMENT => 'int',
Options::IGNORE => 'string',
];
$spec = require __DIR__ . '/../../../config/backend.spec.php';
foreach ($optionals as $key => $type) {
if (null !== ($value = $data->get('options.' . $key))) {
settype($value, $type);
$config = ag_set($config, "options.{$key}", $value);
foreach ($data->get('options', []) as $key => $value) {
if (false === ag_exists($spec, "options.{$key}") || null === $value) {
continue;
}
$config = ag_set($config, "options.{$key}", $value);
}
return $client->fromRequest($config, $request);

View File

@@ -240,7 +240,7 @@ final class ManageCommand extends Command
$output->writeln('');
// -- $name.options.IMPORT_METADATA_ONLY
(function () use ($input, $output, &$u) {
(function () use ($input, $output, &$u, $name) {
if (true === (bool)ag($u, 'import.enabled')) {
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>
HELP. PHP_EOL . '> ',
[
'name' => $name,
'default' => '[<value>Y|N</value>] [<value>Default: ' . ($chosen ? 'Yes' : 'No') . '</value>]',
]
),

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Libs\Traits;
use App\API\Backend\Index;
use App\Backends\Common\Cache as BackendCache;
use App\Backends\Common\ClientInterface;
use App\Backends\Common\ClientInterface as iClient;
@@ -65,6 +66,16 @@ trait APITraits
$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;
}