Merge pull request #675 from arabcoders/dev
Fix assertion failure in production.
This commit is contained in:
@@ -34,16 +34,16 @@ RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezo
|
||||
# Cache fonts.
|
||||
fc-cache -f && fc-list | sort
|
||||
|
||||
# Copy frankenphp (caddy+php) to the container.
|
||||
#
|
||||
COPY --chown=app:app --from=ghcr.io/arabcoders/franken_builder:latest /usr/local/bin/frankenphp /opt/bin/
|
||||
|
||||
# Copy source code to container.
|
||||
COPY ./ /opt/app
|
||||
|
||||
# Copy frontend to public directory.
|
||||
COPY --chown=app:app --from=npm_builder /frontend/exported/ /opt/app/public/exported/
|
||||
|
||||
# Copy frankenphp (caddy+php) to the container.
|
||||
#
|
||||
COPY --chown=app:app --from=ghcr.io/arabcoders/franken_builder:latest /usr/local/bin/frankenphp /opt/bin/
|
||||
|
||||
# install composer & packages.
|
||||
#
|
||||
RUN echo '' && \
|
||||
|
||||
@@ -234,9 +234,7 @@ return (function () {
|
||||
'opcache.max_wasted_percentage' => 5,
|
||||
'expose_php' => 0,
|
||||
'date.timezone' => ag($config, 'tz', 'UTC'),
|
||||
// 'mbstring.http_input' => ag($config, 'charset', 'UTF-8'),
|
||||
// 'mbstring.http_output' => ag($config, 'charset', 'UTF-8'),
|
||||
// 'mbstring.internal_encoding' => ag($config, 'charset', 'UTF-8'),
|
||||
'zend.assertions' => -1
|
||||
],
|
||||
'fpm' => [
|
||||
'global' => [
|
||||
|
||||
@@ -177,7 +177,13 @@ return [
|
||||
'key' => 'options.plex_external_user',
|
||||
'type' => 'bool',
|
||||
'visible' => false,
|
||||
'description' => 'Mark the plex user as external user.',
|
||||
'description' => 'Mark the plex user as home user.',
|
||||
],
|
||||
[
|
||||
'key' => 'options.plex_guest_user',
|
||||
'type' => 'bool',
|
||||
'visible' => true,
|
||||
'description' => 'Mark the plex user as invited guest.',
|
||||
],
|
||||
[
|
||||
'key' => 'options.ADMIN_PLEX_USER_PIN',
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
</Message>
|
||||
</div>
|
||||
<template v-if="stage >= 0">
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Local User</label>
|
||||
<div class="control has-icons-left">
|
||||
@@ -45,7 +44,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-users"></i>
|
||||
<i class="fas fa-users"/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">
|
||||
@@ -65,7 +64,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-server"></i>
|
||||
<i class="fas fa-server"/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">The backend type.</p>
|
||||
@@ -76,13 +75,13 @@
|
||||
<div class="control has-icons-left">
|
||||
<input class="input" type="text" v-model="backend.name" required :disabled="stage > 0">
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-id-badge"></i>
|
||||
<i class="fas fa-id-badge"/>
|
||||
</div>
|
||||
<p class="help">
|
||||
Choose a unique name for this backend. <b class="has-text-danger">You CANNOT change it later</b>.
|
||||
Backend name must be in <code>lower case a-z, 0-9 and _</code> and cannot start with number.
|
||||
</p>
|
||||
</div>
|
||||
<p class="help">
|
||||
Choose a unique name for this backend. <strong>You CANNOT change it later</strong>.
|
||||
Backend name must be in <strong>lower case a-z, 0-9 and _</strong> and cannot start with number.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
@@ -97,30 +96,29 @@
|
||||
<input class="input" v-model="backend.token" required :disabled="stage > 1"
|
||||
:type="false === exposeToken ? 'password' : 'text'">
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-key"></i>
|
||||
<i class="fas fa-key"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="button" class="button is-primary" @click="exposeToken = !exposeToken"
|
||||
v-tooltip="'Toggle 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>
|
||||
<span class="icon" v-if="!exposeToken"><i class="fas fa-eye"/></span>
|
||||
<span class="icon" v-else><i class="fas fa-eye-slash"/></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">
|
||||
<template v-if="'plex' === backend.type">
|
||||
Enter the <code>X-Plex-Token</code>.
|
||||
Enter the <strong>X-Plex-Token</strong>.
|
||||
<NuxtLink target="_blank" to="https://support.plex.tv/articles/204059436"
|
||||
v-text="'Visit This link'"/>
|
||||
to learn how to get the token. <span
|
||||
class="is-bold has-text-danger">If you plan to add sub-users, YOU MUST use admin level
|
||||
token.</span>
|
||||
to learn how to get the token. <span class="is-bold">If you plan to add sub-users, YOU MUST use
|
||||
admin level token.</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
Generate a new API Key from <code>Dashboard > Settings > API Keys</code>.<br>
|
||||
<span class="icon has-text-warning"><i class="fas fa-info-circle"></i></span>
|
||||
You can use <code>username:password</code> as API key and we will automatically generate limited
|
||||
Generate a new API Key from <strong>Dashboard > Settings > API Keys</strong>.<br>
|
||||
<span class="icon has-text-warning"><i class="fas fa-info-circle"/></span>
|
||||
You can use <strong>username:password</strong> as API key and we will automatically generate limited
|
||||
token if you are unable to generate API Key. This should be used as last resort. and it's mostly
|
||||
untested. and things might not work as expected.
|
||||
<span class="is-bold has-text-danger">If you plan to add sub-users, YOU MUST use API KEY and not
|
||||
@@ -169,17 +167,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="'plex' === backend.type">
|
||||
<div class="field" v-if="'plex' === backend.type">
|
||||
<label class="label">User PIN</label>
|
||||
<div class="control has-icons-left">
|
||||
<input class="input" type="text" v-model="backend.options.PLEX_USER_PIN" :disabled="stage > 1">
|
||||
<div class="icon is-left"><i class="fas fa-key"></i></div>
|
||||
<p class="help">
|
||||
If the user you are going to select has <code>PIN</code> enabled, you need to enter the pin here.
|
||||
Otherwise it will fail to authenticate.
|
||||
</p>
|
||||
<div class="icon is-left"><i class="fas fa-key"/></div>
|
||||
</div>
|
||||
</template>
|
||||
<p class="help">
|
||||
If the user you are going to select has <strong>PIN</strong> enabled, you need to enter the pin here.
|
||||
Otherwise it will fail to authenticate.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="stage >= 1">
|
||||
@@ -187,105 +185,146 @@
|
||||
<label class="label">URL</label>
|
||||
<div class="control has-icons-left">
|
||||
<input class="input" type="text" v-model="backend.url" required :disabled="stage > 1">
|
||||
<div class="icon is-left"><i class="fas fa-link"></i></div>
|
||||
<p class="help">
|
||||
Enter the URL of the backend. For example <code>http://192.168.8.200:8096</code>.
|
||||
</p>
|
||||
<div class="icon is-left"><i class="fas fa-link"/></div>
|
||||
</div>
|
||||
<p class="help">
|
||||
Enter the URL of the backend. For example <strong>http://192.168.8.200:8096</strong>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field" v-else>
|
||||
<label class="label">Plex Server URL</label>
|
||||
<div class="control has-icons-left">
|
||||
<div class="select is-fullwidth">
|
||||
<select v-model="backend.url" class="is-capital" @change="stage = 1; updateIdentifier()" required
|
||||
:disabled="stage > 1">
|
||||
<option value="" disabled>Select Server URL</option>
|
||||
<option v-for="server in servers" :key="'server-' + server.uuid" :value="server.uri">
|
||||
{{ server.name }} - {{ server.uri }}
|
||||
</option>
|
||||
</select>
|
||||
<template v-else>
|
||||
<div class="field">
|
||||
<label class="label">Plex Server URL</label>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded has-icons-left">
|
||||
<div class="select is-fullwidth">
|
||||
<select v-model="backend.url" class="is-capital" @change="stage = 1; updateIdentifier()"
|
||||
required
|
||||
:disabled="stage > 1">
|
||||
<option value="" disabled>Select Server URL</option>
|
||||
<option v-for="server in servers" :key="'server-' + server.uuid" :value="server.uri">
|
||||
{{ server.name }} - {{ server.uri }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-link" v-if="!serversLoading"/>
|
||||
<i class="fas fa-spinner fa-pulse" v-else/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-primary" type="button" :disabled="serversLoading || stage > 2"
|
||||
@click="getServers">
|
||||
<span class="icon"><i class="fa"
|
||||
:class="{'fa-spinner fa-spin': serversLoading,'fa-refresh' : !serversLoading }"/></span>
|
||||
<span class="is-hidden-mobile">Reload</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">
|
||||
Try to use non <strong>.plex.direct</strong> urls if possible, as they are often have problems
|
||||
working in docker. If you use custom domain for your plex server and it's not showing in the list,
|
||||
you can add it via Plex settings page. <strong>Plex > Settings > Network > Custom server access
|
||||
URLs:</strong>. For more information
|
||||
<NuxtLink target="_blank"
|
||||
to="https://support.plex.tv/articles/200430283-network/#Custom-server-access-URLs"
|
||||
v-text="'Visit this link'"/>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-link" v-if="!serversLoading"></i>
|
||||
<i class="fas fa-spinner fa-pulse" v-else></i>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="backend_ownership">Are you invited guest to this backend?</label>
|
||||
<div class="control">
|
||||
<input id="backend_ownership" type="checkbox" class="switch is-success"
|
||||
v-model="backend.options.plex_guest_user" :disabled="stage > 2">
|
||||
<label for="backend_ownership" class="is-unselectable">
|
||||
{{ backend.options?.plex_guest_user ? 'Yes' : 'No' }}
|
||||
</label>
|
||||
</div>
|
||||
<p class="help">
|
||||
<NuxtLink @click="getServers" v-text="'Attempt to discover servers associated with the token.'"
|
||||
v-if="stage < 2"/>
|
||||
Try to use non <code>.plex.direct</code> urls if possible, as they are often have problems working in
|
||||
docker. If you use custom domain for your plex server and it's not showing in the list, you can add it
|
||||
via Plex settings page. <code>Plex > Settings > Network > <strong>Custom server access
|
||||
URLs:</strong></code>. For more information
|
||||
<NuxtLink target="_blank"
|
||||
to="https://support.plex.tv/articles/200430283-network/#Custom-server-access-URLs"
|
||||
v-text="'Visit this link'"/>
|
||||
.
|
||||
This stops WatchState from attempting to generate access-tokens for different users.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<div class="field" v-if="stage >= 3">
|
||||
<label class="label">
|
||||
Associated User
|
||||
</label>
|
||||
<div class="control has-icons-left">
|
||||
<div class="select is-fullwidth">
|
||||
<select v-model="backend.user" class="is-capitalized" :disabled="stage > 3">
|
||||
<option value="" disabled>Select User</option>
|
||||
<option v-for="user in users" :key="'uid-' + user.id" :value="user.id">
|
||||
{{ user.name }}
|
||||
</option>
|
||||
</select>
|
||||
<label class="label">User</label>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded has-icons-left">
|
||||
<div class="select is-fullwidth">
|
||||
<select v-model="backend.user" class="is-capitalized" :disabled="stage > 3">
|
||||
<option value="" disabled>Select User</option>
|
||||
<option v-for="user in users" :key="'uid-' + user.id" :value="user.id">
|
||||
{{ user.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-user-tie" v-if="!usersLoading"/>
|
||||
<i class="fas fa-spinner fa-pulse" v-else/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-primary" type="button" :disabled="usersLoading || stage > 3"
|
||||
@click="getUsers">
|
||||
<span class="icon"><i class="fa"
|
||||
:class="{'fa-spinner fa-spin': usersLoading,'fa-refresh' : !usersLoading }"/></span>
|
||||
<span class="is-hidden-mobile">Reload</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">Which user we should associate this backend with?</p>
|
||||
</div>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-user-tie" v-if="!usersLoading"></i>
|
||||
<i class="fas fa-spinner fa-pulse" v-else></i>
|
||||
</div>
|
||||
<p class="help">
|
||||
Which user we should associate this backend with?
|
||||
<NuxtLink @click="getUsers" v-text="'Retrieve User ids from backend.'" v-if="stage < 4"/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="stage >= 4">
|
||||
<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 play and progress updates 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" class="is-unselectable">{{ backend.import.enabled ? 'Yes' : 'No' }}</label>
|
||||
<p class="help is-bold">
|
||||
Import means to get the data from the backend and store it in WatchState.
|
||||
</p>
|
||||
</div>
|
||||
<p class="help is-bold has-text-danger">
|
||||
<span class="icon"><i class="fas fa-info-circle"/></span>
|
||||
Get play state and progress from this backend.
|
||||
</p>
|
||||
</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>
|
||||
<label class="label" for="backend_import_metadata">Import metadata 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" class="is-unselectable">
|
||||
{{ backend.options?.IMPORT_METADATA_ONLY ? 'Yes' : 'No' }}
|
||||
</label>
|
||||
<p class="help has-text-danger">
|
||||
For best performance, we need at least to import metadata from the backend. This will not alter your
|
||||
play state.
|
||||
</p>
|
||||
</div>
|
||||
<p class="help has-text-danger is-bold">
|
||||
<span class="icon"><i class="fas fa-info-circle"/></span>
|
||||
As you have disabled the state import, you should enable this option for efficient and fast updates
|
||||
to this backend.
|
||||
</p>
|
||||
</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">Send play and progress updates 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" class="is-unselectable">{{ backend.export.enabled ? 'Yes' : 'No' }}</label>
|
||||
<p class="help is-bold">
|
||||
Export mean sending data from WatchState to this backend.
|
||||
</p>
|
||||
</div>
|
||||
<p class="help is-bold has-text-danger">
|
||||
<span class="icon"><i class="fas fa-info-circle"/></span>
|
||||
The backend will not receive any data from WatchState if this is disabled.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field" v-if="backend.webhook">
|
||||
@@ -296,10 +335,10 @@
|
||||
<label for="webhook_match_user" class="is-unselectable">
|
||||
{{ backend.webhook.match.user ? 'Yes' : 'No' }}
|
||||
</label>
|
||||
<p class="help">
|
||||
Check webhook payload for user id match. if it does not match, the payload will be ignored.
|
||||
</p>
|
||||
</div>
|
||||
<p class="help">
|
||||
Check webhook payload for user id match. if it does not match, the payload will be ignored.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field" v-if="backend.webhook">
|
||||
@@ -310,56 +349,58 @@
|
||||
<label for="webhook_match_uuid" class="is-unselectable">
|
||||
{{ backend.webhook.match.uuid ? 'Yes' : 'No' }}
|
||||
</label>
|
||||
<p class="help">
|
||||
Check webhook payload for backend unique id. if it does not match, the payload will be ignored.
|
||||
</p>
|
||||
</div>
|
||||
<p class="help">
|
||||
Check webhook payload for backend unique id. if it does not match, the payload will be ignored.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="field">
|
||||
<h1 class="title is-4">One Time Operations</h1>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<hr>
|
||||
<label class="label has-text-danger" for="backup_data">
|
||||
Create backup for this backend data?
|
||||
</label>
|
||||
<div class="control">
|
||||
<input id="backup_data" type="checkbox" class="switch is-success" v-model="backup_data">
|
||||
<label for="backup_data" class="is-unselectable">{{ backup_data ? 'Yes' : 'No' }}</label>
|
||||
<p class="help">
|
||||
This will run a one time backup for the backend data.
|
||||
</p>
|
||||
</div>
|
||||
<p class="help">
|
||||
This will run a one time backup for the backend data.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field" v-if="backends.length < 1">
|
||||
<hr>
|
||||
<label class="label" for="force_import">
|
||||
Force one time import from this backend?
|
||||
</label>
|
||||
<div class="control">
|
||||
<input id="force_import" type="checkbox" class="switch is-success" v-model="force_import">
|
||||
<label for="force_import" class="is-unselectable">{{ force_import ? 'Yes' : 'No' }}</label>
|
||||
<p class="help">
|
||||
<span class="icon"><i class="fas fa-info-circle"></i></span>
|
||||
Run a one time import from this backend after adding it.
|
||||
</p>
|
||||
</div>
|
||||
<p class="help">
|
||||
<span class="icon"><i class="fas fa-info-circle"/></span>
|
||||
Run a one time import from this backend after adding it.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field" v-if="backends.length > 0">
|
||||
<hr>
|
||||
<label class="label has-text-danger" for="force_export">
|
||||
Force Export local data to this backend?
|
||||
</label>
|
||||
<div class="control">
|
||||
<input id="force_export" type="checkbox" class="switch is-success" v-model="force_export">
|
||||
<label for="force_export" class="is-unselectable">{{ force_export ? 'Yes' : 'No' }}</label>
|
||||
<p class="help has-text-danger">
|
||||
<span class="icon"><i class="fas fa-info-circle"></i></span>
|
||||
THIS OPTION WILL OVERRIDE THE BACKEND DATA with locally stored data.
|
||||
</p>
|
||||
</div>
|
||||
<p class="help has-text-danger is-bold">
|
||||
<span class="icon"><i class="fas fa-info-circle"/></span>
|
||||
THIS OPTION WILL OVERRIDE THE BACKEND DATA with locally stored data.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -367,20 +408,20 @@
|
||||
|
||||
<div class="card-footer-item" v-if="stage >= 1">
|
||||
<button class="button is-fullwidth is-warning" type="button" @click="stage = stage - 1">
|
||||
<span class="icon"><i class="fas fa-arrow-left"></i></span>
|
||||
<span class="icon"><i class="fas fa-arrow-left"/></span>
|
||||
<span>Previous Step</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card-footer-item" v-if="stage < maxStages">
|
||||
<button class="button is-fullwidth is-info" type="button" @click="changeStep()">
|
||||
<span class="icon"><i class="fas fa-arrow-right"></i></span>
|
||||
<span class="icon"><i class="fas fa-arrow-right"/></span>
|
||||
<span>Next Step</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-footer-item" v-else>
|
||||
<button class="button is-fullwidth is-primary" type="submit">
|
||||
<span class="icon"><i class="fas fa-plus"></i></span>
|
||||
<span class="icon"><i class="fas fa-plus"/></span>
|
||||
<span>Add Backend</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -685,19 +726,14 @@ const getUsers = async (showAlert = true) => {
|
||||
token: backend.value.token,
|
||||
url: backend.value.url,
|
||||
uuid: backend.value.uuid,
|
||||
options: {},
|
||||
};
|
||||
|
||||
if (backend.value.options && backend.value.options.ADMIN_TOKEN) {
|
||||
data.options = {
|
||||
ADMIN_TOKEN: backend.value.options.ADMIN_TOKEN
|
||||
['ADMIN_TOKEN', 'plex_guest_user', 'PLEX_USER_PIN', 'is_limited_token'].forEach(v => {
|
||||
if (backend.value.options && backend.value.options[v]) {
|
||||
data.options[v] = backend.value.options[v]
|
||||
}
|
||||
}
|
||||
|
||||
if (backend.value.options && backend.value.options.PLEX_USER_PIN) {
|
||||
data.options = {
|
||||
PLEX_USER_PIN: backend.value.options.PLEX_USER_PIN
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const response = await request(`/backends/users/${backend.value.type}?tokens=1`, {
|
||||
method: 'POST',
|
||||
@@ -873,7 +909,7 @@ const addBackend = async () => {
|
||||
}
|
||||
|
||||
const getServers = async () => {
|
||||
if ('plex' !== backend.value.type || servers.value.length > 0) {
|
||||
if ('plex' !== backend.value.type) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -881,6 +917,7 @@ const getServers = async () => {
|
||||
notification('error', 'Error', `Token is required to get list of servers.`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
serversLoading.value = true
|
||||
|
||||
@@ -916,9 +953,9 @@ const getServers = async () => {
|
||||
|
||||
const updateIdentifier = async () => {
|
||||
backend.value.uuid = servers.value.find(s => s.uri === backend.value.url).identifier
|
||||
if (backend.value.uuid) {
|
||||
await getUsers()
|
||||
}
|
||||
// if (backend.value.uuid) {
|
||||
// await getUsers()
|
||||
// }
|
||||
}
|
||||
|
||||
const n_proxy = (type, title, message, e = null) => {
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-12 is-clearfix is-unselectable">
|
||||
<span class="title is-4">
|
||||
<span class="icon"><i class="fas fa-server"></i> </span>
|
||||
<NuxtLink to="/backends" v-text="'Backends'" />
|
||||
<span class="icon"><i class="fas fa-server"/> </span>
|
||||
<NuxtLink to="/backends" v-text="'Backends'"/>
|
||||
-
|
||||
<NuxtLink :to="'/backend/' + id" v-text="id" />
|
||||
<NuxtLink :to="'/backend/' + id" v-text="id"/>
|
||||
: Edit
|
||||
</span>
|
||||
|
||||
@@ -21,13 +21,11 @@
|
||||
|
||||
<div class="column is-12" v-if="isLimitedToken">
|
||||
<Message title="For your information" message_class="has-background-warning-90 has-text-dark"
|
||||
icon="fas fa-info-circle">
|
||||
icon="fas fa-info-circle">
|
||||
<p>
|
||||
This backend is using accesstoken instead of API keys, And this method untested and may not work as
|
||||
expected.
|
||||
Please make sure you know what you are doing. Simple operations like <code>Import</code>,
|
||||
<code>Export</code>
|
||||
should work fine.
|
||||
expected. Please make sure you know what you are doing. Simple operations like <strong>Import</strong>,
|
||||
<strong>Export</strong> should work fine.
|
||||
</p>
|
||||
<p>
|
||||
How the access token interact with the rest of the API is undefined and untested by us. Please use with
|
||||
@@ -38,7 +36,7 @@
|
||||
|
||||
<div class="column is-12" v-if="isLoading">
|
||||
<Message message_class="is-background-info-90 has-text-dark" title="Loading" icon="fas fa-spinner fa-spin"
|
||||
message="Loading backend settings. Please wait..." />
|
||||
message="Loading backend settings. Please wait..."/>
|
||||
</div>
|
||||
|
||||
<div v-else class="column is-12">
|
||||
@@ -53,30 +51,19 @@
|
||||
<div class="field">
|
||||
<label class="label">Local User</label>
|
||||
<div class="control has-icons-left">
|
||||
<div class="select is-fullwidth">
|
||||
<select class="is-capitalized" disabled>
|
||||
<option v-text="api_user" />
|
||||
</select>
|
||||
</div>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-users"></i>
|
||||
</div>
|
||||
<p class="help">The local user which this backend is associated with.</p>
|
||||
<input type="text" class="input is-capitalized" :value="api_user" required readonly disabled>
|
||||
<div class="icon is-left"><i class="fas fa-user"/></div>
|
||||
</div>
|
||||
<p class="help is-unselectable">The local user which this backend is associated with.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Name</label>
|
||||
<div class="control has-icons-left">
|
||||
<input class="input" type="text" v-model="backend.name" required readonly disabled>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-id-badge"></i>
|
||||
</div>
|
||||
<p class="help">
|
||||
Choose a unique name for this backend. You cannot change it later. Backend name must be in <code>lower
|
||||
case a-z, 0-9 and _</code> only.
|
||||
</p>
|
||||
<div class="icon is-left"><i class="fas fa-id-badge"/></div>
|
||||
</div>
|
||||
<p class="help is-unselectable">The backend name in WatchState.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
@@ -84,43 +71,46 @@
|
||||
<div class="control has-icons-left">
|
||||
<input class="input" type="text" v-model="backend.type" readonly disabled>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-server"></i>
|
||||
<i class="fas fa-server"/>
|
||||
</div>
|
||||
<p class="help">
|
||||
The backend server type. The supported types are <code>{{
|
||||
supported.map(v => ucFirst(v)).join(', ')
|
||||
}}</code>.
|
||||
</p>
|
||||
</div>
|
||||
<p class="help is-unselectable">Backend Type.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">URL</label>
|
||||
<div class="control has-icons-left">
|
||||
<div class="select is-fullwidth" v-if="servers.length > 0">
|
||||
<select v-model="backend.url" class="is-capital" @change="updateIdentifier" required>
|
||||
<option value="" disabled>Select Server</option>
|
||||
<option v-for="server in servers" :key="server.uuid" :value="server.uri">
|
||||
{{ server.name }} - {{ server.uri }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded has-icons-left">
|
||||
<div class="select is-fullwidth" v-if="servers.length > 0">
|
||||
<select v-model="backend.url" class="is-capital" @change="updateIdentifier" required>
|
||||
<option value="" disabled>Select Server</option>
|
||||
<option v-for="server in servers" :key="server.uuid" :value="server.uri">
|
||||
{{ server.name }} - {{ server.uri }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<input class="input" type="text" v-model="backend.url" v-else required>
|
||||
<div class="icon is-left"><i class="fas fa-link"/></div>
|
||||
</div>
|
||||
<div class="control" v-if="servers.length > 0">
|
||||
<button class="button is-primary" type="button" :disabled="serversLoading" @click="getServers">
|
||||
<span class="icon"><i class="fa"
|
||||
:class="{'fa-spinner fa-spin': serversLoading,'fa-refresh' : !serversLoading }"/></span>
|
||||
<span class="is-hidden-mobile">Reload</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input class="input" type="text" v-model="backend.url" v-else required>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-link"></i>
|
||||
</div>
|
||||
<p class="help">
|
||||
<template v-if="servers.length < 1">
|
||||
Enter the URL of the backend. For example
|
||||
<code v-if="'plex' === backend.type">http://192.168.8.11:32400</code>
|
||||
<code v-else>http://192.168.8.100:8096</code>
|
||||
.
|
||||
</template>
|
||||
<template v-else>
|
||||
Those are the servers associated with the Plex Token. Select the server you want to use.
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
<p class="help">
|
||||
<template v-if="servers.length < 1">
|
||||
Enter the URL of the backend. For example
|
||||
<strong>http://192.168.8.100:{{ 'plex' === backend.type ? '32400' : '8096' }}</strong>.
|
||||
</template>
|
||||
<template v-else>Select the server you want to use.</template>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
@@ -133,28 +123,25 @@
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded has-icons-left">
|
||||
<input class="input" v-model="backend.token" required
|
||||
:type="false === exposeToken ? 'password' : 'text'">
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-key"></i>
|
||||
</div>
|
||||
:type="false === exposeToken ? 'password' : 'text'">
|
||||
<div class="icon is-left"><i class="fas fa-key"/></div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="button" class="button is-primary" @click="exposeToken = !exposeToken"
|
||||
v-tooltip="'Toggle 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>
|
||||
v-tooltip="'Toggle token'">
|
||||
<span class="icon"><i class="fas" :class="exposeToken ? 'fa-eye-slash' : 'fa-eye'"/></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">
|
||||
<template v-if="'plex' === backend.type">
|
||||
Enter the <code>X-Plex-Token</code>.
|
||||
Enter the <strong>X-Plex-Token</strong>.
|
||||
<NuxtLink target="_blank"
|
||||
to="https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/"
|
||||
v-text="'Visit This article for more information.'" />
|
||||
to="https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/"
|
||||
v-text="'Visit This article for more information.'"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
You can generate a new API key from <code>Dashboard > Settings > API Keys</code>.
|
||||
You can generate a new API key from <strong>Dashboard > Settings > API Keys</strong>.
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
@@ -163,134 +150,166 @@
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Backend Unique ID</label>
|
||||
<div class="control has-icons-left">
|
||||
<input class="input" type="text" v-model="backend.uuid" required :disabled="isLimitedToken">
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-cloud" v-if="!uuidLoading"></i>
|
||||
<i class="fas fa-spinner fa-pulse" v-else></i>
|
||||
<div class="field-body">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded has-icons-left">
|
||||
<input type="text" class="input is-fullwidth" v-model="backend.uuid" required
|
||||
:disabled="isLimitedToken">
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-cloud" v-if="!uuidLoading"/>
|
||||
<i class="fas fa-spinner fa-pulse" v-else/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control" v-if="!isLimitedToken">
|
||||
<button class="button is-primary" type="button" :disabled="uuidLoading" @click="getUUid">
|
||||
<span class="icon"><i class="fa"
|
||||
:class="{'fa-spinner fa-spin': uuidLoading,'fa-refresh' : !uuidLoading }"/></span>
|
||||
<span class="is-hidden-mobile">Reload</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">
|
||||
<span v-if="'plex' === backend.type">
|
||||
The backend unique ID is random string generated on server setup, In Plex case it used to inquiry
|
||||
about the users associated with the server to generate limited <code>X-Plex-Token</code> for them.
|
||||
It
|
||||
used by webhooks as a filter to match the backend. in-case you are member of multiple servers.
|
||||
</span>
|
||||
<span v-else>
|
||||
The backend unique ID is a random string generated on server setup. It is used to identify the
|
||||
backend
|
||||
uniquely. This is used for webhook matching and filtering.
|
||||
</span>
|
||||
<NuxtLink @click="getUUid" v-if="!isLimitedToken" v-text="'Get from the backend.'" />
|
||||
</p>
|
||||
</div>
|
||||
<p class="help">
|
||||
<span v-if="'plex' === backend.type">
|
||||
The backend unique ID is random string generated on server setup, In Plex case it used to inquiry
|
||||
about the users associated with the server to generate limited <strong>X-Plex-Token</strong> for
|
||||
them.
|
||||
It used by webhooks as a filter to match the backend. in-case you are member of multiple servers.
|
||||
</span>
|
||||
<span v-else>
|
||||
The backend unique ID is a random string generated on server setup. It is used to identify the
|
||||
backend uniquely. This is used for webhook matching and filtering.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">
|
||||
<template v-if="users.length > 0">Associated User</template>
|
||||
<template v-else>User ID</template>
|
||||
</label>
|
||||
<div class="control has-icons-left">
|
||||
<div class="select is-fullwidth" v-if="users.length > 0">
|
||||
<select v-model="backend.user" class="is-capitalized" :disabled="isLimitedToken">
|
||||
<option v-for="user in users" :key="'uid-' + user.id" :value="user.id">
|
||||
{{ user.name }}
|
||||
</option>
|
||||
</select>
|
||||
<label class="label">{{ users.length > 0 ? 'User' : 'User ID' }}</label>
|
||||
<div class="field-body">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded has-icons-left">
|
||||
<div class="select is-fullwidth" v-if="users.length > 0">
|
||||
<select v-model="backend.user" class="is-capitalized" :disabled="isLimitedToken">
|
||||
<option v-for="user in users" :key="'uid-' + user.id" :value="user.id">
|
||||
{{ user.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<input class="input is-fullwidth" type="text" v-model="backend.user" v-else>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-user-tie" v-if="!usersLoading"/>
|
||||
<i class="fas fa-spinner fa-pulse" v-else/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control" v-if="!isLimitedToken">
|
||||
<button class="button is-primary" type="button" :disabled="usersLoading" @click="getUsers">
|
||||
<span class="icon"><i class="fa"
|
||||
:class="{'fa-spinner fa-spin': usersLoading,'fa-refresh' : !usersLoading }"/></span>
|
||||
<span class="is-hidden-mobile">Reload</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<input class="input" type="text" v-model="backend.user" v-else>
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-user-tie" v-if="!usersLoading"></i>
|
||||
<i class="fas fa-spinner fa-pulse" v-else></i>
|
||||
</div>
|
||||
<p class="help">
|
||||
<span v-if="'plex' === backend.type">
|
||||
Plex doesn't use standard API practice for identifying users. They use <code>X-Plex-Token</code>
|
||||
to identify the user. The list can only be populated if the user is admin or has
|
||||
<code>ADMIN_TOKEN</code> set in Additional options.
|
||||
</span>
|
||||
<span v-else>
|
||||
Which <code>{{ ucFirst(backend.type) }}</code> user should this backend use? The User ID will
|
||||
determine the data we get from the backend. And for webhook matching and filtering.
|
||||
</span>
|
||||
<a href="javascript:void(0)" @click="getUsers" v-if="!isLimitedToken">
|
||||
Get users.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<p class="help">
|
||||
<span v-if="'plex' === backend.type">
|
||||
Plex doesn't use standard API practice for identifying users. They use <strong>X-Plex-Token</strong>
|
||||
to identify the user. The list can only be populated if the user is admin or has
|
||||
<strong>ADMIN_TOKEN</strong> set in additional options.
|
||||
</span>
|
||||
<span v-else>
|
||||
Which user should this backend configuration use? The User will determine the data we get from
|
||||
the backend. And for webhook matching and filtering.
|
||||
</span>
|
||||
</p>
|
||||
</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 play and progress updates 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>
|
||||
<p class="help">
|
||||
Import means to get the data from the backend and store it in the database.
|
||||
</p>
|
||||
<label for="backend_import" class="is-unselectable">
|
||||
{{ backend.import.enabled ? 'Yes' : 'No' }}
|
||||
</label>
|
||||
</div>
|
||||
<p class="help is-bold has-text-danger">
|
||||
<span class="icon"><i class="fas fa-info-circle"/></span>
|
||||
Get play state and progress from this backend.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field" v-if="backend.import && !backend.import.enabled">
|
||||
<label class="label" for="backend_import_metadata">Import metadata only from this backend?</label>
|
||||
<label class="label" for="backend_import_metadata">Import metadata 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>
|
||||
v-model="backend.options.IMPORT_METADATA_ONLY">
|
||||
<label for="backend_import_metadata" class="is-unselectable">
|
||||
{{ backend.options.IMPORT_METADATA_ONLY ? 'Yes' : 'No' }}
|
||||
</label>
|
||||
</div>
|
||||
<p class="help has-text-danger is-bold">
|
||||
<span class="icon"><i class="fas fa-info-circle"/></span>
|
||||
As you have disabled the state import, you should enable this option for efficient and fast updates
|
||||
to this backend.
|
||||
</p>
|
||||
</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">Send play and progress updates 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>
|
||||
<p class="help">
|
||||
Export means to send the data from the database to this backend.
|
||||
</p>
|
||||
<label for="backend_export" class="is-unselectable">
|
||||
{{ backend.export.enabled ? 'Yes' : 'No' }}
|
||||
</label>
|
||||
</div>
|
||||
<p class="help is-bold has-text-danger">
|
||||
<span class="icon"><i class="fas fa-info-circle"/></span>
|
||||
The backend will not receive any data from WatchState if this is disabled.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field" v-if="backend.webhook">
|
||||
<label class="label" for="webhook_match_user">Webhook match user</label>
|
||||
<label class="label" for="webhook_match_user">Enable match user for webhook?</label>
|
||||
<div class="control">
|
||||
<input id="webhook_match_user" type="checkbox" class="switch is-success"
|
||||
v-model="backend.webhook.match.user">
|
||||
<label for="webhook_match_user">Enable</label>
|
||||
<p class="help">
|
||||
Check webhook payload for user id match. if it does not match, the payload will be ignored.
|
||||
</p>
|
||||
v-model="backend.webhook.match.user">
|
||||
<label for="webhook_match_user" class="is-unselectable">
|
||||
{{ backend.webhook.match.user ? 'Yes' : 'No' }}
|
||||
</label>
|
||||
</div>
|
||||
<p class="help">
|
||||
Check webhook payload for user id match. if it does not match, the payload will be ignored.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field" v-if="backend.webhook">
|
||||
<label class="label" for="webhook_match_uuid">Webhook match backend id</label>
|
||||
<label class="label" for="webhook_match_uuid">Enable match backend id for webhook?</label>
|
||||
<div class="control">
|
||||
<input id="webhook_match_uuid" type="checkbox" class="switch is-success"
|
||||
v-model="backend.webhook.match.uuid">
|
||||
<label for="webhook_match_uuid">Enable</label>
|
||||
<p class="help">
|
||||
Check webhook payload for backend unique id. if it does not match, the payload will be ignored.
|
||||
</p>
|
||||
v-model="backend.webhook.match.uuid">
|
||||
<label for="webhook_match_uuid">
|
||||
{{ backend.webhook.match.uuid ? 'Yes' : 'No' }}
|
||||
</label>
|
||||
</div>
|
||||
<p class="help">
|
||||
Check webhook payload for backend unique id. if it does not match, the payload will be ignored.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="field">
|
||||
<label class="label is-clickable" @click="showOptions = !showOptions">
|
||||
<label class="label is-clickable is-unselectable" @click="showOptions = !showOptions">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i v-if="showOptions" class="fas fa-arrow-up"></i>
|
||||
<i v-else class="fas fa-arrow-down"></i>
|
||||
<i v-if="showOptions" class="fas fa-arrow-up"/>
|
||||
<i v-else class="fas fa-arrow-down"/>
|
||||
</span>
|
||||
<span>Additional options...</span>
|
||||
</span>
|
||||
</label>
|
||||
<p class="help is-unselectable">
|
||||
These are advanced options. Please only change them, if you are told to do so by the developers.
|
||||
</p>
|
||||
<template v-if="showOptions">
|
||||
<div class="columns is-multiline is-mobile">
|
||||
<template v-for="_option in flatOptionPaths" :key="'bo-'+_option">
|
||||
@@ -298,18 +317,18 @@
|
||||
<input type="text" class="input" :value="_option" readonly disabled>
|
||||
<p class="help is-unselectable">
|
||||
<span class="icon has-text-info">
|
||||
<i class="fas fa-info-circle" :class="{ 'fa-bounce': newOptions[_option] }"></i>
|
||||
<i class="fas fa-info-circle" :class="{ 'fa-bounce': newOptions[_option] }"/>
|
||||
</span>
|
||||
{{ option_describe(_option) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<input type="text" class="input" :value="option_get(_option)"
|
||||
@input="e => option_set(_option, e.target.value)" required>
|
||||
@input="e => option_set(_option, e.target.value)" required>
|
||||
</div>
|
||||
<div class="column is-1">
|
||||
<button class="button is-danger" @click.prevent="removeOption(_option)">
|
||||
<span class="icon"><i class="fas fa-trash" /></span>
|
||||
<span class="icon"><i class="fas fa-trash"/></span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -317,7 +336,7 @@
|
||||
<div class="columns is-multiline is-mobile">
|
||||
<div class="column is-12">
|
||||
<span class="icon-text">
|
||||
<span class="icon"><i class="fas fa-plus"></i></span>
|
||||
<span class="icon"><i class="fas fa-plus"/></span>
|
||||
<span>Add new option</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -326,7 +345,7 @@
|
||||
<select v-model="selectedOption">
|
||||
<option value="">Select Option</option>
|
||||
<option v-for="option in filteredOptions(optionsList)" :key="'opt-' + option.key"
|
||||
:value="option.key">
|
||||
:value="option.key">
|
||||
{{ option.key }}
|
||||
</option>
|
||||
</select>
|
||||
@@ -338,7 +357,7 @@
|
||||
<div class="column is-1">
|
||||
<button class="button is-primary" @click.prevent="addOption">
|
||||
<span class="icon">
|
||||
<i class="fas fa-add"></i>
|
||||
<i class="fas fa-add"/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -348,11 +367,11 @@
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="button card-footer-item is-fullwidth is-primary" type="submit">
|
||||
<span class="icon"><i class="fas fa-save"></i></span>
|
||||
<span class="icon"><i class="fas fa-save"/></span>
|
||||
<span>Save Settings</span>
|
||||
</button>
|
||||
<NuxtLink class="card-footer-item button is-fullwidth is-danger" :to="`/backend/${id}`">
|
||||
<span class="icon"><i class="fas fa-cancel"></i></span>
|
||||
<span class="icon"><i class="fas fa-cancel"/></span>
|
||||
<span>Cancel changes</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
@@ -365,9 +384,9 @@
|
||||
|
||||
<script setup>
|
||||
import 'assets/css/bulma-switch.css'
|
||||
import { notification, ucFirst } from '~/utils/index'
|
||||
import {notification} from '~/utils/index'
|
||||
import Message from '~/components/Message'
|
||||
import { useStorage } from "@vueuse/core";
|
||||
import {useStorage} from "@vueuse/core";
|
||||
import request from "~/utils/request.js";
|
||||
|
||||
const id = useRoute().params.backend
|
||||
@@ -380,9 +399,9 @@ const backend = ref({
|
||||
token: '',
|
||||
uuid: '',
|
||||
user: '',
|
||||
import: { enabled: false },
|
||||
export: { enabled: false },
|
||||
webhook: { match: { user: false, uuid: false } },
|
||||
import: {enabled: false},
|
||||
export: {enabled: false},
|
||||
webhook: {match: {user: false, uuid: false}},
|
||||
options: {}
|
||||
})
|
||||
|
||||
@@ -406,7 +425,7 @@ const selectedOptionHelp = computed(() => {
|
||||
return option ? option.description : ''
|
||||
});
|
||||
|
||||
useHead({ title: 'Backends - Edit: ' + id })
|
||||
useHead({title: 'Backends - Edit: ' + id})
|
||||
|
||||
const loadContent = async () => {
|
||||
supported.value = await (await request('/system/supported')).json()
|
||||
@@ -442,7 +461,7 @@ const saveContent = async () => {
|
||||
try {
|
||||
const response = await request(`/backend/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(json_text)
|
||||
})
|
||||
|
||||
@@ -454,7 +473,7 @@ const saveContent = async () => {
|
||||
|
||||
notification('success', 'Success', `Successfully updated '${id}' settings.`)
|
||||
const to = !redirect.startsWith('/') ? `/backend/${id}` : redirect
|
||||
await navigateTo({ path: to })
|
||||
await navigateTo({path: to})
|
||||
} catch (e) {
|
||||
notification('error', 'Error', `Request error. ${e.message}`)
|
||||
}
|
||||
@@ -471,7 +490,7 @@ const removeOption = async (key) => {
|
||||
return
|
||||
}
|
||||
|
||||
const response = await request(`/backend/${id}/option/options.${key}`, { method: 'DELETE' })
|
||||
const response = await request(`/backend/${id}/option/options.${key}`, {method: 'DELETE'})
|
||||
|
||||
if (!response.ok) {
|
||||
const json = await response.json()
|
||||
@@ -540,20 +559,15 @@ const getUsers = async (showAlert = true) => {
|
||||
token: backend.value.token,
|
||||
url: backend.value.url,
|
||||
uuid: backend.value.uuid,
|
||||
user: backend.value.user
|
||||
user: backend.value.user,
|
||||
options: {},
|
||||
};
|
||||
|
||||
if (backend.value.options && backend.value.options?.ADMIN_TOKEN) {
|
||||
data.options = {
|
||||
ADMIN_TOKEN: backend.value.options.ADMIN_TOKEN
|
||||
['ADMIN_TOKEN', 'plex_guest_user', 'PLEX_USER_PIN', 'is_limited_token'].forEach(v => {
|
||||
if (backend.value.options && backend.value.options[v]) {
|
||||
data.options[v] = backend.value.options[v]
|
||||
}
|
||||
}
|
||||
|
||||
if (backend.value.options && backend.value.options?.is_limited_token) {
|
||||
data.options = {
|
||||
is_limited_token: Boolean(backend.value.options.is_limited_token)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const response = await request(`/backends/users/${backend.value.type}?tokens=1`, {
|
||||
method: 'POST',
|
||||
@@ -599,7 +613,7 @@ const filteredOptions = options => {
|
||||
}
|
||||
|
||||
const getServers = async () => {
|
||||
if ('plex' !== backend.value.type || servers.value.length > 0) {
|
||||
if ('plex' !== backend.value.type) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,10 @@ environment without modifying the original data.
|
||||
### Adding your backend with has the most accurate data
|
||||
|
||||
First, go to <!--i:fa-server--> **Backends** and click on the <!--i:fa-plus--> **Add Backend** button. Follow the
|
||||
interactive setup guide. When you reach the step asking *`Export data to this backend?`*, disable it, this applies to
|
||||
your main backend as you don’t want to alter its data. Keep *`Import data from this backend?`* enabled, which will allow
|
||||
interactive setup guide. When you reach the step asking *`Send play and progress updates to this backend?`*, disable it,
|
||||
this applies to
|
||||
your main backend as you don’t want to alter its data. Keep *`Import play and progress updates from this backend?`*
|
||||
enabled, which will allow
|
||||
you to import data from the backend.
|
||||
|
||||
Enable the *`Force one time import from this backend?`* option to import your current data into WatchState. This will
|
||||
@@ -62,16 +64,17 @@ For each backend, follow these steps:
|
||||
|
||||
Do exactly as you did for the main backend, but make the following changes:
|
||||
|
||||
- *`Import data from this backend?`*: No
|
||||
- *`Import metadata only from this backend?`*: Yes
|
||||
- *`Export data to this backend?`*: Yes
|
||||
- *`Import play and progress updates from this backend?`*: No
|
||||
- *`Import metadata from this backend?`*: Yes
|
||||
- *`Send play and progress updates to this backend?`*: Yes
|
||||
- *`Force Export local data to this backend?`*: This depends on your setup:
|
||||
- If you have only one extra backend and have already imported your main backend data, select *Yes* and skip Step 2.
|
||||
- If you have multiple backends, keep this option disabled and proceed to Step 2.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Selecting the correct options is crucial to avoid altering the data in the main backend. The
|
||||
> *`Import metadata only from this backend?`* option will only appear if you have *`Import data from this backend?`*
|
||||
> *`Import metadata from this backend?`* option will only appear if you have
|
||||
*`Import play and progress updates from this backend?`*
|
||||
> disabled.
|
||||
|
||||
### Step 2
|
||||
|
||||
@@ -19,8 +19,8 @@ On the export side, we compare the backend's last sync date with any local chang
|
||||
items that need updating for each backend. If there are only a few changes, we trigger a quick sync operation
|
||||
`push mode`. If the changes are more extensive, we perform a full export, which compares all remote data with the local
|
||||
data. This full export only happens when there are many changes and/or metadata is missing from the backend, which is
|
||||
why it's crucial to keep the `Import data from this backend?` or, at a minimum, the
|
||||
`Import metadata only from this backend?` option enabled.
|
||||
why it's crucial to keep the `Import play and progress updates from this backend?` or, at a minimum, the
|
||||
`Import metadata from this backend?` option enabled.
|
||||
|
||||
# Setting Up Two-Way Sync
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ final class GetUsersList
|
||||
|
||||
private array $rawRequests = [];
|
||||
|
||||
|
||||
public function __construct(iHttp $http, protected iLogger $logger)
|
||||
{
|
||||
$this->http = new RetryableHttpClient(client: $http, maxRetries: $this->maxRetry, logger: $this->logger);
|
||||
@@ -156,6 +155,10 @@ final class GetUsersList
|
||||
*/
|
||||
private function getExternalUsers(Context $context, array $opts = []): Response
|
||||
{
|
||||
if (true === (bool)ag($context->options, Options::PLEX_GUEST_USER, false)) {
|
||||
return new Response(status: true, response: []);
|
||||
}
|
||||
|
||||
$url = Container::getNew(iUri::class)->withPort(443)->withScheme('https')->withHost('plex.tv')
|
||||
->withPath('/api/users/');
|
||||
|
||||
@@ -201,7 +204,7 @@ final class GetUsersList
|
||||
$url = $url->withQuery(http_build_query(['pin' => $pin]));
|
||||
}
|
||||
|
||||
$this->logger->debug("Requesting '{user}@{backend}' external users accesstokens.", [
|
||||
$this->logger->debug("Requesting '{user}@{backend}' external users access-tokens.", [
|
||||
'user' => $context->userContext->name,
|
||||
'backend' => $context->backendName,
|
||||
'url' => (string)$url,
|
||||
@@ -240,21 +243,27 @@ final class GetUsersList
|
||||
*
|
||||
* @return array Return processed response.
|
||||
* @throws iException if an error occurs during the request.
|
||||
* @throws JsonException if an error occurs during the JSON parsing.
|
||||
*/
|
||||
private function processExternalUsers(iResponse $response, Context $context, iUri $url): array
|
||||
{
|
||||
$data = json_decode(
|
||||
json: json_encode(simplexml_load_string($response->getContent(false))),
|
||||
associative: true,
|
||||
flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE
|
||||
);
|
||||
$content = simplexml_load_string($response->getContent(false));
|
||||
$data = [];
|
||||
foreach ($content->User ?? [] as $_user) {
|
||||
$user = [];
|
||||
// @INFO: This workaround is needed, for some reason array_map() doesn't work correctly on xml objects.
|
||||
/** @noinspection PhpLoopCanBeConvertedToArrayMapInspection */
|
||||
foreach ($_user->attributes() as $k => $v) {
|
||||
$user[$k] = (string)$v;
|
||||
}
|
||||
|
||||
$data[] = $user;
|
||||
}
|
||||
|
||||
if ($this->logRequests) {
|
||||
$this->rawRequests[] = [
|
||||
'url' => (string)$url,
|
||||
'headers' => $response->getHeaders(false),
|
||||
'body' => $data,
|
||||
'body' => json_decode(json_encode($content), true),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -267,15 +276,12 @@ final class GetUsersList
|
||||
}
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach (ag($data, 'User', []) as $data) {
|
||||
$user = ag($data, '@attributes', []);
|
||||
foreach ($data as $user) {
|
||||
$uuidStatus = preg_match('/\/users\/(?<uuid>.+?)\/avatar/', ag($user, 'thumb', ''), $matches);
|
||||
|
||||
$list[] = [
|
||||
'id' => ag($user, 'id'),
|
||||
'uuid' => 1 === $uuidStatus ? ag($matches, 'uuid') : ag($user, 'invited_user'),
|
||||
'name' => ag($user, ['username', 'title', 'email'], '??'),
|
||||
'name' => ag($user, ['username', 'title', 'email', 'id'], '??'),
|
||||
'admin' => false,
|
||||
'guest' => 1 !== (int)ag($user, 'home'),
|
||||
'restricted' => 1 === (int)ag($user, 'restricted'),
|
||||
@@ -502,7 +508,6 @@ final class GetUsersList
|
||||
'backend' => $context->backendName,
|
||||
'status_code' => $response->getStatusCode(),
|
||||
'body' => $response->getContent(false),
|
||||
'parsed' => $response->toArray(false),
|
||||
'extra_msg' => !$extra_msg ? '' : ". $extra_msg",
|
||||
'tokenType' => ag_exists(
|
||||
$context->options,
|
||||
|
||||
@@ -28,6 +28,7 @@ final class Options
|
||||
public const string PLEX_USER_UUID = 'plex_user_uuid';
|
||||
public const string PLEX_USER_NAME = 'plex_user_name';
|
||||
public const string PLEX_EXTERNAL_USER = 'plex_external_user';
|
||||
public const string PLEX_GUEST_USER = 'plex_guest_user';
|
||||
public const string NO_THROW = 'NO_THROW';
|
||||
public const string NO_LOGGING = 'NO_LOGGING';
|
||||
public const string MAX_EPISODE_RANGE = 'MAX_EPISODE_RANGE';
|
||||
|
||||
@@ -98,6 +98,10 @@ trait APITraits
|
||||
$backend['options'] = [];
|
||||
}
|
||||
|
||||
if (false === ag_exists($backend, 'webhook')) {
|
||||
$backend['webhook'] = ['match' => ['user' => false, 'uuid' => false]];
|
||||
}
|
||||
|
||||
$backends[] = $backend;
|
||||
}
|
||||
|
||||
@@ -154,6 +158,10 @@ trait APITraits
|
||||
$options[Options::IS_LIMITED_TOKEN] = (bool)$data->get('options.' . Options::IS_LIMITED_TOKEN, false);
|
||||
}
|
||||
|
||||
if (null !== $data->get('options.' . Options::PLEX_GUEST_USER)) {
|
||||
$options[Options::PLEX_GUEST_USER] = (bool)$data->get('options.' . Options::PLEX_GUEST_USER, false);
|
||||
}
|
||||
|
||||
$instance = Container::getNew($class);
|
||||
assert($instance instanceof ClientInterface, new InvalidArgumentException('Invalid client class.'));
|
||||
return $instance->withContext(
|
||||
|
||||
Reference in New Issue
Block a user