Standardized how we use the Message component.

This commit is contained in:
Abdulmhsen B. A. A.
2024-06-11 21:08:10 +03:00
parent 84307db783
commit c72346ea08
26 changed files with 764 additions and 899 deletions

View File

@@ -1,23 +1,20 @@
<template>
<Message title="Important Information" message_class="has-background-warning-80 has-text-dark">
<div class="content is-bold">
<Message title="Important" message_class="has-background-warning-80 has-text-dark" icon="fas fa-exclamation-triangle">
<ul>
<li>
WatchState is single user tool. It doesn't support syncing multiple users play state.
<NuxtLink target="_blank" v-text="'Visit this link'"
href="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#is-there-support-for-multi-user-setup"/>
to="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#is-there-support-for-multi-user-setup"/>
to learn more.
</li>
<li>
If you are adding new backend that is fresh and doesn't have your current watch state, you should turn off
import and enable only metadata import at the start to prevent overriding your current play state.
<NuxtLink
href="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#my-new-backend-overriding-my-old-backend-state--my-watch-state-is-not-correct"
target="_blank" v-text="'Visit this link'"/>
<NuxtLink target="_blank" v-text="'Visit this link'"
to="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#my-new-backend-overriding-my-old-backend-state--my-watch-state-is-not-correct"/>
to learn more.
</li>
</ul>
</div>
</Message>
<form id="backend_add_form" @submit.prevent="addBackend" @change="changeStage">
<div class="card">

View File

@@ -3,7 +3,7 @@
</template>
<script setup>
import {useStorage} from "@vueuse/core"
import {useStorage} from '@vueuse/core'
import {marked} from 'marked'
import {baseUrl} from 'marked-base-url'
@@ -23,8 +23,21 @@ onMounted(() => fetch(`${api_url.value}${props.file}`).then(response => response
renderer: {
text: (text) => {
// -- replace github [!] with icon
text = text.replace(/\[!IMPORTANT\]/g, '<i class="fas fa-exclamation-triangle has-text-danger"></i>')
text = text.replace(/\[!NOTE\]/g, '<i class="fas fa-exclamation-circle has-text-info"></i>')
text = text.replace(/\[!IMPORTANT\]/g, `
<span class="is-block title is-4">
<span class="icon-text">
<span class="icon"><i class="fas fa-exclamation-triangle has-text-danger"></i></span>
<span>Important</span>
</span>
</span>`)
text = text.replace(/\[!NOTE\]/g, `
<span class="is-block title is-4">
<span class="icon-text">
<span class="icon"><i class="fas fa-info-circle has-text-info-50"></i></span>
<span>Note</span>
</span>
</span>`)
return text
}
},

View File

@@ -1,9 +1,27 @@
<template>
<div class="notification" :class="message_class">
<h2 class="title is-5" v-if="title">{{ title }}</h2>
<p v-if="message">{{ message }}</p>
<button class="delete" @click="$emit('close')" v-if="!useToggle && useClose"></button>
<div class="is-pulled-right is-unselectable" v-if="useToggle">
<span class="icon">
<i class="fas" :class="{'fa-arrow-up':toggle,'fa-arrow-down':!toggle}"></i>
</span>
<span>{{ toggle ? 'Close' : 'Open' }}</span>
</div>
<div class="notification-title is-unselectable" :class="{'is-clickable':useToggle}" v-if="title || icon"
@click="useToggle ? $emit('toggle', toggle):null">
<template v-if="icon">
<span class="icon-text">
<span class="icon"><i :class="icon"></i></span>
<span>{{ title }}</span>
</span>
</template>
<template v-else>{{ title }}</template>
</div>
<div class="notification-content content" v-if="false === useToggle || toggle">
<template v-if="message">{{ message }}</template>
<slot/>
</div>
</div>
</template>
<script setup>
@@ -13,6 +31,11 @@ defineProps({
default: null,
required: false
},
icon: {
type: String,
default: null,
required: false
},
message: {
type: String,
default: null,
@@ -22,6 +45,23 @@ defineProps({
type: String,
default: 'is-info',
required: false
},
useToggle: {
type: Boolean,
default: false,
required: false
},
toggle: {
type: Boolean,
default: false,
required: false
},
useClose: {
type: Boolean,
default: false,
required: false
}
})
defineEmits(['toggle', 'close'])
</script>

View File

@@ -1,17 +1,8 @@
<template>
<div class="notification has-text-dark is-background-warning-80">
<h5 class="title is-5 is-unselectable">
<span class="icon-text">
<span class="icon"><i class="fas fa-exclamation-triangle"></i></span>
<span>Warning</span>
</span>
</h5>
<div class="content">
<p>There is no <em class="is-bold">{{ api_var }}</em> configured.</p>
<p>Please configure the API connection using the button <i class="fa fa-cog"></i> in the top right corner of the
page.</p>
</div>
</div>
<Message title="Warning" message_class="has-background-warning-80 has-text-dark" icon="fas fa-exclamation-triangle">
There is no <em class="is-bold">{{ api_var }}</em> configured. Please configure the API connection using the button
<i class="fa fa-cog"></i> in the top right corner of the page.
</Message>
</template>
<script setup>

View File

@@ -1,6 +1,6 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">
<NuxtLink to="/backends">Backends</NuxtLink>
- Edit:
@@ -10,13 +10,15 @@
<div class="is-pulled-right">
<div class="field is-grouped"></div>
</div>
<div class="is-hidden-mobile">
<span class="subtitle">Edit the backend settings.</span>
</div>
</div>
<div class="column is-12" v-if="isLoading">
<Message message_class="is-info" title="Information">
<span class="icon"><i class="fas fa-spinner fa-pulse"></i></span>
<span>Loading backend settings, please wait...</span>
</Message>
<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">
@@ -300,12 +302,14 @@
</div>
</div>
<div class="card-footer">
<div class="card-footer-item">
<button class="button is-fullwidth is-primary" type="submit">
<button class="button card-footer-item is-fullwidth is-primary" type="submit">
<span class="icon"><i class="fas fa-save"></i></span>
<span>Save Settings</span>
</button>
</div>
<NuxtLink class="card-footer-item button is-fullwidth is-danger" :to="`/backend/${backend}`">
<span class="icon"><i class="fas fa-cancel"></i></span>
<span>Cancel changes</span>
</NuxtLink>
</div>
</div>
</form>

View File

@@ -1,7 +1,6 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">
<NuxtLink to="/backends" v-text="'Backends'"/>
: {{ backend }}
@@ -9,7 +8,7 @@
<div class="is-pulled-right">
<div class="field is-grouped">
<p class="control">
<NuxtLink class="button is-primary" v-tooltip="'Edit Backend'" :to="`/backend/${backend}/edit`">
<NuxtLink class="button is-primary" v-tooltip.bottom="'Edit Backend'" :to="`/backend/${backend}/edit`">
<span class="icon"><i class="fas fa-edit"></i></span>
</NuxtLink>
</p>
@@ -27,7 +26,7 @@
<h1 class="title is-4">Useful Tools</h1>
<ul>
<li>
<NuxtLink :to="`/backend/${backend}/mismatched`" v-text="'Find possible mismatched content.'"/>
<NuxtLink :to="`/backend/${backend}/mismatched`" v-text="'Find possible mis-identified content.'"/>
</li>
<li>
<NuxtLink :to="`/backend/${backend}/unmatched`" v-text="'Find unmatched content.'"/>
@@ -49,7 +48,13 @@
</div>
</div>
<div class="columns is-multiline" v-if="bHistory.length>0">
<div class="columns" v-if="bHistory.length<1">
<div class="column is-12">
<Message message_class="is-background-warning-80 has-text-dark" title="Warning" icon="fas fa-exclamation-circle"
message="No items were found. There are probably no items in the local database yet."/>
</div>
</div>
<div class="columns is-multiline" v-else>
<div class="column is-12">
<h1 class="title is-4">Recent History</h1>
</div>
@@ -111,17 +116,17 @@
</div>
</div>
<div class="columns" v-if="bHistory.length<1">
<div class="column is-12">
<Message message_class="is-warning">
<span class="icon-text">
<span class="icon"><i class="fas fa-exclamation"></i></span>
<span>No items were found. There are probably no items in the local database yet.</span>
</span>
</Message>
</div>
</div>
<div class="columns is-multiline" v-if="info">
<div class="column is-12">
<h1 class="title is-4">Basic info</h1>
</div>
<div class="column is-12">
<div class="content">
<code class="is-block is-pre-wrap" v-text="info"></code>
</div>
</div>
</div>
</template>
<script setup>
@@ -130,11 +135,11 @@ import Message from '~/components/Message.vue'
import {formatDuration, notification} from "~/utils/index.js";
const backend = useRoute().params.backend
const historyUrl = `/history/?via=${backend}`
useHead({title: `Backends: ${backend}`})
const bHistory = ref([])
const info = ref({})
const loadRecentHistory = async () => {
const response = await request(`/history/?perpage=6&via=${backend}`)
@@ -148,5 +153,14 @@ const loadRecentHistory = async () => {
bHistory.value = json.history
};
onMounted(() => loadRecentHistory())
const loadInfo = async () => {
const response = await request(`/backend/${backend}/info`)
info.value = await response.json()
}
onMounted(async () => {
await loadInfo();
await loadRecentHistory()
})
</script>

View File

@@ -1,43 +0,0 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<span class="title is-4">
<NuxtLink to="/backends">Backends</NuxtLink>
-
<NuxtLink :to="'/backend/' + backend">{{ backend }}</NuxtLink>
: Info
</span>
<div class="is-pulled-right">
<div class="field is-grouped"></div>
</div>
</div>
<div class="column is-12" v-if="info">
<code class="box logs-container">
{{ JSON.stringify(info, null, 2) }}
</code>
</div>
</div>
</template>
<style scoped>
.logs-container {
min-height: 40vh;
overflow-y: auto;
white-space: pre;
}
</style>
<script setup>
const backend = useRoute().params.backend
const info = ref()
const loadContent = async () => {
const response = await request(`/backend/${backend}/info`)
info.value = await response.json()
}
onMounted(() => loadContent())
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">
<NuxtLink to="/backends">Backends</NuxtLink>
-
@@ -11,8 +11,7 @@
<div class="is-pulled-right">
<div class="field is-grouped">
<p class="control">
<button class="button is-info" @click.prevent="loadContent" :disabled="isLoading"
:class="{'is-loading':isLoading}">
<button class="button is-info" @click="loadContent" :disabled="isLoading" :class="{'is-loading':isLoading}">
<span class="icon"><i class="fas fa-sync"></i></span>
</button>
</p>
@@ -25,16 +24,11 @@
</div>
<div class="column is-12" v-if="items.length < 1">
<Message message_class="has-background-info-90 has-text-dark" title="No Libraries">
<span class="icon-text" v-if="isLoading">
<span class="icon"><i class="fas fa-spinner fa-spin"></i></span>
<span>Loading libraries list. Please wait...</span>
</span>
<span class="icon-text" v-else>
<span class="icon"><i class="fas fa-info-circle"></i></span>
<span>No libraries found in the backend.</span>
</span>
</Message>
<Message message_class="has-background-info-90 has-text-dark" title="Loading" icon="fas fa-spinner fa-spin"
message="Loading libraries list. Please wait..." v-if="isLoading"/>
<Message v-else message_class="has-background-warning-80 has-text-dark" title="Warning"
icon="fas fa-exclamation-circle"
message="WatchState was unable to get any libraries from the backend."/>
</div>
<div class="column is-6" v-for="item in items" :key="`library-${item.id}`">
@@ -82,31 +76,14 @@
</div>
<div class="column is-12">
<Message message_class="has-background-info-90 has-text-dark">
<div class="is-pulled-right">
<NuxtLink @click="show_page_tips=false" v-if="show_page_tips">
<span class="icon"><i class="fas fa-arrow-up"></i></span>
<span>Close</span>
</NuxtLink>
<NuxtLink @click="show_page_tips=true" v-else>
<span class="icon"><i class="fas fa-arrow-down"></i></span>
<span>Open</span>
</NuxtLink>
</div>
<h5 class="title is-5 is-unselectable">
<span class="icon-text">
<span class="icon"><i class="fas fa-info-circle"></i></span>
<span>Tips</span>
</span>
</h5>
<div class="content" v-if="show_page_tips">
<Message message_class="has-background-info-90 has-text-dark" title="Tips" icon="fas fa-info-circle"
:toggle="show_page_tips" @toggle="show_page_tips = !show_page_tips" :use-toggle="true">
<ul>
<li>Ignoring library will prevent any content from being added to the local database from that library
during import process, and via webhook events.
<li>Ignoring library will prevent any content from being added to the local database from the library
during import process, and webhook events handling.
</li>
<li>Libraries that shows <code>Supported: No</code> will not be processed by the system.</li>
<li>Libraries that shows <code>Supported: No</code> will not be processed by <code>WatchState</code>.</li>
</ul>
</div>
</Message>
</div>
</div>

View File

@@ -1,31 +1,27 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">
<NuxtLink to="/backends">Backends</NuxtLink>
-
<NuxtLink :to="'/backend/' + backend">{{ backend }}</NuxtLink>
: Mismatched
: Misidentified
</span>
<div class="is-pulled-right" v-if="hasLooked">
<div class="field is-grouped">
<p class="control">
<button class="button is-info" @click.prevent="loadContent" :disabled="isLoading"
:class="{'is-loading':isLoading}">
<button class="button is-info" @click="loadContent" :disabled="isLoading" :class="{'is-loading':isLoading}">
<span class="icon"><i class="fas fa-sync"></i></span>
</button>
</p>
</div>
</div>
<div class="subtitle is-hidden-mobile">
This page will show items that <code>WatchState</code> thinks are possible mismatches.
This page will show items that <code>WatchState</code> thinks are possibly mis-identified in your backend.
</div>
</div>
<template v-if="false === hasLooked">
<div class="column is-12">
<div class="column is-12" v-if="false === hasLooked">
<div class="card">
<header class="card-header">
<p class="card-header-title is-justify-center">Request Analyze</p>
@@ -48,34 +44,19 @@
</div>
</div>
</div>
</template>
<template v-else>
<div class="column is-12" v-if="isLoading && items.length < 1">
<Message message_class="is-info" title="Analyzing">
<span class="icon-text">
<span class="icon"><i class="fas fa-spinner fa-spin"></i></span>
<span>Analyzing the backend content. Please wait. It will take a while...</span>
</span>
</Message>
</div>
<div class="column is-12" v-else-if="hasLooked && items.length < 1">
<Message message_class="has-background-success-90 has-text-dark" title="Success!">
<span class="icon-text">
<span class="icon"><i class="fas fa-check"></i></span>
<span>WatchState did not find possible mismatched items in the libraries we looked at.</span>
</span>
</Message>
<div class="column is-12" v-if="items.length < 1">
<Message v-if="isLoading" message_class="is-background-info-90 has-text-dark"
title="Analyzing" icon="fas fa-spinner fa-spin"
message="Analyzing the backend content. Please wait. It will take a while..."/>
<Message v-else-if="!isLoading && hasLooked" message_class="has-background-success-90 has-text-dark"
title="Success!" icon="fas fa-check"
message="WatchState did not find any possible mismatched items in the libraries we looked at."/>
</div>
<template v-else>
<template v-if="items.length > 1">
<div class="column is-12">
<h1 class="title is-4">
<span class="icon-text">
<span class="icon has-text-warning"><i class="fas fa-exclamation-triangle"></i></span>
<span>Possible Mismatches</span>
</span>
</h1>
<Message class="has-background-warning-80 has-text-dark" title="Warning" icon="fas fa-exclamation-triangle"
message="WatchState found some items that might be mis-identified in your backend. Please review the results."/>
</div>
<div class="column is-6" v-for="item in items">
@@ -122,41 +103,30 @@
</div>
</div>
</template>
</template>
<div class="column is-12">
<Message message_class="has-background-info-90 has-text-dark">
<div class="is-pulled-right">
<NuxtLink @click="show_page_tips=false" v-if="show_page_tips">
<span class="icon"><i class="fas fa-arrow-up"></i></span>
<span>Close</span>
</NuxtLink>
<NuxtLink @click="show_page_tips=true" v-else>
<span class="icon"><i class="fas fa-arrow-down"></i></span>
<span>Open</span>
</NuxtLink>
</div>
<h5 class="title is-5 is-unselectable">
<span class="icon-text">
<span class="icon"><i class="fas fa-info-circle"></i></span>
<span>Tips</span>
</span>
</h5>
<div class="content" v-if="show_page_tips">
<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">
<ul>
<li>
This service expects standard plex naming conventions. So if you libraries doesn't follow the same
conventions, you will see a lot of items being reported as mismatches.
This service expects standard plex naming conventions
<NuxtLink target="_blank" to="https://support.plex.tv/articles/naming-and-organizing-your-tv-show-files/"
v-text="'for series'"/>
, and
<NuxtLink target="_blank"
to="https://support.plex.tv/articles/naming-and-organizing-your-movie-media-files/"
v-text="'for movies'"/>
. So if you libraries doesn't follow the same conventions, you will see a lot of items being reported as
misidentified.
</li>
<li>
If you see a lot of mismatches, you might want to check the that the source directory matches the item.
If you see a lot of misidentified items, you might want to check the that the source directory matches the
item.
</li>
<li>
Clicking on the icon next to the title will show you the raw data that was used to generate the report.
</li>
</ul>
</div>
</Message>
</div>
</div>
@@ -165,6 +135,7 @@
<script setup>
import {makeSearchLink, notification} from "~/utils/index.js";
import {useStorage} from "@vueuse/core";
import Message from "~/components/Message.vue";
const backend = useRoute().params.backend
const items = ref([])
@@ -172,6 +143,7 @@ const isLoading = ref(false)
const hasLooked = ref(false)
const show_page_tips = useStorage('show_page_tips', true)
useHead({title: `Backends: ${backend} - Misidentified items`})
const loadContent = async () => {
hasLooked.value = true
isLoading.value = true
@@ -181,30 +153,20 @@ const loadContent = async () => {
try {
response = await request(`/backend/${backend}/mismatched`)
} catch (e) {
isLoading.value = false
return notification('error', 'Error', e.message)
}
try {
json = await response.json()
} catch (e) {
json = {
error: {
code: response.status,
message: response.statusText
}
}
}
isLoading.value = false
if (!response.ok) {
notification('error', 'Error', `${json.error.code}: ${json.error.message}`)
notification('error', 'Error', `${json.error.code ?? response.status}: ${json.error.message ?? response.statusText}`)
return
}
items.value = json
} catch (e) {
hasLooked.value = false
return notification('error', 'Error', e.message)
} finally {
isLoading.value = false
}
}
const percentColor = (percent) => {

View File

@@ -1,7 +1,7 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<span class="title is-4 is-unselectable">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">
<NuxtLink to="/backends">Backends</NuxtLink>
-
<NuxtLink :to="'/backend/' + backend">{{ backend }}</NuxtLink>
@@ -10,15 +10,14 @@
<div class="is-pulled-right">
<div class="field is-grouped">
<p class="control">
<button class="button is-info" @click.prevent="searchContent">
<span class="icon">
<i class="fas fa-sync"></i>
</span>
<button class="button is-info" @click="searchContent" :disabled="isLoading || !searchField || !query"
:class="{'is-loading':isLoading}">
<span class="icon"><i class="fas fa-sync"></i></span>
</button>
</p>
</div>
</div>
<div class="is-hidden-mobile is-unselectable">
<div class="is-hidden-mobile">
<span class="subtitle">This page search the remote backend data not the locally stored data.</span>
</div>
</div>
@@ -89,27 +88,16 @@
</div>
<div class="column is-12" v-if="items?.length<1 && hasSearched">
<Message message_class="is-info" v-if="true === isLoading">
<span class="icon-text">
<span class="icon"><i class="fas fa-spinner fa-pulse"></i></span>
<span>Loading data please wait...</span>
</span>
</Message>
<Message v-else class="has-background-warning-80 has-text-dark">
<button v-if="query" class="delete" @click="clearSearch"></button>
<div class="icon-text">
<span class="icon"><i class="fas fa-info"></i></span>
<Message v-if="isLoading" message_class="is-background-info-90 has-text-dark" icon="fas fa-spinner fa-spin"
title="Loading" message="Loading data please wait..."/>
<Message v-else class="has-background-warning-80 has-text-dark" title="Warning"
icon="fas fa-exclamation-triangle" :use-close="true" @close="clearSearch">
<span v-if="error?.message" v-text="error.message"></span>
<template v-else>
<span>No items found.</span>
<span v-if="query">For <code><strong>{{ searchField }}</strong> : <strong>{{ query }}</strong></code></span>
</div>
<template v-if="error">
<div class="content mt-4">
<h5 class="has-text-dark">API Response ({{ error?.code ?? 0 }})</h5>
<code class="is-pre-wrap is-block mt-4">
{{ error?.message ?? error }}
</code>
</div>
<span v-if="query">
Search query <code><strong>{{ searchField }}</strong> : <strong>{{ query }}</strong></code>
</span>
</template>
</Message>
</div>
@@ -141,12 +129,14 @@
</div>
<div class="column is-12 is-clickable has-text-left" v-if="item?.path"
@click="(e) => e.target.firstChild?.classList?.toggle('is-text-overflow')">
<div class="is-text-overflow">
<span class="icon"><i class="fas fa-file"></i></span>
<span class="is-hidden-mobile">Path:&nbsp;</span>
<NuxtLink :to="makeSearchLink('path',item.path)" v-text="item.path"/>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="card-footer-item">
<span class="icon-text">
@@ -184,24 +174,8 @@
</div>
<div class="column is-12">
<Message message_class="has-background-info-90 has-text-dark">
<div class="is-pulled-right">
<NuxtLink @click="show_page_tips=false" v-if="show_page_tips">
<span class="icon"><i class="fas fa-arrow-up"></i></span>
<span>Close</span>
</NuxtLink>
<NuxtLink @click="show_page_tips=true" v-else>
<span class="icon"><i class="fas fa-arrow-down"></i></span>
<span>Open</span>
</NuxtLink>
</div>
<h5 class="title is-5 is-unselectable">
<span class="icon-text">
<span class="icon"><i class="fas fa-info-circle"></i></span>
<span>Tips</span>
</span>
</h5>
<div class="content" v-if="show_page_tips">
<Message message_class="has-background-info-90 has-text-dark" title="Tips" icon="fas fa-info-circle"
:toggle="show_page_tips" @toggle="show_page_tips = !show_page_tips" :use-toggle="true">
<ul>
<li>
Items with <code>Referenced locally</code> link are items we were able to find local match for. While
@@ -214,7 +188,6 @@
the backend. While clicking <code>Referenced locally</code> will take you to the local item page.
</li>
</ul>
</div>
</Message>
</div>
</div>

View File

@@ -1,6 +1,6 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">
<NuxtLink to="/backends">Backends</NuxtLink>
-
@@ -11,8 +11,7 @@
<div class="is-pulled-right">
<div class="field is-grouped">
<p class="control">
<button class="button is-info" @click.prevent="loadContent" :disabled="isLoading"
:class="{'is-loading':isLoading}">
<button class="button is-info" @click="loadContent" :disabled="isLoading" :class="{'is-loading':isLoading}">
<span class="icon"><i class="fas fa-sync"></i></span>
</button>
</p>
@@ -25,18 +24,10 @@
</div>
<div class="column is-12" v-if="items.length < 1">
<Message message_class="is-info" title="Loading..." v-if="isLoading">
<span class="icon-text">
<span class="icon"><i class="fas fa-spinner fa-spin"></i></span>
<span>Requesting active play sessions. Please wait...</span>
</span>
</Message>
<Message message_class="has-background-success-90 has-text-dark" title="Information" v-else>
<span class="icon-text">
<span class="icon"><i class="fas fa-check"></i></span>
<span>There are no active play sessions currently running.</span>
</span>
</Message>
<Message message_class="is-background-info-90 has-text-dark" title="Loading" icon="fas fa-spinner fa-spin"
v-if="isLoading" message="Requesting active play sessions. Please wait..."/>
<Message v-else message_class="has-background-success-90 has-text-dark" title="Information"
icon="fa fa-info-circle" message="There are no active play sessions currently running."/>
</div>
<template v-else>
<div class="column is-12">
@@ -122,6 +113,4 @@ const makeItemLink = (item) => {
return `/history?${params.toString()}`
}
onMounted(async () => loadContent())
</script>

View File

@@ -1,13 +1,12 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">
<NuxtLink to="/backends">Backends</NuxtLink>
-
<NuxtLink :to="'/backend/' + backend">{{ backend }}</NuxtLink>
: Unmatched
</span>
<div class="is-pulled-right" v-if="hasLooked">
<div class="field is-grouped">
<p class="control">
@@ -18,14 +17,12 @@
</p>
</div>
</div>
<div class="subtitle is-hidden-mobile">
In this page you will find items <code>WatchState</code> knows that are un-matched in your backend.
</div>
</div>
<template v-if="false === hasLooked">
<div class="column is-12">
<div class="column is-12" v-if="false === hasLooked">
<div class="card">
<header class="card-header">
<p class="card-header-title is-justify-center">Request Analyze</p>
@@ -48,27 +45,17 @@
</div>
</div>
</div>
</template>
<template v-else>
<div class="column is-12" v-if="isLoading && items.length < 1">
<Message message_class="is-info" title="Analyzing">
<span class="icon-text">
<span class="icon"><i class="fas fa-spinner fa-spin"></i></span>
<span>Analyzing the backend content. Please wait. It will take a while...</span>
</span>
</Message>
</div>
<div class="column is-12" v-else-if="hasLooked && items.length < 1">
<Message message_class="has-background-success-90 has-text-dark" title="Success!">
<span class="icon-text">
<span class="icon"><i class="fas fa-check"></i></span>
<span>There are no unmatched content in the libraries we looked at.</span>
</span>
</Message>
<div class="column is-12" v-if="items.length < 1">
<Message v-if="isLoading" message_class="has-background-info-90 has-text-dark"
title="Analyzing" icon="fas fa-spinner fa-spin"
message="Analyzing the backend content. Please wait. It will take a while..."/>
<Message v-if="!isLoading && hasLooked" message_class="has-background-success-90 has-text-dark"
title="Success!" icon="fas fa-check"
message="WatchState did not find any unmatched content in the libraries we looked at."/>
</div>
<template v-else>
<div class="column is-12">
<div class="column is-12" v-if="items.length > 1">
<h1 class="title is-4">
<span class="icon-text">
<span class="icon has-text-danger"><i class="fas fa-exclamation-triangle"></i></span>
@@ -135,10 +122,7 @@
</div>
</div>
</div>
</div>
</template>
</template>
</div>
</template>
@@ -150,6 +134,8 @@ const items = ref([])
const isLoading = ref(false)
const hasLooked = ref(false)
useHead({title: `Backends: ${backend} - Unmatched items.`})
const loadContent = async () => {
hasLooked.value = true
isLoading.value = true

View File

@@ -1,43 +1,35 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">
<NuxtLink to="/backends">Backends</NuxtLink>
-
<NuxtLink :to="'/backend/' + backend">{{ backend }}</NuxtLink>
: Users
</span>
<div class="is-pulled-right">
<div class="field is-grouped">
<p class="control">
<button class="button is-info" @click.prevent="loadContent" :disabled="isLoading"
:class="{'is-loading':isLoading}">
<button class="button is-info" @click="loadContent" :disabled="isLoading" :class="{'is-loading':isLoading}">
<span class="icon"><i class="fas fa-sync"></i></span>
</button>
</p>
</div>
</div>
<div class="subtitle is-hidden-mobile">
Show all users that are available in the backend.
</div>
</div>
<div class="column is-12" v-if="items.length < 1">
<Message message_class="has-background-info-90 has-text-dark" title="No Libraries">
<span class="icon-text" v-if="isLoading">
<span class="icon"><i class="fas fa-spinner fa-spin"></i></span>
<span>Loading users list. Please wait...</span>
</span>
<span class="icon-text" v-else>
<span class="icon"><i class="fas fa-info-circle"></i></span>
<span>No users found in the backend. This is expected if the backend is plex and the token is limited.</span>
</span>
</Message>
<Message message_class="has-background-info-90 has-text-dark" title="Loading" icon="fas fa-spinner fa-spin"
message="Loading users list. Please wait..." v-if="isLoading"/>
<Message v-else message_class="has-background-warning-80 has-text-dark" title="Warning"
icon="fas fa-exclamation-circle"
message="WatchState was unable to get any users from the backend. This is expected if the backend is plex and the token is limited."/>
</div>
<div class="column is-6" v-for="item in items" :key="`library-${item.id}`">
<div class="column is-6" v-for="item in items" :key="`users-${item.id}`">
<div class="card">
<header class="card-header">
<p class="card-header-title is-text-overflow">
@@ -76,24 +68,9 @@
</div>
<div class="column is-12">
<Message message_class="has-background-info-90 has-text-dark">
<div class="is-pulled-right">
<NuxtLink @click="show_page_tips=false" v-if="show_page_tips">
<span class="icon"><i class="fas fa-arrow-up"></i></span>
<span>Close</span>
</NuxtLink>
<NuxtLink @click="show_page_tips=true" v-else>
<span class="icon"><i class="fas fa-arrow-down"></i></span>
<span>Open</span>
</NuxtLink>
</div>
<h5 class="title is-5 is-unselectable">
<span class="icon-text">
<span class="icon"><i class="fas fa-info-circle"></i></span>
<span>Tips</span>
</span>
</h5>
<div class="content" v-if="show_page_tips">
<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">
<div class="notification-content content" v-if="show_page_tips">
<ul>
<li>For <code>Plex</code> backends, if the <code>X-Plex-Token</code> is limited one, the users will not show
up. This is a limitation of the Plex API.

View File

@@ -1,16 +1,17 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">Backends</span>
<div class="is-pulled-right">
<div class="field is-grouped">
<p class="control">
<button class="button is-primary" v-tooltip="'Add New Backend'" @click="toggleForm = !toggleForm">
<button class="button is-primary" v-tooltip.bottom="'Add New Backend'"
@click="toggleForm = !toggleForm" :disabled="isLoading">
<span class="icon"><i class="fas fa-add"></i></span>
</button>
</p>
<p class="control">
<button class="button is-info" @click.prevent="loadContent">
<button class="button is-info" @click="loadContent" :disabled="isLoading" :class="{'is-loading':isLoading}">
<span class="icon"><i class="fas fa-sync"></i></span>
</button>
</p>
@@ -25,16 +26,15 @@
<BackendAdd @addBackend="toggleForm = false; loadContent()" :backends="backends"/>
</div>
<template v-else>
<div class="column is-12" v-if="backends.length<1 && !toggleForm">
<Message message_class="is-warning" title="Warning">
<span class="icon-text">
<span class="icon"><i class="fas fa-exclamation"></i></span>
<span>
<div class="column is-12" v-if="backends.length<1">
<Message v-if="isLoading" message_class="has-background-info-90 has-text-dark" title="Loading"
icon="fas fa-spinner fa-spin" message="Requesting active play sessions. Please wait..."/>
<Message v-else message_class="is-background-warning-80 has-text-dark" title="Warning"
icon="fas fa-exclamation-circle">
No backends found. Please add new backends to start using the tool. You can add new backend by
<NuxtLink @click="toggleForm=true" v-text="'clicking here'"/>
or by clicking the <span class="icon is-small"><i class="fas fa-add"></i></span> button above.
</span>
</span>
or by clicking the <span class="icon is-clickable" @click="toggleForm=true"><i class="fas fa-add"></i></span>
button above.
</Message>
</div>
@@ -94,24 +94,8 @@
</template>
<div class="column is-12">
<Message message_class="has-background-info-90 has-text-dark">
<div class="is-pulled-right">
<NuxtLink @click="show_page_tips=false" v-if="show_page_tips">
<span class="icon"><i class="fas fa-arrow-up"></i></span>
<span>Close</span>
</NuxtLink>
<NuxtLink @click="show_page_tips=true" v-else>
<span class="icon"><i class="fas fa-arrow-down"></i></span>
<span>Open</span>
</NuxtLink>
</div>
<h5 class="title is-5 is-unselectable">
<span class="icon-text">
<span class="icon"><i class="fas fa-info-circle"></i></span>
<span>Tips</span>
</span>
</h5>
<div class="content" v-if="show_page_tips">
<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">
<ul>
<li>
<strong>Import</strong> means pulling data from the backends into the local database.
@@ -145,7 +129,6 @@
page, or using the the following command <code>config:delete -s backend_name</code> in shell.
</li>
</ul>
</div>
</Message>
</div>
</div>
@@ -156,8 +139,9 @@ import 'assets/css/bulma-switch.css'
import moment from 'moment'
import request from '~/utils/request.js'
import BackendAdd from '~/components/BackendAdd.vue'
import {copyText, makeConsoleCommand} from '~/utils/index.js'
import {copyText, makeConsoleCommand, notification} from '~/utils/index.js'
import {useStorage} from "@vueuse/core";
import Message from "~/components/Message.vue";
useHead({title: 'Backends'})
@@ -165,11 +149,19 @@ const backends = ref([])
const toggleForm = ref(false)
const api_url = useStorage('api_url', '')
const show_page_tips = useStorage('show_page_tips', true)
const isLoading = ref(false)
const loadContent = async () => {
backends.value = []
isLoading.value = true
try {
const response = await request('/backends')
backends.value = await response.json()
} catch (e) {
notification('error', 'Error', `Failed to load backends. ${e.message}`)
} finally {
isLoading.value = false
}
}
onMounted(() => loadContent())

View File

@@ -1,6 +1,6 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<h1 class="title is-4">Console</h1>
<div class="subtitle">
You can execute <strong>non-interactive</strong> commands here. This interface is jailed to <code>console</code>
@@ -69,24 +69,8 @@
</div>
<div class="column is-12">
<Message message_class="has-background-info-90 has-text-dark">
<div class="is-pulled-right">
<NuxtLink @click="show_page_tips=false" v-if="show_page_tips">
<span class="icon"><i class="fas fa-arrow-up"></i></span>
<span>Close</span>
</NuxtLink>
<NuxtLink @click="show_page_tips=true" v-else>
<span class="icon"><i class="fas fa-arrow-down"></i></span>
<span>Open</span>
</NuxtLink>
</div>
<h5 class="title is-5 is-unselectable">
<span class="icon-text">
<span class="icon"><i class="fas fa-info-circle"></i></span>
<span>Tips</span>
</span>
</h5>
<div class="content" v-if="show_page_tips">
<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">
<ul>
<li>
You can also run a command from the task page by clicking on the <strong>Run via console</strong>. The
@@ -106,7 +90,6 @@
yaml</code>.
</li>
</ul>
</div>
</Message>
</div>
</div>
@@ -114,8 +97,9 @@
<script setup>
import {useStorage} from "@vueuse/core";
import {notification} from "~/utils/index.js";
import {useStorage} from '@vueuse/core'
import {notification} from '~/utils/index.js'
import Message from '~/components/Message.vue'
const route = useRoute()

View File

@@ -1,11 +1,11 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<span id="env_page_title" class="title is-4">Environment Variables</span>
<div class="column is-12 is-clearfix is-unselectable">
<span id="env_page_title" class="title is-4">Environment variables</span>
<div class="is-pulled-right">
<div class="field is-grouped">
<p class="control">
<button class="button is-primary" v-tooltip="'Add New Variable'" @click="toggleForm = !toggleForm">
<button class="button is-primary" v-tooltip.bottom="'Add new variable'" @click="toggleForm = !toggleForm">
<span class="icon">
<i class="fas fa-add"></i>
</span>
@@ -13,9 +13,7 @@
</p>
<p class="control">
<button class="button is-info" @click="loadContent">
<span class="icon">
<i class="fas fa-sync"></i>
</span>
<span class="icon"><i class="fas fa-sync"></i></span>
</button>
</p>
</div>
@@ -161,24 +159,8 @@
</div>
<div class="column is-12">
<Message message_class="has-background-info-90 has-text-dark">
<div class="is-pulled-right">
<NuxtLink @click="show_page_tips=false" v-if="show_page_tips">
<span class="icon"><i class="fas fa-arrow-up"></i></span>
<span>Close</span>
</NuxtLink>
<NuxtLink @click="show_page_tips=true" v-else>
<span class="icon"><i class="fas fa-arrow-down"></i></span>
<span>Open</span>
</NuxtLink>
</div>
<h5 class="title is-5 is-unselectable">
<span class="icon-text">
<span class="icon"><i class="fas fa-info-circle"></i></span>
<span>Tips</span>
</span>
</h5>
<div class="content" v-if="show_page_tips">
<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">
<ul>
<li>
Some variables values are masked, to unmask them click on icon <i class="fa fa-unlock"></i>.
@@ -193,7 +175,6 @@
To add a new variable click on the <i class="fa fa-add"></i> button.
</li>
</ul>
</div>
</Message>
</div>
</div>
@@ -204,6 +185,7 @@ import 'assets/css/bulma-switch.css'
import request from '~/utils/request.js'
import {awaitElement, copyText, notification} from '~/utils/index.js'
import {useStorage} from '@vueuse/core'
import Message from "~/components/Message.vue"
useHead({title: 'Environment Variables'})

View File

@@ -1,16 +1,16 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">
<NuxtLink to="/history">History</NuxtLink>
: {{ data?.full_title ?? data?.title ?? id }}
: {{ headerTitle }}
</span>
<div class="is-pulled-right" v-if="data?.via">
<div class="field is-grouped">
<p class="control">
<button class="button" @click="toggleWatched"
:class="{ 'is-success': !data.watched, 'is-danger': data.watched }"
v-tooltip="'Toggle played/unplayed'">
v-tooltip.bottom="'Toggle watch state'">
<span class="icon">
<i class="fas" :class="{'fa-eye-slash':data.watched,'fa-eye':!data.watched}"></i>
</span>
@@ -23,45 +23,36 @@
</p>
</div>
</div>
<div class="subtitle" v-if="data?.via && getTitle !== data.title">
{{ getTitle }}
<div class="subtitle is-5" v-if="data?.via && headerTitle !== data?.title">
<span class="icon">
<i class="fas fa-tv" :class="{ 'fa-tv': 'episode' === data.type, 'fa-film': 'movie' === data.type }"></i>
</span>
{{ data?.title }}
</div>
</div>
<div class="column is-12" v-if="!data?.via && isLoading">
<Message>
<span class="icon"><i class="fas fa-spinner fa-pulse"></i></span>
<span>Loading data. Please wait...</span>
</Message>
<Message message_class="has-background-info-90 has-text-dark" title="Loading"
icon="fas fa-spinner fa-spin" message="Loading data. Please wait..."/>
</div>
<div class="column is-12" v-if="data?.not_reported_by && data.not_reported_by.length>0">
<Message message_class="has-background-warning-80 has-text-dark">
<div class="is-pulled-right">
<NuxtLink @click="show_history_page_warning=false" v-if="show_history_page_warning">
<span class="icon"><i class="fas fa-arrow-up"></i></span>
<span>Close</span>
</NuxtLink>
<NuxtLink @click="show_history_page_warning=true" v-else>
<span class="icon"><i class="fas fa-arrow-down"></i></span>
<span>Open</span>
</NuxtLink>
</div>
<h5 class="title is-5 is-unselectable">
<span class="icon-text">
<span class="icon"><i class="fas fa-exclamation-triangle"></i></span>
<span>Warning</span>
</span>
</h5>
<div class="content" v-if="show_history_page_warning">
<Message message_class="has-background-warning-80 has-text-dark" icon="fas fa-exclamation-triangle"
:toggle="show_history_page_warning" title="Warning" :use-toggle="true"
@toggle="show_history_page_warning=!show_history_page_warning">
<p>
<span class="icon"><i class="fas fa-exclamation"></i></span>
There are no metadata regarding this <strong>{{ data.type }}</strong> from (
<span class="tag mr-1" v-for="backend in data.not_reported_by" :key="`nr-${backend}`">
<span class="tag mr-1 has-text-dark" v-for="backend in data.not_reported_by" :key="`nr-${backend}`">
<NuxtLink :to="`/backend/${backend}`" v-text="backend"/>
</span>).
</p>
<h5 class="has-text-dark">Possible reasons:</h5>
<h5 class="has-text-dark">
<span class="icon-text">
<span class="icon"><i class="fas fa-question-circle"></i></span>
<span>Possible reasons</span>
</span>
</h5>
<ul>
<li>Delayed import operation. Might be yet to be imported due to webhooks not being used, or the backend
doesn't support webhooks.
@@ -72,11 +63,6 @@
being reported, And thus it was treated as separate item.
</li>
</ul>
<p class="has-text-danger-50">
<span class="icon"><i class="fas fa-info"></i></span> To see if your media backends are reporting different
metadata for the same file, click on the file link which will filter your history based on that file.
</p>
</div>
</Message>
</div>
@@ -406,25 +392,13 @@
</div>
<div class="column is-12">
<Message message_class="has-background-info-90 has-text-dark">
<div class="is-pulled-right">
<NuxtLink @click="show_page_tips=false" v-if="show_page_tips">
<span class="icon"><i class="fas fa-arrow-up"></i></span>
<span>Close</span>
</NuxtLink>
<NuxtLink @click="show_page_tips=true" v-else>
<span class="icon"><i class="fas fa-arrow-down"></i></span>
<span>Open</span>
</NuxtLink>
</div>
<h5 class="title is-5 is-unselectable">
<span class="icon-text">
<span class="icon"><i class="fas fa-info-circle"></i></span>
<span>Tips</span>
</span>
</h5>
<div class="content" v-if="show_page_tips">
<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">
<ul>
<li>
To see if your media backends are reporting different metadata for the same file, click on the file link
which will filter your history based on that file.
</li>
<li>Clicking on the ID in <code>metadata via</code> boxes will take you directly to the item in the source
backend. While clicking on the GUIDs will take you to that source link, similarly clicking on the series
GUIDs will take you to the series link that was provided by the external source.
@@ -447,7 +421,6 @@
</li>
</template>
</ul>
</div>
</Message>
</div>
</div>
@@ -458,6 +431,7 @@ import request from '~/utils/request.js'
import {ag, formatDuration, makeGUIDLink, makeSearchLink, notification, ucFirst} from '~/utils/index.js'
import moment from 'moment'
import {useStorage} from "@vueuse/core";
import Message from "~/components/Message.vue";
const id = useRoute().params.id
@@ -537,5 +511,7 @@ const toggleWatched = async () => {
}
}
const headerTitle = computed(() => `${data.value?.full_title ?? data.value?.title ?? id}`)
onMounted(async () => loadContent(id))
</script>

View File

@@ -1,18 +1,18 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">History</span>
<div class="is-pulled-right">
<div class="field is-grouped">
<p class="control">
<button class="button is-primary" @click.prevent="searchForm = !searchForm">
<button class="button is-primary" @click="searchForm = !searchForm">
<span class="icon">
<i class="fas fa-search"></i>
</span>
</button>
</p>
<p class="control">
<button class="button is-info" @click.prevent="loadContent(page, true)">
<button class="button is-info" @click="loadContent(page, true)">
<span class="icon">
<i class="fas fa-sync"></i>
</span>
@@ -73,7 +73,7 @@
<select v-model="searchField" class="is-capitalized" :disabled="isLoading">
<option value="">Select Field</option>
<option v-for="field in searchable" :key="'search-' + field.key" :value="field.key">
{{ field.key }}
{{ field.display ?? field.key }}
</option>
</select>
</div>
@@ -189,18 +189,12 @@
</div>
</div>
<div class="column is-12" v-else>
<Message message_class="is-info" v-if="true === isLoading">
<span class="icon-text">
<span class="icon"><i class="fas fa-spinner fa-pulse"></i></span>
<span>Loading data please wait...</span>
</span>
</Message>
<Message v-else message_class="is-warning">
<button v-if="query" class="delete" @click="clearSearch"></button>
<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..."/>
<Message v-else class="has-background-warning-80 has-text-dark" title="Warning"
icon="fas fa-exclamation-triangle" :use-close="true" @close="clearSearch">
<div class="icon-text">
<span class="icon"><i class="fas fa-info"></i></span>
<span>No items found.</span>
No items found.
<span v-if="query">For <code><strong>{{ searchField }}</strong> : <strong>{{ query }}</strong></code></span>
</div>
<code class="is-block mt-4" v-if="error">{{ error }}</code>
@@ -231,7 +225,7 @@ const total = ref(0)
const last_page = computed(() => Math.ceil(total.value / perpage.value))
const query = ref(route.query.q ?? '')
const searchField = ref(route.query.key ?? '')
const searchField = ref(route.query.key ?? 'title')
const isLoading = ref(false)
const searchForm = ref(false)
@@ -340,7 +334,6 @@ const makePagination = () => {
const clearSearch = () => {
query.value = ''
searchField.value = ''
searchForm.value = false
loadContent(1)
}

View File

@@ -1,11 +1,12 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span id="env_page_title" class="title is-4">Ignored GUIDs</span>
<div class="is-pulled-right">
<div class="field is-grouped">
<p class="control">
<button class="button is-primary" v-tooltip="'Add New Ignore'" @click="toggleForm = !toggleForm">
<button class="button is-primary" v-tooltip.bottom="'Add New Ignore rule'"
@click="toggleForm = !toggleForm">
<span class="icon">
<i class="fas fa-add"></i>
</span>
@@ -244,42 +245,17 @@
</div>
<div class="column is-12" v-if="items.length < 1">
<Message message_class="is-info" title="Loading..." v-if="isLoading">
<span class="icon-text">
<span class="icon"><i class="fas fa-spinner fa-spin"></i></span>
<span>Request ignore list. Please wait...</span>
</span>
</Message>
<Message message_class="has-background-success-90 has-text-dark" title="Information" v-else>
<span class="icon-text">
<span class="icon"><i class="fas fa-check"></i></span>
<span>
There are no ignore rules currently set. You can add new ignore rules by clicking on the <i
class="fas fa-add"></i> button.
</span>
</span>
<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..."/>
<Message v-else message_class="has-background-success-90 has-text-dark" title="Information" icon="fas fa-check">
There are no ignore rules configured. You can add new ignore rules by clicking on the
<i @click="toggleForm=true" class="is-clickable fas fa-add"></i> button.
</Message>
</div>
<div class="column is-12">
<Message message_class="has-background-info-90 has-text-dark">
<div class="is-pulled-right">
<NuxtLink @click="show_page_tips=false" v-if="show_page_tips">
<span class="icon"><i class="fas fa-arrow-up"></i></span>
<span>Close</span>
</NuxtLink>
<NuxtLink @click="show_page_tips=true" v-else>
<span class="icon"><i class="fas fa-arrow-down"></i></span>
<span>Open</span>
</NuxtLink>
</div>
<h5 class="title is-5 is-unselectable">
<span class="icon-text">
<span class="icon"><i class="fas fa-info-circle"></i></span>
<span>Tips</span>
</span>
</h5>
<div class="content" v-if="show_page_tips">
<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">
<ul>
<li>Ignoring specific GUID sometimes helps in preventing incorrect data being added to WatchState, due to
incorrect metadata being provided by backends.
@@ -288,9 +264,10 @@
<code>GUID</code> means in terms of WatchState is the unique identifier for a specific item in the
external data source.
</li>
<li>To add a new ignore rule click on the <i class="fa fa-add"></i> button.</li>
<li>To add a new ignore rule click on the <i @click="toggleForm=true" class="is-clickable fa fa-add"></i>
button.
</li>
</ul>
</div>
</Message>
</div>
</div>
@@ -302,6 +279,7 @@ import {awaitElement, copyText, notification, stringToRegex} from '~/utils/index
import {useStorage} from '@vueuse/core'
import moment from 'moment'
import 'assets/css/bulma-switch.css'
import Message from "~/components/Message.vue";
useHead({title: 'Ignored GUIDs'})

View File

@@ -7,7 +7,7 @@
</div>
<div class="column is-12">
<div class="columns is-multiline" v-if="lastHistory.length>0">
<div class="columns is-multiline" v-if="lastHistory.length>1">
<div class="column is-6-tablet" v-for="history in lastHistory" :key="history.id">
<div class="card" :class="{ 'is-success': history.watched }">
<header class="card-header">
@@ -58,11 +58,9 @@
</div>
</div>
<div class="column is-12" v-else>
<Message message_class="is-warning">
<span class="icon-text">
<span class="icon"><i class="fas fa-exclamation"></i></span>
<span>No items were found. There are probably no items in the local database yet.</span>
</span>
<Message title="Warning" message_class="has-background-warning-90 has-text-dark"
icon="fas fa-exclamation-triangle"
message="No items were found. There are probably no items in the local database yet.">
</Message>
</div>
</div>
@@ -80,7 +78,7 @@
<div class="column is-12">
<div class="content">
<Message message_class="has-background-info-90 has-text-dark">
<Message title="Welcome" message_class="has-background-info-90 has-text-dark" icon="fas fa-heart">
If you have question, or want clarification on something, or just want to chat with other users, you are
welcome to join our <span class="icon-text is-underlined">
<span class="icon"><i class="fas fa-brands fa-discord"></i></span>

View File

@@ -1,6 +1,6 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">
<NuxtLink to="/logs">Logs</NuxtLink>
: {{ filename }}
@@ -10,33 +10,33 @@
<div class="field is-grouped">
<p class="control">
<button class="button is-danger" v-tooltip="'Delete Logfile.'" @click="deleteFile">
<button class="button is-danger" v-tooltip.bottom="'Delete Logfile.'" @click="deleteFile">
<span class="icon"><i class="fas fa-trash"></i></span>
</button>
</p>
<p class="control">
<button class="button is-danger is-light" v-tooltip="'Download the entire logfile.'" @click="downloadFile"
:class="{'is-loading':isDownloading}">
<button class="button is-danger is-light" v-tooltip.bottom="'Download the entire logfile.'"
@click="downloadFile" :class="{'is-loading':isDownloading}">
<span class="icon"><i class="fas fa-download"></i></span>
</button>
</p>
<p class="control" v-if="filename.includes(moment().format('YYYYMMDD'))">
<button class="button" v-tooltip="'Watch log'" @click="watchLog"
<button class="button" v-tooltip.bottom="'Watch log'" @click="watchLog"
:class="{'is-primary':!stream,'is-danger':stream}">
<span class="icon"><i class="fas fa-stream"></i></span>
</button>
</p>
<p class="control">
<button class="button is-warning" @click.prevent="wrapLines = !wrapLines" v-tooltip="'Toggle wrap line'">
<button class="button is-warning" @click="wrapLines = !wrapLines" v-tooltip.bottom="'Toggle wrap line'">
<span class="icon"><i class="fas fa-text-width"></i></span>
</button>
</p>
<p class="control">
<button class="button is-info" @click.prevent="loadContent">
<button class="button is-info" @click="loadContent" :disabled="isLoading" :class="{'is-loading':isLoading}">
<span class="icon"><i class="fas fa-sync"></i></span>
</button>
</p>
@@ -46,25 +46,22 @@
</div>
<div class="column is-12">
<template v-if="stream">
<Message message_class="is-info">
<div class="notification has-background-info-90 has-text-dark" v-if="stream">
<button class="delete" @click="watchLog"></button>
<span class="icon-text">
<span class="icon"><i class="fas fa-spinner fa-pulse"></i></span>
<span>Streaming log content...</span>
</span>
</Message>
</template>
</div>
<code ref="logContainer" class="box logs-container" v-if="!error"
:class="{'is-pre': !wrapLines, 'is-pre-wrap': wrapLines}">
<span class="is-log-line is-block" v-for="(item, index) in data" :key="'log_line-'+index">
<span class="is-log-line is-block pt-1" v-for="(item, index) in data" :key="'log_line-'+index">
{{ item }}
</span>
</code>
<template v-else>
<Message title="Request Error" message_class="is-danger" :message="error"/>
</template>
<Message v-if="error" title="API Error" message_class="has-background-warning-90 has-text-dark"
:message="error" :use-close="true" @close="router.push('/logs')"/>
</div>
</div>
</template>
@@ -84,14 +81,16 @@ import {useStorage} from '@vueuse/core'
import {notification} from '~/utils/index.js'
import request from '~/utils/request.js'
const router = useRouter()
const filename = useRoute().params.filename
useHead({title: `Logs : ${filename}`})
const data = ref([])
const error = ref('')
const wrapLines = ref(true)
const wrapLines = useStorage('logs_wrap_lines', false)
const isDownloading = ref(false)
const isLoading = ref(false)
const api_path = useStorage('api_path', '/v1/api')
const api_url = useStorage('api_url', '')
@@ -105,15 +104,23 @@ const logContainer = ref(null)
const loadContent = async () => {
try {
isLoading.value = true
const response = await request(`/log/${filename}`)
if (response.ok) {
const text = await response.text()
data.value = text.split('\n')
} else {
try {
const json = await response.json();
error.value = `${json.error.code}: ${json.error.message}`
} catch (e) {
error.value = `${response.status}: ${response.statusText}`
}
}
} catch (e) {
error.value = e
} finally {
isLoading.value = false
}
}
@@ -205,5 +212,10 @@ const deleteFile = async () => {
const updateScroll = () => logContainer.value.scrollTop = logContainer.value.scrollHeight;
onUpdated(() => updateScroll());
onUpdated(() => {
if (error.value) {
return
}
updateScroll()
});
</script>

View File

@@ -5,7 +5,7 @@
<div class="is-pulled-right">
<div class="field is-grouped">
<p class="control">
<button class="button is-info" @click.prevent="loadContent">
<button class="button is-info" @click="loadContent" :disabled="isLoading" :class="{'is-loading':isLoading}">
<span class="icon"><i class="fas fa-sync"></i></span>
</button>
</p>
@@ -18,6 +18,13 @@
</div>
</div>
<div class="column is-12" v-if="logs.length < 1 || isLoading">
<Message v-if="isLoading" message_class="is-background-info-90 has-text-dark" icon="fas fa-spinner fa-spin"
title="Loading" message="Loading data. Please wait..."/>
<Message v-else title="Warning" message_class="is-background-warning-80 has-text-dark"
icon="fas fa-exclamation-triangle" message="No logs files found."/>
</div>
<div class="column is-4-tablet" v-for="(item, index) in logs" :key="'log-'+index">
<div class="card">
<header class="card-header">
@@ -59,19 +66,29 @@
import request from "~/utils/request.js";
import moment from "moment";
import {humanFileSize} from "~/utils/index.js";
import Message from "~/components/Message.vue";
useHead({title: 'Logs'})
const logs = ref([])
const isLoading = ref(false)
const loadContent = async () => {
logs.value = []
isLoading.value = true
try {
const response = await request('/logs')
let data = await response.json();
data.sort((a, b) => new Date(b.modified) - new Date(a.modified));
logs.value = data;
} catch (e) {
notification('error', 'Error', e.message)
} finally {
isLoading.value = false
}
}
onMounted(() => loadContent())

View File

@@ -1,6 +1,6 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">Queue</span>
<div class="is-pulled-right">
<div class="field is-grouped">
@@ -18,18 +18,11 @@
</div>
<div class="column is-12" v-if="items.length < 1">
<Message message_class="is-info" title="Loading..." v-if="isLoading">
<span class="icon-text">
<span class="icon"><i class="fas fa-spinner fa-spin"></i></span>
<span>Requesting queued events. Please wait...</span>
</span>
</Message>
<Message message_class="has-background-success-90 has-text-dark" title="Information" v-else>
<span class="icon-text">
<span class="icon"><i class="fas fa-check"></i></span>
<span>There are currently no queued events to be sent to backends.</span>
</span>
</Message>
<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..."/>
<Message v-else message_class="is-background-success-90 has-text-dark" title="Information"
icon="fas fa-info-circle"
message="There are currently no queued events."/>
</div>
<div class="column is-4 is-6-tablet" v-for="item in items" :key="item.id">

View File

@@ -1,11 +1,11 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">System Report</span>
<div class="is-pulled-right" v-if="false === show_report_warning">
<div class="field is-grouped">
<p class="control">
<button class="button is-primary" @click="copyText(data.join('\n'))" v-tooltip="'Copy Report'">
<button class="button is-primary" @click="copyText(data.join('\n'))" v-tooltip.bottom="'Copy Report'">
<span class="icon"><i class="fas fa-copy"></i></span>
</button>
</p>
@@ -18,25 +18,23 @@
<div class="column is-12">
<template v-if="show_report_warning">
<Message message_class="has-background-warning-80 has-text-dark" title="Warning">
<p>While we try to make sure no sensitive information is leaked via the report, it's possible that
something might be missed. Please review the report before posting it. If you notice
any sensitive information, please report it to the developers. so we can fix it.</p>
<Message message_class="has-background-warning-80 has-text-dark" title="Warning"
icon="fas fa-exclamation-triangle">
While we try to make sure no sensitive information is leaked via the report, it's possible that something
might be missed. Please review the report before posting it. If you notice any sensitive information, please
report it to the developers. so we can fix it.
</Message>
<div class="mt-4">
<button class="button is-block is-fullwidth is-primary" @click="show_report_warning = false">
<div class="mt-4 has-text-centered">
<NuxtLink class="is-block is-fullwidth is-primary" @click="show_report_warning = false">
<span class="icon-text">
<span class="icon"><i class="fas fa-thumbs-up"></i></span>
<span>I Understand. Show me the report.</span>
</span>
</button>
</NuxtLink>
</div>
</template>
<Message message_class="is-info" v-if="!show_report_warning && data.length < 1">
<span class="icon"><i class="fas fa-spinner fa-pulse"></i></span>
<span>Generating the report. Please wait...</span>
</Message>
<Message message_class="has-background-info-90 has-text-dark" v-if="!show_report_warning && data.length < 1"
title="Loading" icon="fas fa-spinner fa-spin" message="Generating the report. Please wait..."/>
<template v-if="!show_report_warning && data.length > 0">
<pre style="min-height: 60vh;max-height:70vh; overflow-y: scroll" id="report-content"
><code><span v-for="(item, index) in data" :key="index" class="is-block">{{ item }}</span></code></pre>
@@ -60,5 +58,4 @@ watch(show_report_warning, async (value) => {
const response = await request(`/system/report`)
data.value = await response.json()
})
</script>

View File

@@ -1,11 +1,12 @@
<template>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix">
<div class="column is-12 is-clearfix is-unselectable">
<span class="title is-4">Tasks</span>
<div class="is-pulled-right">
<div class="field is-grouped">
<p class="control">
<button class="button is-info" @click.prevent="loadContent(true)">
<button class="button is-info" @click="loadContent()" :disabled="isLoading"
:class="{'is-loading':isLoading}">
<span class="icon"><i class="fas fa-sync"></i></span>
</button>
</p>
@@ -19,7 +20,10 @@
</template>
</span>
</div>
</div>
<div class="column is-12" v-if="isLoading">
<Message message_class="has-background-info-90 has-text-dark" title="Loading"
icon="fas fa-spinner fa-spin" message="Loading data. Please wait..."/>
</div>
<div v-for="task in tasks" :key="task.name" class="column is-6-tablet is-12-mobile">
@@ -95,6 +99,23 @@
</footer>
</div>
</div>
<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">
<ul>
<li>For long running tasks like <code>Import</code> and <code>Export</code>, you should queue the task to run
in background. As running them via web console will take longer if you have many backends and/or has large
libraries.
</li>
<li>Use the switch next to the task enable to enable or disable the task from automatically running.</li>
<li>To change when task is scheduled to run, please visit
<NuxtLink to="/env" v-text="'Environment variables'"/>
page. The <code>WS_CRON_(TASK)_*</code> variables are used to control scheduled tasks.
</li>
</ul>
</Message>
</div>
</div>
</template>
@@ -102,27 +123,37 @@
import 'assets/css/bulma-switch.css'
import moment from 'moment'
import request from '~/utils/request.js'
import {notification} from "~/utils/index.js";
import {notification} from '~/utils/index.js'
import cronstrue from 'cronstrue'
import Message from '~/components/Message.vue'
import {useStorage} from '@vueuse/core'
useHead({title: 'Tasks'})
const tasks = ref([])
const queued = ref([])
const isLoading = ref(false)
const show_page_tips = useStorage('show_page_tips', true)
const loadContent = async (clear = false) => {
if (clear) {
const loadContent = async () => {
isLoading.value = true
tasks.value = []
}
try {
const response = await request('/tasks')
const json = await response.json()
tasks.value = json.tasks
queued.value = json.queued
} catch (e) {
notification('error', 'Error', `Request error. ${e.message}`)
} finally {
isLoading.value = false
}
}
onMounted(() => loadContent())
const toggleTask = async (task) => {
const toggleTask = async task => {
try {
const keyName = `WS_CRON_${task.name.toUpperCase()}`
await request(`/system/env/${keyName}`, {
method: 'POST',
@@ -131,21 +162,28 @@ const toggleTask = async (task) => {
const response = await request(`/tasks/${task.name}`)
tasks.value[tasks.value.findIndex(b => b.name === task.name)] = await response.json()
} catch (e) {
notification('error', 'Error', `Request error. ${e.message}`)
}
}
const queueTask = async (task) => {
const queueTask = async task => {
if (!confirm(`Queue '${task.name}' to run in background?`)) {
return
}
try {
const response = await request(`/tasks/${task.name}/queue`, {method: 'POST'})
if (response.ok) {
notification('success', 'Success', `Task ${task.name} has been queued.`)
await loadContent()
}
} catch (e) {
notification('error', 'Error', `Request error. ${e.message}`)
}
}
const confirmRun = async (task) => {
const confirmRun = async task => {
if (!confirm(`Are you sure you want to run '${task.name}' via web console now?`)) {
return
}

View File

@@ -200,6 +200,22 @@ final class Index
$filters[iState::COLUMN_META_PATH] = $data->get(iState::COLUMN_META_PATH);
}
if ($data->get('subtitle')) {
foreach ($this->getBackends() as $backend) {
$bName = $backend['name'];
if ($data->get('exact')) {
$or[] = "json_extract(" . iState::COLUMN_META_DATA . ",'$.{$bName}.extra.title') = :subtitle_{$bName}";
} else {
$or[] = "json_extract(" . iState::COLUMN_META_DATA . ",'$.{$bName}.extra.title') LIKE \"%\" || :subtitle_{$bName} || \"%\"";
}
$params["subtitle_{$bName}"] = $data->get('subtitle');
}
$filters['subtitle'] = $data->get('subtitle');
}
if ($data->get(iState::COLUMN_EXTRA)) {
$sField = $data->get('key');
$sValue = $data->get('value');
@@ -336,6 +352,7 @@ final class Index
],
[
'key' => 'via',
'display' => 'Backend',
'description' => 'Search using the backend name.',
'type' => 'string',
],
@@ -369,13 +386,15 @@ final class Index
],
[
'key' => 'parent',
'display' => 'Series GUID',
'description' => 'Search using the parent GUID.',
'type' => 'guid://id',
'type' => 'provider://id',
],
[
'key' => 'guids',
'display' => 'Content GUID',
'description' => 'Search using the GUID.',
'type' => 'guid://id',
'type' => 'provider://id',
],
[
'key' => 'metadata',
@@ -397,6 +416,12 @@ final class Index
'description' => 'Search using file path. Searching this field might be slow.',
'type' => 'string',
],
[
'key' => 'subtitle',
'display' => 'Content title',
'description' => 'Search using content title. Searching this field will be slow.',
'type' => 'string',
],
],
];