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

View File

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

View File

@@ -1,9 +1,27 @@
<template> <template>
<div class="notification" :class="message_class"> <div class="notification" :class="message_class">
<h2 class="title is-5" v-if="title">{{ title }}</h2> <button class="delete" @click="$emit('close')" v-if="!useToggle && useClose"></button>
<p v-if="message">{{ message }}</p> <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/> <slot/>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
@@ -13,6 +31,11 @@ defineProps({
default: null, default: null,
required: false required: false
}, },
icon: {
type: String,
default: null,
required: false
},
message: { message: {
type: String, type: String,
default: null, default: null,
@@ -22,6 +45,23 @@ defineProps({
type: String, type: String,
default: 'is-info', default: 'is-info',
required: false 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> </script>

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
<template> <template>
<div class="columns is-multiline"> <div class="columns is-multiline">
<div class="column is-12 is-clearfix is-unselectable">
<div class="column is-12 is-clearfix">
<span class="title is-4"> <span class="title is-4">
<NuxtLink to="/backends" v-text="'Backends'"/> <NuxtLink to="/backends" v-text="'Backends'"/>
: {{ backend }} : {{ backend }}
@@ -9,7 +8,7 @@
<div class="is-pulled-right"> <div class="is-pulled-right">
<div class="field is-grouped"> <div class="field is-grouped">
<p class="control"> <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> <span class="icon"><i class="fas fa-edit"></i></span>
</NuxtLink> </NuxtLink>
</p> </p>
@@ -27,7 +26,7 @@
<h1 class="title is-4">Useful Tools</h1> <h1 class="title is-4">Useful Tools</h1>
<ul> <ul>
<li> <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>
<li> <li>
<NuxtLink :to="`/backend/${backend}/unmatched`" v-text="'Find unmatched content.'"/> <NuxtLink :to="`/backend/${backend}/unmatched`" v-text="'Find unmatched content.'"/>
@@ -49,7 +48,13 @@
</div> </div>
</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"> <div class="column is-12">
<h1 class="title is-4">Recent History</h1> <h1 class="title is-4">Recent History</h1>
</div> </div>
@@ -111,17 +116,17 @@
</div> </div>
</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> </template>
<script setup> <script setup>
@@ -130,11 +135,11 @@ import Message from '~/components/Message.vue'
import {formatDuration, notification} from "~/utils/index.js"; import {formatDuration, notification} from "~/utils/index.js";
const backend = useRoute().params.backend const backend = useRoute().params.backend
const historyUrl = `/history/?via=${backend}`
useHead({title: `Backends: ${backend}`}) useHead({title: `Backends: ${backend}`})
const bHistory = ref([]) const bHistory = ref([])
const info = ref({})
const loadRecentHistory = async () => { const loadRecentHistory = async () => {
const response = await request(`/history/?perpage=6&via=${backend}`) const response = await request(`/history/?perpage=6&via=${backend}`)
@@ -148,5 +153,14 @@ const loadRecentHistory = async () => {
bHistory.value = json.history 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> </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> <template>
<div class="columns is-multiline"> <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"> <span class="title is-4">
<NuxtLink to="/backends">Backends</NuxtLink> <NuxtLink to="/backends">Backends</NuxtLink>
- -
@@ -11,8 +11,7 @@
<div class="is-pulled-right"> <div class="is-pulled-right">
<div class="field is-grouped"> <div class="field is-grouped">
<p class="control"> <p class="control">
<button class="button is-info" @click.prevent="loadContent" :disabled="isLoading" <button class="button is-info" @click="loadContent" :disabled="isLoading" :class="{'is-loading':isLoading}">
:class="{'is-loading':isLoading}">
<span class="icon"><i class="fas fa-sync"></i></span> <span class="icon"><i class="fas fa-sync"></i></span>
</button> </button>
</p> </p>
@@ -25,16 +24,11 @@
</div> </div>
<div class="column is-12" v-if="items.length < 1"> <div class="column is-12" v-if="items.length < 1">
<Message message_class="has-background-info-90 has-text-dark" title="No Libraries"> <Message message_class="has-background-info-90 has-text-dark" title="Loading" icon="fas fa-spinner fa-spin"
<span class="icon-text" v-if="isLoading"> message="Loading libraries list. Please wait..." v-if="isLoading"/>
<span class="icon"><i class="fas fa-spinner fa-spin"></i></span> <Message v-else message_class="has-background-warning-80 has-text-dark" title="Warning"
<span>Loading libraries list. Please wait...</span> icon="fas fa-exclamation-circle"
</span> message="WatchState was unable to get any libraries from the backend."/>
<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>
</div> </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="`library-${item.id}`">
@@ -82,31 +76,14 @@
</div> </div>
<div class="column is-12"> <div class="column is-12">
<Message message_class="has-background-info-90 has-text-dark"> <Message message_class="has-background-info-90 has-text-dark" title="Tips" icon="fas fa-info-circle"
<div class="is-pulled-right"> :toggle="show_page_tips" @toggle="show_page_tips = !show_page_tips" :use-toggle="true">
<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">
<ul> <ul>
<li>Ignoring library will prevent any content from being added to the local database from that library <li>Ignoring library will prevent any content from being added to the local database from the library
during import process, and via webhook events. during import process, and webhook events handling.
</li> </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> </ul>
</div>
</Message> </Message>
</div> </div>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
</div> </div>
<div class="column is-12"> <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="column is-6-tablet" v-for="history in lastHistory" :key="history.id">
<div class="card" :class="{ 'is-success': history.watched }"> <div class="card" :class="{ 'is-success': history.watched }">
<header class="card-header"> <header class="card-header">
@@ -58,11 +58,9 @@
</div> </div>
</div> </div>
<div class="column is-12" v-else> <div class="column is-12" v-else>
<Message message_class="is-warning"> <Message title="Warning" message_class="has-background-warning-90 has-text-dark"
<span class="icon-text"> icon="fas fa-exclamation-triangle"
<span class="icon"><i class="fas fa-exclamation"></i></span> message="No items were found. There are probably no items in the local database yet.">
<span>No items were found. There are probably no items in the local database yet.</span>
</span>
</Message> </Message>
</div> </div>
</div> </div>
@@ -80,7 +78,7 @@
<div class="column is-12"> <div class="column is-12">
<div class="content"> <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 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"> welcome to join our <span class="icon-text is-underlined">
<span class="icon"><i class="fas fa-brands fa-discord"></i></span> <span class="icon"><i class="fas fa-brands fa-discord"></i></span>

View File

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

View File

@@ -5,7 +5,7 @@
<div class="is-pulled-right"> <div class="is-pulled-right">
<div class="field is-grouped"> <div class="field is-grouped">
<p class="control"> <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> <span class="icon"><i class="fas fa-sync"></i></span>
</button> </button>
</p> </p>
@@ -18,6 +18,13 @@
</div> </div>
</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="column is-4-tablet" v-for="(item, index) in logs" :key="'log-'+index">
<div class="card"> <div class="card">
<header class="card-header"> <header class="card-header">
@@ -59,19 +66,29 @@
import request from "~/utils/request.js"; import request from "~/utils/request.js";
import moment from "moment"; import moment from "moment";
import {humanFileSize} from "~/utils/index.js"; import {humanFileSize} from "~/utils/index.js";
import Message from "~/components/Message.vue";
useHead({title: 'Logs'}) useHead({title: 'Logs'})
const logs = ref([]) const logs = ref([])
const isLoading = ref(false)
const loadContent = async () => { const loadContent = async () => {
logs.value = [] logs.value = []
isLoading.value = true
try {
const response = await request('/logs') const response = await request('/logs')
let data = await response.json(); let data = await response.json();
data.sort((a, b) => new Date(b.modified) - new Date(a.modified)); data.sort((a, b) => new Date(b.modified) - new Date(a.modified));
logs.value = data; logs.value = data;
} catch (e) {
notification('error', 'Error', e.message)
} finally {
isLoading.value = false
}
} }
onMounted(() => loadContent()) onMounted(() => loadContent())

View File

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

View File

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

View File

@@ -1,11 +1,12 @@
<template> <template>
<div class="columns is-multiline"> <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> <span class="title is-4">Tasks</span>
<div class="is-pulled-right"> <div class="is-pulled-right">
<div class="field is-grouped"> <div class="field is-grouped">
<p class="control"> <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> <span class="icon"><i class="fas fa-sync"></i></span>
</button> </button>
</p> </p>
@@ -19,7 +20,10 @@
</template> </template>
</span> </span>
</div> </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>
<div v-for="task in tasks" :key="task.name" class="column is-6-tablet is-12-mobile"> <div v-for="task in tasks" :key="task.name" class="column is-6-tablet is-12-mobile">
@@ -95,6 +99,23 @@
</footer> </footer>
</div> </div>
</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> </div>
</template> </template>
@@ -102,27 +123,37 @@
import 'assets/css/bulma-switch.css' import 'assets/css/bulma-switch.css'
import moment from 'moment' import moment from 'moment'
import request from '~/utils/request.js' import request from '~/utils/request.js'
import {notification} from "~/utils/index.js"; import {notification} from '~/utils/index.js'
import cronstrue from 'cronstrue' import cronstrue from 'cronstrue'
import Message from '~/components/Message.vue'
import {useStorage} from '@vueuse/core'
useHead({title: 'Tasks'}) useHead({title: 'Tasks'})
const tasks = ref([]) const tasks = ref([])
const queued = ref([]) const queued = ref([])
const isLoading = ref(false)
const show_page_tips = useStorage('show_page_tips', true)
const loadContent = async (clear = false) => { const loadContent = async () => {
if (clear) { isLoading.value = true
tasks.value = [] tasks.value = []
} try {
const response = await request('/tasks') const response = await request('/tasks')
const json = await response.json() const json = await response.json()
tasks.value = json.tasks tasks.value = json.tasks
queued.value = json.queued queued.value = json.queued
} catch (e) {
notification('error', 'Error', `Request error. ${e.message}`)
} finally {
isLoading.value = false
}
} }
onMounted(() => loadContent()) onMounted(() => loadContent())
const toggleTask = async (task) => { const toggleTask = async task => {
try {
const keyName = `WS_CRON_${task.name.toUpperCase()}` const keyName = `WS_CRON_${task.name.toUpperCase()}`
await request(`/system/env/${keyName}`, { await request(`/system/env/${keyName}`, {
method: 'POST', method: 'POST',
@@ -131,21 +162,28 @@ const toggleTask = async (task) => {
const response = await request(`/tasks/${task.name}`) const response = await request(`/tasks/${task.name}`)
tasks.value[tasks.value.findIndex(b => b.name === task.name)] = await response.json() 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?`)) { if (!confirm(`Queue '${task.name}' to run in background?`)) {
return return
} }
try {
const response = await request(`/tasks/${task.name}/queue`, {method: 'POST'}) const response = await request(`/tasks/${task.name}/queue`, {method: 'POST'})
if (response.ok) { if (response.ok) {
notification('success', 'Success', `Task ${task.name} has been queued.`) notification('success', 'Success', `Task ${task.name} has been queued.`)
await loadContent() 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?`)) { if (!confirm(`Are you sure you want to run '${task.name}' via web console now?`)) {
return return
} }

View File

@@ -200,6 +200,22 @@ final class Index
$filters[iState::COLUMN_META_PATH] = $data->get(iState::COLUMN_META_PATH); $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)) { if ($data->get(iState::COLUMN_EXTRA)) {
$sField = $data->get('key'); $sField = $data->get('key');
$sValue = $data->get('value'); $sValue = $data->get('value');
@@ -336,6 +352,7 @@ final class Index
], ],
[ [
'key' => 'via', 'key' => 'via',
'display' => 'Backend',
'description' => 'Search using the backend name.', 'description' => 'Search using the backend name.',
'type' => 'string', 'type' => 'string',
], ],
@@ -369,13 +386,15 @@ final class Index
], ],
[ [
'key' => 'parent', 'key' => 'parent',
'display' => 'Series GUID',
'description' => 'Search using the parent GUID.', 'description' => 'Search using the parent GUID.',
'type' => 'guid://id', 'type' => 'provider://id',
], ],
[ [
'key' => 'guids', 'key' => 'guids',
'display' => 'Content GUID',
'description' => 'Search using the GUID.', 'description' => 'Search using the GUID.',
'type' => 'guid://id', 'type' => 'provider://id',
], ],
[ [
'key' => 'metadata', 'key' => 'metadata',
@@ -397,6 +416,12 @@ final class Index
'description' => 'Search using file path. Searching this field might be slow.', 'description' => 'Search using file path. Searching this field might be slow.',
'type' => 'string', 'type' => 'string',
], ],
[
'key' => 'subtitle',
'display' => 'Content title',
'description' => 'Search using content title. Searching this field will be slow.',
'type' => 'string',
],
], ],
]; ];