Merge pull request #657 from arabcoders/dev
Fixed Additional options in backend edit page.
This commit is contained in:
@@ -18,8 +18,9 @@ environment variable. This mode only applies to `import`, `export`, and `backup`
|
||||
|
||||
Additionally, two command-line flags let you override the mode on the fly `--sync-requests` and `--async-requests`.
|
||||
|
||||
We’ll be evaluating this feature for a trial period. If it proves effective (and the slowdown is acceptable), we may
|
||||
make **sequential** mode the default in a future release.
|
||||
We’ll be evaluating this feature, and if it proves effective (and the slowdown is acceptable), we may
|
||||
make **sequential** mode the default in a future release. So far from our testing, we’ve seen between 1.5x to 2.0x
|
||||
increase in import time when using the sequential mode.
|
||||
|
||||
> [!NOTE]
|
||||
> Because we cache many HTTP requests, comparing timings between sequential and parallel runs of `import` can be
|
||||
@@ -36,7 +37,7 @@ sub-users, and regenerate updated config files.
|
||||
|
||||
We have also added new guard for the command, so if you already generated your sub-users, re-running the command will
|
||||
show you a warning message and exit without doing anything. to run the command again either you need to use
|
||||
`--re-create`or `--run` flag. The `--run` flag will run the command without deleting the current sub-users.
|
||||
`--re-create` or `--run` flag. The `--run` flag will run the command without deleting the current sub-users.
|
||||
|
||||
---
|
||||
Refer to [NEWS](NEWS.md) for old updates.
|
||||
|
||||
@@ -17,13 +17,13 @@ use Monolog\Level;
|
||||
|
||||
return (function () {
|
||||
$inContainer = inContainer();
|
||||
$progressTimeCheck = fn(int $v, int $d): int => 0 === $v || $v >= 180 ? $v : $d;
|
||||
$progressTimeCheck = fn (int $v, int $d): int => 0 === $v || $v >= 180 ? $v : $d;
|
||||
|
||||
$config = [
|
||||
'name' => 'WatchState',
|
||||
'version' => '$(version_via_ci)',
|
||||
'tz' => env('WS_TZ', env('TZ', 'UTC')),
|
||||
'path' => fixPath(env('WS_DATA_PATH', fn() => $inContainer ? '/config' : __DIR__ . '/../var')),
|
||||
'path' => fixPath(env('WS_DATA_PATH', fn () => $inContainer ? '/config' : __DIR__ . '/../var')),
|
||||
'logs' => [
|
||||
'context' => (bool)env('WS_LOGS_CONTEXT', false),
|
||||
'prune' => [
|
||||
@@ -44,7 +44,7 @@ return (function () {
|
||||
'encode' => JSON_INVALID_UTF8_IGNORE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
'X-Application-Version' => fn() => getAppVersion(),
|
||||
'X-Application-Version' => fn () => getAppVersion(),
|
||||
'Access-Control-Allow-Origin' => '*',
|
||||
],
|
||||
],
|
||||
@@ -67,9 +67,9 @@ return (function () {
|
||||
'not_found' => (int)env('WS_EXPORT_NOT_FOUND', 259_200),
|
||||
],
|
||||
'episodes' => [
|
||||
'disable' => [
|
||||
'guid' => (bool)env('WS_EPISODES_DISABLE_GUID', true),
|
||||
]
|
||||
'enable' => [
|
||||
'guid' => (bool)env('WS_EPISODES_ENABLE_GUID', false),
|
||||
],
|
||||
],
|
||||
'ignore' => [],
|
||||
'trust' => [
|
||||
@@ -159,14 +159,14 @@ return (function () {
|
||||
|
||||
$config['profiler'] = [
|
||||
'save' => (bool)env('WS_PROFILER_SAVE', true),
|
||||
'path' => env('WS_PROFILER_PATH', fn() => ag($config, 'tmpDir') . '/profiler'),
|
||||
'path' => env('WS_PROFILER_PATH', fn () => ag($config, 'tmpDir') . '/profiler'),
|
||||
'collector' => env('WS_PROFILER_COLLECTOR', null),
|
||||
];
|
||||
|
||||
$config['cache'] = [
|
||||
'prefix' => env('WS_CACHE_PREFIX', null),
|
||||
'url' => env('WS_CACHE_URL', 'redis://127.0.0.1:6379'),
|
||||
'path' => env('WS_CACHE_PATH', fn() => ag($config, 'tmpDir') . '/cache'),
|
||||
'path' => env('WS_CACHE_PATH', fn () => ag($config, 'tmpDir') . '/cache'),
|
||||
];
|
||||
|
||||
$config['logger'] = [
|
||||
|
||||
@@ -95,10 +95,9 @@ return (function () {
|
||||
'type' => 'int',
|
||||
],
|
||||
[
|
||||
'key' => 'WS_EPISODES_DISABLE_GUID',
|
||||
'description' => 'DO NOT parse episodes GUID.',
|
||||
'key' => 'WS_EPISODES_ENABLE_GUID',
|
||||
'description' => 'Enable Episodes GUID parsing.',
|
||||
'type' => 'bool',
|
||||
'deprecated' => true,
|
||||
],
|
||||
[
|
||||
'key' => 'WS_BACKENDS_FILE',
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<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'"/>
|
||||
<NuxtLink to="/backends" v-text="'Backends'" />
|
||||
-
|
||||
<NuxtLink :to="'/backend/' + id" v-text="id"/>
|
||||
<NuxtLink :to="'/backend/' + id" v-text="id" />
|
||||
: Edit
|
||||
</span>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<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.
|
||||
@@ -37,8 +37,8 @@
|
||||
</div>
|
||||
|
||||
<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 message_class="is-background-info-90 has-text-dark" title="Loading" icon="fas fa-spinner fa-spin"
|
||||
message="Loading backend settings. Please wait..." />
|
||||
</div>
|
||||
|
||||
<div v-else class="column is-12">
|
||||
@@ -55,7 +55,7 @@
|
||||
<div class="control has-icons-left">
|
||||
<div class="select is-fullwidth">
|
||||
<select class="is-capitalized" disabled>
|
||||
<option v-text="api_user"/>
|
||||
<option v-text="api_user" />
|
||||
</select>
|
||||
</div>
|
||||
<div class="icon is-left">
|
||||
@@ -110,7 +110,7 @@
|
||||
<i class="fas fa-link"></i>
|
||||
</div>
|
||||
<p class="help">
|
||||
<template v-if="servers.length<1">
|
||||
<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>
|
||||
@@ -133,25 +133,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'">
|
||||
:type="false === exposeToken ? 'password' : 'text'">
|
||||
<div class="icon is-left">
|
||||
<i class="fas fa-key"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="button" class="button is-primary" @click="exposeToken = !exposeToken"
|
||||
v-tooltip="'Toggle token'">
|
||||
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>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">
|
||||
<template v-if="'plex'===backend.type">
|
||||
<template v-if="'plex' === backend.type">
|
||||
Enter the <code>X-Plex-Token</code>.
|
||||
<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>.
|
||||
@@ -181,20 +181,20 @@
|
||||
backend
|
||||
uniquely. This is used for webhook matching and filtering.
|
||||
</span>
|
||||
<NuxtLink @click="getUUid" v-if="!isLimitedToken" v-text="'Get from the backend.'"/>
|
||||
<NuxtLink @click="getUUid" v-if="!isLimitedToken" v-text="'Get from the backend.'" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">
|
||||
<template v-if="users.length>0">Associated User</template>
|
||||
<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">
|
||||
<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">
|
||||
<option v-for="user in users" :key="'uid-' + user.id" :value="user.id">
|
||||
{{ user.name }}
|
||||
</option>
|
||||
</select>
|
||||
@@ -236,7 +236,7 @@
|
||||
<label class="label" for="backend_import_metadata">Import metadata only 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">
|
||||
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
|
||||
@@ -261,7 +261,7 @@
|
||||
<label class="label" for="webhook_match_user">Webhook match user</label>
|
||||
<div class="control">
|
||||
<input id="webhook_match_user" type="checkbox" class="switch is-success"
|
||||
v-model="backend.webhook.match.user">
|
||||
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.
|
||||
@@ -273,7 +273,7 @@
|
||||
<label class="label" for="webhook_match_uuid">Webhook match backend id</label>
|
||||
<div class="control">
|
||||
<input id="webhook_match_uuid" type="checkbox" class="switch is-success"
|
||||
v-model="backend.webhook.match.uuid">
|
||||
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.
|
||||
@@ -293,24 +293,23 @@
|
||||
</label>
|
||||
<template v-if="showOptions">
|
||||
<div class="columns is-multiline is-mobile">
|
||||
<template v-for="(val, key) in backend?.options" :key="'bo-'+key">
|
||||
<template v-for="_option in flatOptionPaths" :key="'bo-'+_option">
|
||||
<div class="column is-5">
|
||||
<input type="text" class="input" :value="key" readonly disabled>
|
||||
<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[key]}"></i>
|
||||
<i class="fas fa-info-circle" :class="{ 'fa-bounce': newOptions[_option] }"></i>
|
||||
</span>
|
||||
{{ optionsList.find(v => v.key === key)?.description }}
|
||||
{{ option_describe(_option) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<input type="text" class="input" v-model="backend.options[key]" required>
|
||||
<input type="text" class="input" :value="option_get(_option)"
|
||||
@input="e => option_set(_option, e.target.value)" required>
|
||||
</div>
|
||||
<div class="column is-1">
|
||||
<button class="button is-danger" @click.prevent="removeOption(key)">
|
||||
<span class="icon">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
<button class="button is-danger" @click.prevent="removeOption(_option)">
|
||||
<span class="icon"><i class="fas fa-trash" /></span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -326,8 +325,8 @@
|
||||
<div class="select is-fullwidth">
|
||||
<select v-model="selectedOption">
|
||||
<option value="">Select Option</option>
|
||||
<option v-for="option in filteredOptions(optionsList)"
|
||||
:key="'opt-'+option.key" :value="option.key">
|
||||
<option v-for="option in filteredOptions(optionsList)" :key="'opt-' + option.key"
|
||||
:value="option.key">
|
||||
{{ option.key }}
|
||||
</option>
|
||||
</select>
|
||||
@@ -366,9 +365,9 @@
|
||||
|
||||
<script setup>
|
||||
import 'assets/css/bulma-switch.css'
|
||||
import {notification, ucFirst} from '~/utils/index'
|
||||
import { notification, ucFirst } 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
|
||||
@@ -381,9 +380,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: {}
|
||||
})
|
||||
|
||||
@@ -407,7 +406,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()
|
||||
@@ -431,13 +430,20 @@ const loadContent = async () => {
|
||||
}
|
||||
|
||||
const saveContent = async () => {
|
||||
const json_text = toRaw(backend.value)
|
||||
|
||||
const flat = {}
|
||||
flatOptionPaths.value.forEach(path => flat[path] = option_get(path))
|
||||
|
||||
if (Object.keys(flat).length > 0) {
|
||||
json_text.options = flat
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await request(`/backend/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(backend.value)
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(json_text)
|
||||
})
|
||||
|
||||
const json = await response.json()
|
||||
@@ -448,7 +454,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}`)
|
||||
}
|
||||
@@ -461,10 +467,11 @@ const removeOption = async (key) => {
|
||||
return
|
||||
}
|
||||
|
||||
if (!confirm(`Are you sure you want to remove this option [${key}]?`)) {
|
||||
if (!confirm(`Are you sure you want to remove this option '${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()
|
||||
@@ -475,6 +482,7 @@ const removeOption = async (key) => {
|
||||
notification('success', 'Information', `Option [${key}] removed successfully.`)
|
||||
delete backend.value.options[key]
|
||||
}
|
||||
|
||||
const addOption = async () => {
|
||||
if (!selectedOption.value) {
|
||||
notification('error', 'Error', 'Please select an option to add.')
|
||||
@@ -482,17 +490,11 @@ const addOption = async () => {
|
||||
}
|
||||
|
||||
backend.value.options = backend.value.options || {}
|
||||
if (backend.value.options.length < 1) {
|
||||
backend.value.options = {[selectedOption.value]: ''}
|
||||
} else {
|
||||
backend.value.options[selectedOption.value] = ''
|
||||
}
|
||||
|
||||
option_set(selectedOption.value, '')
|
||||
newOptions.value[selectedOption.value] = true
|
||||
selectedOption.value = ''
|
||||
}
|
||||
|
||||
|
||||
const getUUid = async () => {
|
||||
const required_values = ['type', 'token', 'url'];
|
||||
|
||||
@@ -570,7 +572,7 @@ const getUsers = async (showAlert = true) => {
|
||||
users.value = json
|
||||
}
|
||||
|
||||
watch(showOptions, async (value) => {
|
||||
watch(showOptions, async value => {
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
@@ -589,14 +591,13 @@ watch(showOptions, async (value) => {
|
||||
})
|
||||
});
|
||||
|
||||
const filteredOptions = (options) => {
|
||||
const filteredOptions = options => {
|
||||
if (!options) {
|
||||
return []
|
||||
}
|
||||
return options.filter(v => !backend.value.options[v.key] && !newOptions.value[v.key])
|
||||
}
|
||||
|
||||
|
||||
const getServers = async () => {
|
||||
if ('plex' !== backend.value.type || servers.value.length > 0) {
|
||||
return
|
||||
@@ -674,6 +675,56 @@ watch(() => backend.value.user, async () => {
|
||||
})
|
||||
})
|
||||
|
||||
const flattenOptions = (obj, prefix = '') => {
|
||||
const out = []
|
||||
|
||||
for (const [key, val] of Object.entries(obj)) {
|
||||
const path = prefix ? `${prefix}.${key}` : key
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
if (val.length === 0) {
|
||||
continue
|
||||
}
|
||||
out.push(path)
|
||||
continue
|
||||
}
|
||||
|
||||
if (val !== null && typeof val === 'object') {
|
||||
if (Object.keys(val).length === 0) {
|
||||
continue
|
||||
}
|
||||
out.push(...flattenOptions(val, path))
|
||||
continue
|
||||
}
|
||||
|
||||
out.push(path)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
const flatOptionPaths = computed(() => flattenOptions(backend.value.options))
|
||||
|
||||
const option_get = path => path.split('.').reduce((o, k) => (o == null ? undefined : o[k]), backend.value.options)
|
||||
const option_set = (path, value) => {
|
||||
const keys = path.split('.')
|
||||
const last = keys.pop()
|
||||
let target = backend.value.options
|
||||
for (const k of keys) {
|
||||
if (target[k] == null || typeof target[k] !== 'object' || Array.isArray(target[k])) {
|
||||
target[k] = {}
|
||||
}
|
||||
target = target[k]
|
||||
}
|
||||
|
||||
target[last] = value
|
||||
}
|
||||
|
||||
const option_describe = path => {
|
||||
const item = optionsList.value.find((v) => v.key === path)
|
||||
return item ? item.description : ''
|
||||
}
|
||||
|
||||
onMounted(async () => await loadContent())
|
||||
|
||||
</script>
|
||||
|
||||
@@ -10,19 +10,19 @@
|
||||
<div class="field is-grouped">
|
||||
<div class="control has-icons-left" v-if="toggleFilter || query">
|
||||
<input type="search" v-model.lazy="query" class="input" id="filter"
|
||||
placeholder="Filter displayed content">
|
||||
<span class="icon is-left"><i class="fas fa-filter"/></span>
|
||||
placeholder="Filter displayed content">
|
||||
<span class="icon is-left"><i class="fas fa-filter" /></span>
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<button class="button is-danger is-light" @click="toggleFilter = !toggleFilter">
|
||||
<span class="icon"><i class="fas fa-filter"/></span>
|
||||
<span class="icon"><i class="fas fa-filter" /></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="control">
|
||||
<button class="button is-primary" v-tooltip.bottom="'Add new variable'" @click="toggleForm = !toggleForm"
|
||||
:disabled="isLoading">
|
||||
:disabled="isLoading">
|
||||
<span class="icon">
|
||||
<i class="fas fa-add"></i>
|
||||
</span>
|
||||
@@ -30,7 +30,7 @@
|
||||
</p>
|
||||
<p class="control">
|
||||
<button class="button is-info" @click="loadContent" :disabled="isLoading || toggleForm"
|
||||
:class="{'is-loading':isLoading}">
|
||||
:class="{ 'is-loading': isLoading }">
|
||||
<span class="icon"><i class="fas fa-sync"></i></span>
|
||||
</button>
|
||||
</p>
|
||||
@@ -45,16 +45,15 @@
|
||||
|
||||
<div class="column is-12" v-if="!toggleForm && filteredRows.length < 1">
|
||||
<Message v-if="isLoading" message_class="has-background-info-90 has-text-dark" title="Loading"
|
||||
icon="fas fa-spinner fa-spin" message="Loading data. Please wait..."/>
|
||||
icon="fas fa-spinner fa-spin" message="Loading data. Please wait..." />
|
||||
<Message v-else message_class="has-background-warning-90 has-text-dark"
|
||||
:title="query ? 'No results' : 'Information'"
|
||||
icon="fas fa-info-circle">
|
||||
:title="query ? 'No results' : 'Information'" icon="fas fa-info-circle">
|
||||
<p v-if="query">
|
||||
No environment variables found matching <strong>{{ query }}</strong>. Please try a different filter.
|
||||
</p>
|
||||
<p v-else>
|
||||
No environment variables configured yet. Click on the
|
||||
<i @click="toggleForm=true" class="is-clickable fa fa-add"></i> button to add a new variable.
|
||||
<i @click="toggleForm = true" class="is-clickable fa fa-add"></i> button to add a new variable.
|
||||
</p>
|
||||
</Message>
|
||||
</div>
|
||||
@@ -87,16 +86,16 @@
|
||||
<label class="label is-unselectable" for="form_value">Environment value</label>
|
||||
<div class="control has-icons-left">
|
||||
<template v-if="'bool' === form_type">
|
||||
<input id="form_value" type="checkbox" class="switch is-success"
|
||||
:checked="fixBool(form_value)" @change="form_value = !fixBool(form_value)">
|
||||
<input id="form_value" type="checkbox" class="switch is-success" :checked="fixBool(form_value)"
|
||||
@change="form_value = !fixBool(form_value)">
|
||||
<label for="form_value">
|
||||
<template v-if="fixBool(form_value)">On (True)</template>
|
||||
<template v-else>Off (False)</template>
|
||||
</label>
|
||||
</template>
|
||||
<template v-else-if=" 'int' === form_type ">
|
||||
<template v-else-if="'int' === form_type">
|
||||
<input class="input" id="form_value" type="number" placeholder="Value" v-model="form_value"
|
||||
pattern="[0-9]*" inputmode="numeric">
|
||||
pattern="[0-9]*" inputmode="numeric">
|
||||
<div class="icon is-small is-left">
|
||||
<i class="fas fa-font"></i>
|
||||
</div>
|
||||
@@ -135,7 +134,7 @@
|
||||
<div v-else class="column is-12" v-if="filteredRows">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column" v-for="item in filteredRows" :key="item.key"
|
||||
:class="{'is-4':!item?.danger,'is-12':item.danger}">
|
||||
:class="{ 'is-4': !item?.danger, 'is-12': item.danger }">
|
||||
<div class="card" :class="{ 'is-danger': item?.danger }">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-unselectable">
|
||||
@@ -153,7 +152,7 @@
|
||||
</template>
|
||||
</p>
|
||||
<span class="card-header-icon" v-if="item.mask" @click="item.mask = false"
|
||||
v-tooltip="'Unmask the value'">
|
||||
v-tooltip="'Unmask the value'">
|
||||
<span class="icon"><i class="fas fa-unlock"></i></span>
|
||||
</span>
|
||||
</header>
|
||||
@@ -169,8 +168,8 @@
|
||||
</span>
|
||||
</p>
|
||||
<p v-else class="is-text-overflow is-clickable is-unselectable"
|
||||
:class="{ 'is-masked': item.mask, 'is-unselectable': item.mask }"
|
||||
@click="(e) => e.target.classList.toggle('is-text-overflow')">
|
||||
:class="{ 'is-masked': item.mask, 'is-unselectable': item.mask }"
|
||||
@click="(e) => e.target.classList.toggle('is-text-overflow')">
|
||||
{{ item.value }}</p>
|
||||
|
||||
<p v-if="item?.danger" class="title is-5 has-text-danger">
|
||||
@@ -211,7 +210,7 @@
|
||||
|
||||
<div class="column is-12">
|
||||
<Message message_class="has-background-info-90 has-text-dark" :toggle="show_page_tips"
|
||||
@toggle="show_page_tips = !show_page_tips" :use-toggle="true" title="Tips" icon="fas fa-info-circle">
|
||||
@toggle="show_page_tips = !show_page_tips" :use-toggle="true" title="Tips" icon="fas fa-info-circle">
|
||||
<ul>
|
||||
<li>Some variables values are masked, to unmask them click on icon <i class="fa fa-unlock"></i>.</li>
|
||||
<li>Some values are too large to fit into the view, clicking on the value will show the full value.</li>
|
||||
@@ -232,13 +231,13 @@
|
||||
<script setup>
|
||||
import 'assets/css/bulma-switch.css'
|
||||
import request from '~/utils/request'
|
||||
import {awaitElement, copyText, notification} from '~/utils/index'
|
||||
import {useStorage} from '@vueuse/core'
|
||||
import { awaitElement, copyText, notification } from '~/utils/index'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import Message from '~/components/Message'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
useHead({title: 'Environment Variables'})
|
||||
useHead({ title: 'Environment Variables' })
|
||||
|
||||
const items = ref([])
|
||||
const toggleForm = ref(false)
|
||||
@@ -287,20 +286,26 @@ const deleteEnv = async (env) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await request(`/system/env/${env.key}`, {method: 'DELETE'})
|
||||
const response = await request(`/system/env/${env.key}`, { method: 'DELETE' })
|
||||
|
||||
if (200 !== response.status) {
|
||||
let json
|
||||
try {
|
||||
json = await response.json()
|
||||
} catch (e) {
|
||||
json = {error: {code: response.status, message: response.statusText}}
|
||||
json = { error: { code: response.status, message: response.statusText } }
|
||||
}
|
||||
notification('error', 'Error', `${json.error.code}: ${json.error.message}`, 5000)
|
||||
return
|
||||
}
|
||||
|
||||
items.value = items.value.filter(i => i.key !== env.key)
|
||||
items.value = items.value.filter(i => {
|
||||
const state = i.key !== env.key
|
||||
if (true === state) {
|
||||
delete i.value
|
||||
}
|
||||
return state;
|
||||
})
|
||||
notification('success', 'Success', `Environment variable '${env.key}' successfully deleted.`, 5000)
|
||||
} catch (e) {
|
||||
notification('error', 'Error', `Request error. ${e.message}`, 5000)
|
||||
@@ -321,15 +326,10 @@ const addVariable = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
const data = items.value.filter(i => i.key === key)
|
||||
if (data.length > 0 && data[0].value === form_value.value) {
|
||||
return cancelForm()
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await request(`/system/env/${key}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({value: form_value.value})
|
||||
body: JSON.stringify({ value: form_value.value })
|
||||
})
|
||||
|
||||
if (304 === response.status) {
|
||||
@@ -358,10 +358,15 @@ const addVariable = async () => {
|
||||
const editEnv = env => {
|
||||
form_key.value = env.key
|
||||
form_value.value = env.value
|
||||
|
||||
if (typeof env.value === 'undefined' && 'bool' === env.type) {
|
||||
form_value.value = false
|
||||
}
|
||||
|
||||
form_type.value = env.type
|
||||
toggleForm.value = true
|
||||
if (!useRoute().query.edit) {
|
||||
useRouter().push({'path': '/env', query: {'edit': env.key}})
|
||||
useRouter().push({ 'path': '/env', query: { 'edit': env.key } })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,12 +377,12 @@ const cancelForm = async () => {
|
||||
form_type.value = null
|
||||
toggleForm.value = false
|
||||
if (route.query?.callback) {
|
||||
await navigateTo({path: route.query.callback})
|
||||
await navigateTo({ path: route.query.callback })
|
||||
return
|
||||
}
|
||||
|
||||
if (route.query?.edit || route.query?.value) {
|
||||
await useRouter().push({path: '/env'})
|
||||
await useRouter().push({ path: '/env' })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +390,7 @@ watch(toggleForm, async value => {
|
||||
if (!value) {
|
||||
await cancelForm()
|
||||
} else {
|
||||
awaitElement('#env_page_title', (_, el) => el.scrollIntoView({behavior: 'smooth'}))
|
||||
awaitElement('#env_page_title', (_, el) => el.scrollIntoView({ behavior: 'smooth' }))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -397,7 +402,12 @@ const keyChanged = () => {
|
||||
let data = items.value.filter(i => i.key === form_key.value)
|
||||
form_value.value = (data.length > 0) ? data[0].value : ''
|
||||
form_type.value = (data.length > 0) ? data[0].type : 'string'
|
||||
useRouter().push({'path': '/env', query: {'edit': form_key.value}})
|
||||
nextTick(() => {
|
||||
if (typeof form_value.value === 'undefined' && 'bool' === form_type.value) {
|
||||
form_value.value = false
|
||||
}
|
||||
});
|
||||
useRouter().push({ 'path': '/env', query: { 'edit': form_key.value } })
|
||||
}
|
||||
|
||||
const getHelp = key => {
|
||||
|
||||
@@ -119,10 +119,6 @@ final class Env
|
||||
return api_error(r("No value was provided for '{key}'.", ['key' => $key]), Status::BAD_REQUEST);
|
||||
}
|
||||
|
||||
if ($value === ag($spec, 'value')) {
|
||||
return api_response(Status::NOT_MODIFIED);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = $this->setType($spec, $value);
|
||||
|
||||
|
||||
@@ -207,9 +207,9 @@ final class ParseWebhook
|
||||
],
|
||||
];
|
||||
|
||||
$disableGuid = (bool)Config::get('episodes.disable.guid');
|
||||
$enableGUID = (bool)Config::get('episodes.enable.guid');
|
||||
|
||||
if (EmbyClient::TYPE_EPISODE === $type && true === $disableGuid) {
|
||||
if (EmbyClient::TYPE_EPISODE === $type && false === $enableGUID) {
|
||||
$guids = [];
|
||||
} else {
|
||||
$guids = $guid->get(guids: ag($json, 'Item.ProviderIds', []), context: $logContext);
|
||||
@@ -263,7 +263,7 @@ final class ParseWebhook
|
||||
context: $context,
|
||||
guid: $guid,
|
||||
item: $obj,
|
||||
opts: ['override' => $fields, Options::DISABLE_GUID => $disableGuid],
|
||||
opts: ['override' => $fields, Options::ENABLE_EPISODE_GUID => $enableGUID],
|
||||
)->setIsTainted(isTainted: true === in_array($event, self::WEBHOOK_TAINTED_EVENTS));
|
||||
|
||||
if (false === $entity->hasGuids() && false === $entity->hasRelativeGuid()) {
|
||||
|
||||
@@ -238,7 +238,7 @@ class EmbyClient implements iClient
|
||||
guid: $this->guid,
|
||||
mapper: $mapper,
|
||||
after: $after,
|
||||
opts: [Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid')]
|
||||
opts: [Options::ENABLE_EPISODE_GUID => (bool)Config::get('episodes.enable.guid')]
|
||||
);
|
||||
|
||||
if ($response->hasError()) {
|
||||
@@ -263,7 +263,7 @@ class EmbyClient implements iClient
|
||||
mapper: $mapper,
|
||||
opts: ag_sets($opts, [
|
||||
'writer' => $writer,
|
||||
Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid'),
|
||||
Options::ENABLE_EPISODE_GUID => (bool)Config::get('episodes.enable.guid'),
|
||||
])
|
||||
);
|
||||
|
||||
@@ -290,7 +290,7 @@ class EmbyClient implements iClient
|
||||
after: $after,
|
||||
opts: [
|
||||
'queue' => $queue,
|
||||
Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid'),
|
||||
Options::ENABLE_EPISODE_GUID => (bool)Config::get('episodes.enable.guid'),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -448,7 +448,7 @@ class EmbyClient implements iClient
|
||||
mapper: $mapper,
|
||||
after: null,
|
||||
opts: ag_sets($opts, [
|
||||
Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid'),
|
||||
Options::ENABLE_EPISODE_GUID => (bool)Config::get('episodes.enable.guid'),
|
||||
Options::ONLY_LIBRARY_ID => $libraryId,
|
||||
])
|
||||
);
|
||||
|
||||
@@ -166,7 +166,7 @@ final class ParseWebhook
|
||||
],
|
||||
];
|
||||
|
||||
$disableGuid = (bool)Config::get('episodes.disable.guid');
|
||||
$enableGUID = (bool)Config::get('episodes.enable.guid');
|
||||
|
||||
$providersId = [];
|
||||
|
||||
@@ -177,7 +177,7 @@ final class ParseWebhook
|
||||
$providersId[after($key, 'provider_')] = $val;
|
||||
}
|
||||
|
||||
if (JFC::TYPE_EPISODE === $type && true === $disableGuid) {
|
||||
if (JFC::TYPE_EPISODE === $type && false === $enableGUID) {
|
||||
$guids = [];
|
||||
} else {
|
||||
$guids = $guid->get(guids: $providersId, context: $logContext);
|
||||
@@ -227,7 +227,7 @@ final class ParseWebhook
|
||||
context: $context,
|
||||
guid: $guid,
|
||||
item: $obj,
|
||||
opts: ['override' => $fields, Options::DISABLE_GUID => $disableGuid],
|
||||
opts: ['override' => $fields, Options::ENABLE_EPISODE_GUID => $enableGUID],
|
||||
)->setIsTainted(isTainted: true === in_array($event, self::WEBHOOK_TAINTED_EVENTS));
|
||||
|
||||
if (false === $entity->hasGuids() && false === $entity->hasRelativeGuid()) {
|
||||
|
||||
@@ -93,7 +93,7 @@ trait JellyfinActionTrait
|
||||
],
|
||||
];
|
||||
|
||||
if (iState::TYPE_EPISODE === $type && true === (bool)ag($opts, Options::DISABLE_GUID, false)) {
|
||||
if (iState::TYPE_EPISODE === $type && false === (bool)ag($opts, Options::ENABLE_EPISODE_GUID, false)) {
|
||||
$guids = [];
|
||||
} else {
|
||||
$guids = $guid->get(guids: ag($item, 'ProviderIds', []), context: $logContext);
|
||||
|
||||
@@ -265,7 +265,7 @@ class JellyfinClient implements iClient
|
||||
guid: $this->guid,
|
||||
mapper: $mapper,
|
||||
after: $after,
|
||||
opts: [Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid')]
|
||||
opts: [Options::ENABLE_EPISODE_GUID => (bool)Config::get('episodes.enable.guid')]
|
||||
);
|
||||
|
||||
if ($response->hasError()) {
|
||||
@@ -290,7 +290,7 @@ class JellyfinClient implements iClient
|
||||
mapper: $mapper,
|
||||
opts: ag_sets($opts, [
|
||||
'writer' => $writer,
|
||||
Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid')
|
||||
Options::ENABLE_EPISODE_GUID => (bool)Config::get('episodes.enable.guid')
|
||||
])
|
||||
);
|
||||
|
||||
@@ -317,7 +317,7 @@ class JellyfinClient implements iClient
|
||||
after: $after,
|
||||
opts: [
|
||||
'queue' => $queue,
|
||||
Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid'),
|
||||
Options::ENABLE_EPISODE_GUID => (bool)Config::get('episodes.enable.guid'),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -499,8 +499,8 @@ class JellyfinClient implements iClient
|
||||
mapper: $mapper,
|
||||
after: null,
|
||||
opts: ag_sets($opts, [
|
||||
Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid'),
|
||||
Options::ONLY_LIBRARY_ID => $libraryId,
|
||||
Options::ENABLE_EPISODE_GUID => (bool)Config::get('episodes.enable.guid'),
|
||||
])
|
||||
);
|
||||
|
||||
|
||||
@@ -182,9 +182,9 @@ final class ParseWebhook
|
||||
],
|
||||
];
|
||||
|
||||
$disableGuid = (bool)Config::get('episodes.disable.guid');
|
||||
$enableGUID = (bool)Config::get('episodes.enable.guid');
|
||||
|
||||
if (PlexClient::TYPE_EPISODE === $type && true === $disableGuid) {
|
||||
if (PlexClient::TYPE_EPISODE === $type && false === $enableGUID) {
|
||||
$guids = [];
|
||||
} else {
|
||||
$guids = $guid->get(guids: ag($item, 'Guid', []), context: $logContext);
|
||||
@@ -234,7 +234,7 @@ final class ParseWebhook
|
||||
context: $context,
|
||||
guid: $guid,
|
||||
item: $obj,
|
||||
opts: ['override' => $fields, Options::DISABLE_GUID => $disableGuid],
|
||||
opts: ['override' => $fields, Options::ENABLE_EPISODE_GUID => $enableGUID],
|
||||
)->setIsTainted(isTainted: true === in_array($event, self::WEBHOOK_TAINTED_EVENTS));
|
||||
|
||||
if (false === $entity->hasGuids() && false === $entity->hasRelativeGuid()) {
|
||||
|
||||
@@ -104,7 +104,7 @@ trait PlexActionTrait
|
||||
],
|
||||
];
|
||||
|
||||
if (iState::TYPE_EPISODE === $type && true === (bool)ag($opts, Options::DISABLE_GUID, false)) {
|
||||
if (iState::TYPE_EPISODE === $type && false === (bool)ag($opts, Options::ENABLE_EPISODE_GUID, false)) {
|
||||
$guids = [];
|
||||
} else {
|
||||
$guids = $guid->get(guids: ag($item, 'Guid', []), context: $logContext);
|
||||
|
||||
@@ -40,6 +40,7 @@ use App\Libs\Enums\Http\Method;
|
||||
use App\Libs\Enums\Http\Status;
|
||||
use App\Libs\Exceptions\Backends\RuntimeException;
|
||||
use App\Libs\Exceptions\HttpException;
|
||||
use App\Libs\Extends\HttpClient;
|
||||
use App\Libs\Mappers\Import\ReadOnlyMapper;
|
||||
use App\Libs\Mappers\ImportInterface as iImport;
|
||||
use App\Libs\Options;
|
||||
@@ -257,7 +258,7 @@ class PlexClient implements iClient
|
||||
mapper: $mapper,
|
||||
after: $after,
|
||||
opts: [
|
||||
Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid'),
|
||||
Options::ENABLE_EPISODE_GUID => (bool)Config::get('episodes.enable.guid'),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -283,7 +284,7 @@ class PlexClient implements iClient
|
||||
mapper: $mapper,
|
||||
opts: ag_sets($opts, [
|
||||
'writer' => $writer,
|
||||
Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid')
|
||||
Options::ENABLE_EPISODE_GUID => (bool)Config::get('episodes.enable.guid')
|
||||
])
|
||||
);
|
||||
|
||||
@@ -308,7 +309,7 @@ class PlexClient implements iClient
|
||||
guid: $this->guid,
|
||||
mapper: $mapper,
|
||||
after: $after,
|
||||
opts: ['queue' => $queue, Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid')],
|
||||
opts: ['queue' => $queue, Options::ENABLE_EPISODE_GUID => (bool)Config::get('episodes.enable.guid')],
|
||||
);
|
||||
|
||||
if ($response->hasError()) {
|
||||
@@ -467,8 +468,8 @@ class PlexClient implements iClient
|
||||
mapper: $mapper,
|
||||
after: null,
|
||||
opts: ag_sets($opts, [
|
||||
Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid'),
|
||||
Options::ONLY_LIBRARY_ID => $libraryId,
|
||||
Options::ENABLE_EPISODE_GUID => (bool)Config::get('episodes.enable.guid'),
|
||||
]),
|
||||
);
|
||||
|
||||
@@ -748,7 +749,7 @@ class PlexClient implements iClient
|
||||
/**
|
||||
* Retrieves a list of Plex servers using the Plex.tv API.
|
||||
*
|
||||
* @param iHttp $http The HTTP client used to send the request.
|
||||
* @param iHttp&HttpClient $http The HTTP client used to send the request.
|
||||
* @param string $token The Plex authentication token.
|
||||
* @param array $opts (Optional) options.
|
||||
*
|
||||
@@ -788,7 +789,8 @@ class PlexClient implements iClient
|
||||
'payload' => $payload
|
||||
]),
|
||||
]
|
||||
), $response->getStatusCode()
|
||||
),
|
||||
$response->getStatusCode()
|
||||
);
|
||||
}
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
@@ -801,7 +803,9 @@ class PlexClient implements iClient
|
||||
'line' => $e->getLine(),
|
||||
'file' => after($e->getFile(), ROOT_PATH),
|
||||
]
|
||||
), code: 500, previous: $e
|
||||
),
|
||||
code: 500,
|
||||
previous: $e
|
||||
);
|
||||
}
|
||||
|
||||
@@ -870,7 +874,7 @@ class PlexClient implements iClient
|
||||
/**
|
||||
* Check if given plex token is valid.
|
||||
*
|
||||
* @param iHttp $http The HTTP client used to send the request.
|
||||
* @param iHttp&HttpClient $http The HTTP client used to send the request.
|
||||
* @param string $token The Plex authentication token.
|
||||
* @param array $opts (Optional) options.
|
||||
*
|
||||
@@ -934,7 +938,9 @@ class PlexClient implements iClient
|
||||
'line' => $e->getLine(),
|
||||
'file' => after($e->getFile(), ROOT_PATH),
|
||||
]
|
||||
), code: 500, previous: $e
|
||||
),
|
||||
code: 500,
|
||||
previous: $e
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -958,7 +964,7 @@ class PlexClient implements iClient
|
||||
private function throwError(Response $response, string $className = RuntimeException::class, int $code = 0): void
|
||||
{
|
||||
throw new $className(
|
||||
message: ag($response->extra, 'message', fn() => $response->error->format()),
|
||||
message: ag($response->extra, 'message', fn () => $response->error->format()),
|
||||
code: $code,
|
||||
previous: $response->error->previous
|
||||
);
|
||||
|
||||
@@ -21,7 +21,6 @@ final class Options
|
||||
public const string MAPPER_DISABLE_AUTOCOMMIT = 'DISABLE_AUTOCOMMIT';
|
||||
public const string IMPORT_METADATA_ONLY = 'IMPORT_METADATA_ONLY';
|
||||
public const string MISMATCH_DEEP_SCAN = 'MISMATCH_DEEP_SCAN';
|
||||
public const string DISABLE_GUID = 'DISABLE_GUID';
|
||||
public const string LIBRARY_SEGMENT = 'LIBRARY_SEGMENT';
|
||||
public const string STATE_UPDATE_EVENT = 'STATE_UPDATE_EVENT';
|
||||
public const string DUMP_PAYLOAD = 'DUMP_PAYLOAD';
|
||||
@@ -51,6 +50,7 @@ final class Options
|
||||
public const string DELAY_BY = 'DELAY_BY';
|
||||
public const string RAW_RESPONSE_CALLBACK = 'RAW_RESPONSE_CALLBACK';
|
||||
public const string INTERNAL_REQUEST = 'INTERNAL_REQUEST';
|
||||
public const string ENABLE_EPISODE_GUID = 'ENABLE_EPISODE_GUID';
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user