ignore escape characters in regex, get playlist from other days, fix ffplayout #599, colorize over length, save playlists longer then 24 hours, format with prettier, eslint ignore slash on void

This commit is contained in:
jb-alvarado 2024-04-17 09:55:57 +02:00
parent 6c574feda1
commit cbb69d0e16
27 changed files with 257 additions and 276 deletions

2
.gitignore vendored
View File

@ -29,8 +29,6 @@ tv-media/
Videos Videos
Videos/ Videos/
*.tar* *.tar*
.vscode
.vscode/
home home
home/ home/
live1 live1

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"eslint.experimental.useFlatConfig": true,
}

View File

@ -18,7 +18,7 @@
type="text" type="text"
placeholder="Type here" placeholder="Type here"
class="input input-bordered w-full" class="input input-bordered w-full"
> />
</label> </label>
<label class="form-control w-full mt-5"> <label class="form-control w-full mt-5">
@ -29,7 +29,7 @@
v-model="configStore.configGui[configStore.configID].preview_url" v-model="configStore.configGui[configStore.configID].preview_url"
type="text" type="text"
class="input input-bordered w-full" class="input input-bordered w-full"
> />
</label> </label>
<label class="form-control w-full mt-5"> <label class="form-control w-full mt-5">
@ -40,7 +40,7 @@
v-model="configStore.configGui[configStore.configID].config_path" v-model="configStore.configGui[configStore.configID].config_path"
type="text" type="text"
class="input input-bordered w-full" class="input input-bordered w-full"
> />
</label> </label>
<label class="form-control w-full mt-5"> <label class="form-control w-full mt-5">
@ -51,7 +51,7 @@
v-model="configStore.configGui[configStore.configID].extra_extensions" v-model="configStore.configGui[configStore.configID].extra_extensions"
type="text" type="text"
class="input input-bordered w-full" class="input input-bordered w-full"
> />
</label> </label>
<label class="form-control w-full mt-5"> <label class="form-control w-full mt-5">
@ -63,7 +63,7 @@
type="text" type="text"
class="input input-bordered w-full !bg-base-100" class="input input-bordered w-full !bg-base-100"
disabled disabled
> />
</label> </label>
<div class="join my-4"> <div class="join my-4">

View File

@ -2,11 +2,11 @@
<div class="max-w-[1200px] pe-8"> <div class="max-w-[1200px] pe-8">
<h2 class="pt-3 text-3xl">{{ $t('config.playoutConf') }}</h2> <h2 class="pt-3 text-3xl">{{ $t('config.playoutConf') }}</h2>
<form <form
v-if="configStore.configPlayout" v-if="configStore.playout"
class="mt-10 grid md:grid-cols-[180px_auto] gap-5" class="mt-10 grid md:grid-cols-[180px_auto] gap-5"
@submit.prevent="onSubmitPlayout" @submit.prevent="onSubmitPlayout"
> >
<template v-for="(item, key) in configStore.configPlayout" :key="key"> <template v-for="(item, key) in configStore.playout" :key="key">
<div class="text-xl pt-3 text-right">{{ setTitle(key.toString()) }}:</div> <div class="text-xl pt-3 text-right">{{ setTitle(key.toString()) }}:</div>
<div class="md:pt-4"> <div class="md:pt-4">
<label <label
@ -15,6 +15,7 @@
class="form-control w-full" class="form-control w-full"
:class="[typeof prop === 'boolean' && 'flex-row', name.toString() !== 'help_text' && 'mt-2']" :class="[typeof prop === 'boolean' && 'flex-row', name.toString() !== 'help_text' && 'mt-2']"
> >
<template v-if="name.toString() !== 'startInSec' && name.toString() !== 'lengthInSec'">
<div v-if="name.toString() !== 'help_text'" class="label"> <div v-if="name.toString() !== 'help_text'" class="label">
<span class="label-text !text-md font-bold">{{ name }}</span> <span class="label-text !text-md font-bold">{{ name }}</span>
</div> </div>
@ -27,7 +28,7 @@
type="password" type="password"
:placeholder="$t('config.placeholderPass')" :placeholder="$t('config.placeholderPass')"
class="input input-sm input-bordered w-full" class="input input-sm input-bordered w-full"
> />
<textarea <textarea
v-else-if="name.toString() === 'output_param' || name.toString() === 'custom_filter'" v-else-if="name.toString() === 'output_param' || name.toString() === 'custom_filter'"
v-model="item[name]" v-model="item[name]"
@ -39,7 +40,7 @@
v-model="item[name]" v-model="item[name]"
type="number" type="number"
class="input input-sm input-bordered w-full" class="input input-sm input-bordered w-full"
> />
<input <input
v-else-if="typeof prop === 'number'" v-else-if="typeof prop === 'number'"
v-model="item[name]" v-model="item[name]"
@ -47,31 +48,32 @@
class="input input-sm input-bordered w-full" class="input input-sm input-bordered w-full"
step="0.0001" step="0.0001"
style="max-width: 250px" style="max-width: 250px"
> />
<input <input
v-else-if="typeof prop === 'boolean'" v-else-if="typeof prop === 'boolean'"
v-model="item[name]" v-model="item[name]"
type="checkbox" type="checkbox"
class="checkbox checkbox-sm ms-2 mt-2" class="checkbox checkbox-sm ms-2 mt-2"
> />
<input <input
v-else-if="name === 'ignore_lines'" v-else-if="name === 'ignore_lines'"
v-model="formatIgnoreLines" v-model="formatIgnoreLines"
type="text" type="text"
class="input input-sm input-bordered w-full" class="input input-sm input-bordered w-full"
> />
<input <input
v-else v-else
:id="name" :id="name"
v-model="item[name]" v-model="item[name]"
type="text" type="text"
class="input input-sm input-bordered w-full" class="input input-sm input-bordered w-full"
> />
</template>
</label> </label>
</div> </div>
</template> </template>
<div class="mt-5 mb-10"> <div class="mt-5 mb-10">
<button class="btn btn-primary" type="submit">Save</button> <button class="btn btn-primary" type="submit">{{ $t('config.save') }}</button>
</div> </div>
</form> </form>
</div> </div>
@ -95,11 +97,11 @@ const showModal = ref(false)
const formatIgnoreLines = computed({ const formatIgnoreLines = computed({
get() { get() {
return configStore.configPlayout.logging.ignore_lines.join(';') return configStore.playout.logging.ignore_lines.join(';')
}, },
set(value) { set(value) {
configStore.configPlayout.logging.ignore_lines = value.split(';') configStore.playout.logging.ignore_lines = value.split(';')
}, },
}) })
@ -162,7 +164,7 @@ function setHelp(key: string, text: string): string {
} }
async function onSubmitPlayout() { async function onSubmitPlayout() {
const update = await configStore.setPlayoutConfig(configStore.configPlayout) const update = await configStore.setPlayoutConfig(configStore.playout)
if (update.status === 200) { if (update.status === 200) {
indexStore.msgAlert('success', 'Update playout config success!', 2) indexStore.msgAlert('success', 'Update playout config success!', 2)

View File

@ -3,8 +3,8 @@
<h2 class="pt-3 text-3xl">{{ $t('user.title') }}</h2> <h2 class="pt-3 text-3xl">{{ $t('user.title') }}</h2>
<div class="flex flex-col xs:flex-row gap-2 w-full mb-5 mt-10"> <div class="flex flex-col xs:flex-row gap-2 w-full mb-5 mt-10">
<div class="grow"> <div class="grow">
<select class="select select-bordered w-full max-w-xs" v-model="selected" @change="onChange($event)"> <select v-model="selected" class="select select-bordered w-full max-w-xs" @change="onChange($event)">
<option v-for="item in users">{{ item.username }}</option> <option v-for="item in users" :key="item.username">{{ item.username }}</option>
</select> </select>
</div> </div>
<div class="flex-none join"> <div class="flex-none join">
@ -22,9 +22,9 @@
<span class="label-text">{{ $t('user.name') }}</span> <span class="label-text">{{ $t('user.name') }}</span>
</div> </div>
<input <input
v-model="configStore.configUser.username"
type="text" type="text"
class="input input-bordered w-full !bg-base-100" class="input input-bordered w-full !bg-base-100"
v-model="configStore.configUser.username"
disabled disabled
/> />
</label> </label>
@ -33,33 +33,21 @@
<div class="label"> <div class="label">
<span class="label-text">{{ $t('user.mail') }}</span> <span class="label-text">{{ $t('user.mail') }}</span>
</div> </div>
<input <input v-model="configStore.configUser.mail" type="email" class="input input-bordered w-full" />
type="email"
class="input input-bordered w-full"
v-model="configStore.configUser.mail"
/>
</label> </label>
<label class="form-control w-full max-w-md mt-3"> <label class="form-control w-full max-w-md mt-3">
<div class="label"> <div class="label">
<span class="label-text">{{ $t('user.newPass') }}</span> <span class="label-text">{{ $t('user.newPass') }}</span>
</div> </div>
<input <input v-model="newPass" type="password" class="input input-bordered w-full" />
type="password"
class="input input-bordered w-full"
v-model="newPass"
/>
</label> </label>
<label class="form-control w-full max-w-md mt-3"> <label class="form-control w-full max-w-md mt-3">
<div class="label"> <div class="label">
<span class="label-text">{{ $t('user.confirmPass') }}</span> <span class="label-text">{{ $t('user.confirmPass') }}</span>
</div> </div>
<input <input v-model="confirmPass" type="password" class="input input-bordered w-full" />
type="password"
class="input input-bordered w-full"
v-model="confirmPass"
/>
</label> </label>
<div> <div>
@ -74,42 +62,34 @@
<div class="label"> <div class="label">
<span class="label-text">{{ $t('user.name') }}</span> <span class="label-text">{{ $t('user.name') }}</span>
</div> </div>
<input type="text" class="input input-bordered w-full" v-model="user.username" /> <input v-model="user.username" type="text" class="input input-bordered w-full" />
</label> </label>
<label class="form-control w-full mt-3"> <label class="form-control w-full mt-3">
<div class="label"> <div class="label">
<span class="label-text">{{ $t('user.mail') }}</span> <span class="label-text">{{ $t('user.mail') }}</span>
</div> </div>
<input type="email" class="input input-bordered w-full" v-model="user.mail" /> <input v-model="user.mail" type="email" class="input input-bordered w-full" />
</label> </label>
<label class="form-control w-full mt-3"> <label class="form-control w-full mt-3">
<div class="label"> <div class="label">
<span class="label-text">{{ $t('user.password') }}</span> <span class="label-text">{{ $t('user.password') }}</span>
</div> </div>
<input <input v-model="user.password" type="password" class="input input-bordered w-full" />
type="password"
class="input input-bordered w-full"
v-model="user.password"
/>
</label> </label>
<label class="form-control w-full mt-3"> <label class="form-control w-full mt-3">
<div class="label"> <div class="label">
<span class="label-text">{{ $t('user.confirmPass') }}</span> <span class="label-text">{{ $t('user.confirmPass') }}</span>
</div> </div>
<input <input v-model="user.confirm" type="password" class="input input-bordered w-full" />
type="password"
class="input input-bordered w-full"
v-model="user.confirm"
/>
</label> </label>
<div class="form-control mt-3"> <div class="form-control mt-3">
<label class="label cursor-pointer w-1/2"> <label class="label cursor-pointer w-1/2">
<span class="label-text">{{ $t('user.admin') }}</span> <span class="label-text">{{ $t('user.admin') }}</span>
<input type="checkbox" class="checkbox" v-model.number="user.admin" /> <input v-model.number="user.admin" type="checkbox" class="checkbox" />
</label> </label>
</div> </div>
</div> </div>
@ -243,7 +223,7 @@ async function addUser(add: boolean) {
} }
async function onSubmitUser() { async function onSubmitUser() {
if (newPass && newPass.value === confirmPass.value) { if (newPass.value && newPass.value === confirmPass.value) {
configStore.configUser.password = newPass.value configStore.configUser.password = newPass.value
} }

View File

@ -5,7 +5,7 @@
<div class="flex gap-2"> <div class="flex gap-2">
<div class="font-bold text-lg truncate flex-1 w-0">{{ title }}</div> <div class="font-bold text-lg truncate flex-1 w-0">{{ title }}</div>
<button v-if="hideButtons" class="btn btn-sm w-8 h-8 rounded-full" @click="modalAction(false)"> <button v-if="hideButtons" class="btn btn-sm w-8 h-8 rounded-full" @click="modalAction(false)">
<i class="bi bi-x-lg"/> <i class="bi bi-x-lg" />
</button> </button>
</div> </div>

View File

@ -1,11 +1,11 @@
<template> <template>
<div class="navbar bg-base-100 min-h-[52px] p-0 shadow"> <div class="navbar bg-base-100 min-h-[52px] p-0 shadow">
<NuxtLink class="navbar-brand min-w-[46px] p-2" href="/"> <NuxtLink class="navbar-brand min-w-[46px] p-2" href="/">
<img src="~/assets/images/ffplayout-small.png" class="img-fluid" alt="Logo" width="30" height="30" > <img src="~/assets/images/ffplayout-small.png" class="img-fluid" alt="Logo" width="30" height="30" />
</NuxtLink> </NuxtLink>
<div class="navbar-end w-1/5 grow"> <div class="navbar-end w-1/5 grow">
<label class="swap swap-rotate me-2 md:hidden"> <label class="swap swap-rotate me-2 md:hidden">
<input type="checkbox" :checked="indexStore.darkMode" @change="toggleDarkTheme" > <input type="checkbox" :checked="indexStore.darkMode" @change="toggleDarkTheme" />
<SvgIcon name="swap-on" classes="w-5 h-5" /> <SvgIcon name="swap-on" classes="w-5 h-5" />
<SvgIcon name="swap-off" classes="w-5 h-5" /> <SvgIcon name="swap-off" classes="w-5 h-5" />
</label> </label>
@ -81,7 +81,7 @@
</li> </li>
<li class="p-0"> <li class="p-0">
<label class="swap swap-rotate"> <label class="swap swap-rotate">
<input type="checkbox" :checked="indexStore.darkMode" @change="toggleDarkTheme" > <input type="checkbox" :checked="indexStore.darkMode" @change="toggleDarkTheme" />
<SvgIcon name="swap-on" classes="w-5 h-5" /> <SvgIcon name="swap-on" classes="w-5 h-5" />
<SvgIcon name="swap-off" classes="w-5 h-5" /> <SvgIcon name="swap-off" classes="w-5 h-5" />
</label> </label>

View File

@ -61,8 +61,10 @@
{{ filename(playlistStore.currentClip) }} {{ filename(playlistStore.currentClip) }}
</div> </div>
<div class="grow"> <div class="grow">
<strong>{{ $t('player.duration') }}:</strong> {{ secToHMS(playlistStore.currentClipDuration) }} | <strong>{{ $t('player.duration') }}:</strong>
<strong>{{ $t('player.in') }}:</strong> {{ secToHMS(playlistStore.currentClipIn) }} | <strong>{{ $t('player.out') }}:</strong> {{ secToHMS(playlistStore.currentClipDuration) }} |
<strong>{{ $t('player.in') }}:</strong> {{ secToHMS(playlistStore.currentClipIn) }} |
<strong>{{ $t('player.out') }}:</strong>
{{ secToHMS(playlistStore.currentClipOut) }} {{ secToHMS(playlistStore.currentClipOut) }}
</div> </div>
<div class="h-1/3"> <div class="h-1/3">

View File

@ -17,7 +17,7 @@
aria-label="Simple" aria-label="Simple"
checked checked
@change="advancedGenerator = false" @change="advancedGenerator = false"
> />
<div role="tabpanel" class="tab-content w-full pt-3"> <div role="tabpanel" class="tab-content w-full pt-3">
<div class="w-full"> <div class="w-full">
<div class="grid"> <div class="grid">
@ -78,7 +78,7 @@
) )
) )
" "
> />
</div> </div>
</div> </div>
</li> </li>
@ -93,7 +93,7 @@
class="tab" class="tab"
aria-label="Advanced" aria-label="Advanced"
@change=";(advancedGenerator = true), resetCheckboxes()" @change=";(advancedGenerator = true), resetCheckboxes()"
> />
<div role="tabpanel" class="tab-content pt-3"> <div role="tabpanel" class="tab-content pt-3">
<div class="w-full"> <div class="w-full">
<div class="grid grid-cols-[auto_48px] px-3 pt-0"> <div class="grid grid-cols-[auto_48px] px-3 pt-0">
@ -121,7 +121,7 @@
title="Add time block" title="Add time block"
@click="addTemplate()" @click="addTemplate()"
> >
<i class="bi bi-folder-plus"/> <i class="bi bi-folder-plus" />
</button> </button>
</div> </div>
</div> </div>
@ -180,7 +180,7 @@
v-model="item.start" v-model="item.start"
type="text" type="text"
class="input input-sm input-bordered join-item px-2 text-center" class="input input-sm input-bordered join-item px-2 text-center"
> />
<div <div
class="input input-sm input-bordered join-item px-2 text-center bg-base-200" class="input input-sm input-bordered join-item px-2 text-center bg-base-200"
> >
@ -190,7 +190,7 @@
v-model="item.duration" v-model="item.duration"
type="text" type="text"
class="input input-sm input-bordered join-item px-2 text-center" class="input input-sm input-bordered join-item px-2 text-center"
> />
<button <button
class="btn btn-sm input-bordered join-item" class="btn btn-sm input-bordered join-item"
:class="item.shuffle ? 'bg-base-100' : 'bg-base-300'" :class="item.shuffle ? 'bg-base-100' : 'bg-base-300'"
@ -247,7 +247,7 @@
type="checkbox" type="checkbox"
class="checkbox checkbox-xs rounded" class="checkbox checkbox-xs rounded"
@change="resetCheckboxes()" @change="resetCheckboxes()"
> />
</label> </label>
</div> </div>
<div class="join ms-2"> <div class="join ms-2">
@ -291,12 +291,14 @@ const advancedGenerator = ref(false)
const selectedFolders = ref([] as string[]) const selectedFolders = ref([] as string[])
const generateFromAll = ref(false) const generateFromAll = ref(false)
const template = ref({ const template = ref({
sources: [{ sources: [
start: configStore.configPlayout.playlist.day_start, {
start: configStore.playout.playlist.day_start,
duration: '02:00:00', duration: '02:00:00',
shuffle: false, shuffle: false,
paths: [], paths: [],
}], },
],
} as Template) } as Template)
const templateBrowserSortOptions = { const templateBrowserSortOptions = {
@ -331,12 +333,7 @@ async function generatePlaylist() {
body, body,
}) })
.then((response: any) => { .then((response: any) => {
playlistStore.playlist = processPlaylist( playlistStore.playlist = processPlaylist(playlistStore.listDate, response.program, false)
configStore.startInSec,
configStore.playlistLength,
response.program,
false
)
indexStore.msgAlert('success', 'Generate Playlist done...', 2) indexStore.msgAlert('success', 'Generate Playlist done...', 2)
}) })
.catch((e: any) => { .catch((e: any) => {
@ -379,7 +376,7 @@ function addFolderToTemplate(event: any, item: TemplateItem) {
event.item.remove() event.item.remove()
const storagePath = configStore.configPlayout.storage.path const storagePath = configStore.playout.storage.path
const navPath = mediaStore.folderCrumbs[mediaStore.folderCrumbs.length - 1].path const navPath = mediaStore.folderCrumbs[mediaStore.folderCrumbs.length - 1].path
const sourcePath = `${storagePath}/${navPath}/${mediaStore.folderList.folders[o].name}`.replace(/\/[/]+/g, '/') const sourcePath = `${storagePath}/${navPath}/${mediaStore.folderList.folders[o].name}`.replace(/\/[/]+/g, '/')
@ -404,7 +401,10 @@ function addTemplate() {
if (last) { if (last) {
const t = $dayjs(`2000-01-01T${last.duration}`) const t = $dayjs(`2000-01-01T${last.duration}`)
start = $dayjs(`2000-01-01T${last.start}`).add(t.hour(), 'hour').add(t.minute(), 'minute').add(t.second(), 'second') start = $dayjs(`2000-01-01T${last.start}`)
.add(t.hour(), 'hour')
.add(t.minute(), 'minute')
.add(t.second(), 'second')
} }
template.value.sources.push({ template.value.sources.push({

View File

@ -72,6 +72,7 @@
playlistStore.playoutIsRunning && playlistStore.playoutIsRunning &&
listDate === todayDate && listDate === todayDate &&
index === playlistStore.currentClipIndex, index === playlistStore.currentClipIndex,
'!bg-amber-600/40': element.overtime,
}" }"
> >
<td class="ps-4 py-2 text-left">{{ secondsToTime(element.begin) }}</td> <td class="ps-4 py-2 text-left">{{ secondsToTime(element.begin) }}</td>
@ -96,7 +97,7 @@
type="checkbox" type="checkbox"
:checked="element.category && element.category === 'advertisement' ? true : false" :checked="element.category && element.category === 'advertisement' ? true : false"
@change="setCategory($event, element)" @change="setCategory($event, element)"
> />
</td> </td>
<td class="py-2 text-center hover:text-base-content/70"> <td class="py-2 text-center hover:text-base-content/70">
<button @click="editItem(index)"> <button @click="editItem(index)">
@ -193,7 +194,7 @@ function addClip(event: any) {
event.item.remove() event.item.remove()
const storagePath = configStore.configPlayout.storage.path const storagePath = configStore.playout.storage.path
const sourcePath = `${storagePath}/${mediaStore.folderTree.source}/${mediaStore.folderTree.files[o].name}`.replace( const sourcePath = `${storagePath}/${mediaStore.folderTree.source}/${mediaStore.folderTree.files[o].name}`.replace(
/\/[/]+/g, /\/[/]+/g,
'/' '/'
@ -208,12 +209,7 @@ function addClip(event: any) {
duration: mediaStore.folderTree.files[o].duration, duration: mediaStore.folderTree.files[o].duration,
}) })
playlistStore.playlist = processPlaylist( playlistStore.playlist = processPlaylist(listDate.value, playlistStore.playlist, false)
configStore.startInSec,
configStore.playlistLength,
playlistStore.playlist,
false
)
nextTick(() => { nextTick(() => {
const newNode = document.getElementById(`clip-${n}`) const newNode = document.getElementById(`clip-${n}`)
@ -225,12 +221,7 @@ function addClip(event: any) {
function moveItemInArray(event: any) { function moveItemInArray(event: any) {
playlistStore.playlist.splice(event.newIndex, 0, playlistStore.playlist.splice(event.oldIndex, 1)[0]) playlistStore.playlist.splice(event.newIndex, 0, playlistStore.playlist.splice(event.oldIndex, 1)[0])
playlistStore.playlist = processPlaylist( playlistStore.playlist = processPlaylist(listDate.value, playlistStore.playlist, false)
configStore.startInSec,
configStore.playlistLength,
playlistStore.playlist,
false
)
removeBG(event.item) removeBG(event.item)
} }

View File

@ -3,7 +3,7 @@
<div class="p-4 bg-base-100"> <div class="p-4 bg-base-100">
<span class="text-3xl">{{ sysStat.system.name }} {{ sysStat.system.version }}</span> <span class="text-3xl">{{ sysStat.system.name }} {{ sysStat.system.version }}</span>
<span v-if="sysStat.system.kernel"> <span v-if="sysStat.system.kernel">
<br > <br />
{{ sysStat.system.kernel }} {{ sysStat.system.kernel }}
</span> </span>
</div> </div>
@ -16,8 +16,12 @@
<div class="p-4 border border-primary"> <div class="p-4 border border-primary">
<div class="text-xl">{{ $t('system.cpu') }}</div> <div class="text-xl">{{ $t('system.cpu') }}</div>
<div class="grid grid-cols-2"> <div class="grid grid-cols-2">
<div><strong>{{ $t('system.cores') }}:</strong> {{ sysStat.cpu.cores }}</div> <div>
<div><strong>{{ $t('system.usage') }}:</strong> {{ sysStat.cpu.usage.toFixed(2) }}%</div> <strong>{{ $t('system.cores') }}:</strong> {{ sysStat.cpu.cores }}
</div>
<div>
<strong>{{ $t('system.usage') }}:</strong> {{ sysStat.cpu.usage.toFixed(2) }}%
</div>
</div> </div>
</div> </div>
<div class="p-4 border border-primary"> <div class="p-4 border border-primary">
@ -31,15 +35,23 @@
<div class="p-4 border border-primary"> <div class="p-4 border border-primary">
<div class="text-xl">{{ $t('system.memory') }}</div> <div class="text-xl">{{ $t('system.memory') }}</div>
<div class="grid grid-cols-2"> <div class="grid grid-cols-2">
<div><strong>{{ $t('system.total') }}:</strong> {{ fileSize(sysStat.memory.total) }}</div> <div>
<div><strong>{{ $t('system.usage') }}:</strong> {{ fileSize(sysStat.memory.used) }}</div> <strong>{{ $t('system.total') }}:</strong> {{ fileSize(sysStat.memory.total) }}
</div>
<div>
<strong>{{ $t('system.usage') }}:</strong> {{ fileSize(sysStat.memory.used) }}
</div>
</div> </div>
</div> </div>
<div class="p-4 border border-primary"> <div class="p-4 border border-primary">
<div class="text-xl">{{ $t('system.swap') }}</div> <div class="text-xl">{{ $t('system.swap') }}</div>
<div class="grid grid-cols-2"> <div class="grid grid-cols-2">
<div><strong>{{ $t('system.total') }}:</strong> {{ fileSize(sysStat.swap.total) }}</div> <div>
<div><strong>{{ $t('system.usage') }}:</strong> {{ fileSize(sysStat.swap.used) }}</div> <strong>{{ $t('system.total') }}:</strong> {{ fileSize(sysStat.swap.total) }}
</div>
<div>
<strong>{{ $t('system.usage') }}:</strong> {{ fileSize(sysStat.swap.used) }}
</div>
</div> </div>
</div> </div>
<div class="p-4 border border-primary"> <div class="p-4 border border-primary">
@ -47,19 +59,29 @@
{{ $t('system.network') }} <span v-if="sysStat.network" class="fs-6">{{ sysStat.network?.name }}</span> {{ $t('system.network') }} <span v-if="sysStat.network" class="fs-6">{{ sysStat.network?.name }}</span>
</div> </div>
<div class="grid grid-cols-2"> <div class="grid grid-cols-2">
<div><strong>{{ $t('system.in') }}:</strong> {{ fileSize(sysStat.network?.current_in) }}</div> <div>
<div><strong>{{ $t('system.out') }}:</strong> {{ fileSize(sysStat.network?.current_out) }}</div> <strong>{{ $t('system.in') }}:</strong> {{ fileSize(sysStat.network?.current_in) }}
</div>
<div>
<strong>{{ $t('system.out') }}:</strong> {{ fileSize(sysStat.network?.current_out) }}
</div>
<div>{{ fileSize(sysStat.network?.total_in) }}</div> <div>{{ fileSize(sysStat.network?.total_in) }}</div>
<div>{{ fileSize(sysStat.network?.total_out) }}</div> <div>{{ fileSize(sysStat.network?.total_out) }}</div>
</div> </div>
</div> </div>
<div v-if="sysStat.storage?.path" class="p-4 border border-primary"> <div v-if="sysStat.storage?.path" class="p-4 border border-primary">
<div class="text-xl">{{ $t('system.storage') }}</div> <div class="text-xl">{{ $t('system.storage') }}</div>
<div v-if="sysStat.storage"><strong>{{ $t('system.device') }}:</strong> {{ sysStat.storage?.path }}</div> <div v-if="sysStat.storage">
<strong>{{ $t('system.device') }}:</strong> {{ sysStat.storage?.path }}
</div>
<div v-if="sysStat.storage" class="grid grid-cols-2"> <div v-if="sysStat.storage" class="grid grid-cols-2">
<div><strong>{{ $t('system.size') }}:</strong> {{ fileSize(sysStat.storage?.total) }}</div> <div>
<div><strong>{{ $t('system.used') }}:</strong> {{ fileSize(sysStat.storage?.used) }}</div> <strong>{{ $t('system.size') }}:</strong> {{ fileSize(sysStat.storage?.total) }}
</div>
<div>
<strong>{{ $t('system.used') }}:</strong> {{ fileSize(sysStat.storage?.used) }}
</div>
</div> </div>
</div> </div>
<div v-else class="col-6 bg-primary p-2 border" /> <div v-else class="col-6 bg-primary p-2 border" />

View File

@ -181,15 +181,22 @@ export const playlistOperations = () => {
return String(Date.now().toString(32) + Math.random().toString(16)).replace(/\./g, '') return String(Date.now().toString(32) + Math.random().toString(16)).replace(/\./g, '')
} }
function processPlaylist(dayStart: number, length: number, list: PlaylistItem[], forSave: boolean) { function processPlaylist(date: string, list: PlaylistItem[], forSave: boolean) {
if (!dayStart) { const configStore = useConfig()
dayStart = 0
} let begin = configStore.playout.playlist.startInSec
let begin = dayStart
const newList = [] const newList = []
for (const item of list) { for (const item of list) {
if (configStore.playout.playlist.startInSec === begin) {
if (!forSave) {
item.date = date
} else {
delete item.date
}
}
if (!item.uid) { if (!item.uid) {
item.uid = genUID() item.uid = genUID()
} }
@ -208,16 +215,15 @@ export const playlistOperations = () => {
delete item.custom_filter delete item.custom_filter
} }
if (begin + (item.out - item.in) > length + dayStart) { if (
item.class = 'overLength' begin >= configStore.playout.playlist.startInSec + configStore.playout.playlist.lengthInSec &&
!configStore.playout.playlist.infinit
) {
if (forSave) { if (forSave) {
item.out = length + dayStart - begin
}
}
if (forSave && begin >= length + dayStart) {
break break
} else {
item.overtime = true
}
} }
newList.push(item) newList.push(item)

View File

@ -13,8 +13,10 @@ export default withNuxt(
// } // }
{ {
rules: { rules: {
"@typescript-eslint/no-explicit-any": "off", '@typescript-eslint/no-explicit-any': 'off',
"vue/no-v-html": "off", 'no-control-regex': 'off',
'vue/html-self-closing': 'off',
'vue/no-v-html': 'off',
}, },
} }
) )

View File

@ -144,7 +144,8 @@ export default {
help: 'Hilfe', help: 'Hilfe',
generalText: `Manchmal kann es vorkommen, dass eine Datei beschädigt, aber noch abspielbar ist, was einen Streaming-Fehler bei allen folgenden Dateien verursachen kann. Die einzige Möglichkeit, dies zu beheben, besteht darin, ffplayout anzuhalten und neu zu starten. Wir sagen hier nur, wann es gestoppt werden muss, der Startvorgang ist Ihnen überlassen. Der beste Weg ist ein systemd-Dienst unter Linux. generalText: `Manchmal kann es vorkommen, dass eine Datei beschädigt, aber noch abspielbar ist, was einen Streaming-Fehler bei allen folgenden Dateien verursachen kann. Die einzige Möglichkeit, dies zu beheben, besteht darin, ffplayout anzuhalten und neu zu starten. Wir sagen hier nur, wann es gestoppt werden muss, der Startvorgang ist Ihnen überlassen. Der beste Weg ist ein systemd-Dienst unter Linux.
'stop_threshold' wird ffplayout stoppen, wenn es zeitlich über diesem Wert liegt. Eine Zahl kleiner als 3 kann zu unerwarteten Fehlern führen.`, 'stop_threshold' wird ffplayout stoppen, wenn es zeitlich über diesem Wert liegt. Eine Zahl kleiner als 3 kann zu unerwarteten Fehlern führen.`,
rpcText: 'Führe einen JSON-RPC-Server aus, um Informationen über die Wiedergabe und einige Kontrollfunktionen zu erhalten.', rpcText:
'Führe einen JSON-RPC-Server aus, um Informationen über die Wiedergabe und einige Kontrollfunktionen zu erhalten.',
mailText: `Senden Sie Fehlermeldungen an die E-Mail-Adresse, z. B. fehlende Playlist, ungültiges json-Format, fehlender Clip-Pfad. Lassen Sie den Empfänger leer, wenn Sie keine Benachrichtigung benötigen. 'mail_level' kann INFO, WARNING oder ERROR sein. 'interval' bedeutet Sekunden, bis eine neue E-Mail gesendet wird.`, mailText: `Senden Sie Fehlermeldungen an die E-Mail-Adresse, z. B. fehlende Playlist, ungültiges json-Format, fehlender Clip-Pfad. Lassen Sie den Empfänger leer, wenn Sie keine Benachrichtigung benötigen. 'mail_level' kann INFO, WARNING oder ERROR sein. 'interval' bedeutet Sekunden, bis eine neue E-Mail gesendet wird.`,
logText: `Wenn 'log_to_file' true ist, wird in eine Datei protokolliert, wenn false, in die Konsole. 'backup_count' gibt an, wie lange die Log-Dateien in Tagen gespeichert werden. 'local_time' auf false setzt die Log-Zeitstempel auf UTC. Pfad zu /var/log/ nur, wenn Sie dies als Daemon ausführen. logText: `Wenn 'log_to_file' true ist, wird in eine Datei protokolliert, wenn false, in die Konsole. 'backup_count' gibt an, wie lange die Log-Dateien in Tagen gespeichert werden. 'local_time' auf false setzt die Log-Zeitstempel auf UTC. Pfad zu /var/log/ nur, wenn Sie dies als Daemon ausführen.
'level' kann DEBUG, INFO, WARNING, ERROR sein. 'ffmpeg_level' kann INFO, WARNING, ERROR sein. 'detect_silence' protokolliert eine Fehlermeldung, wenn die Audiospur während des Validierungsprozesses 15 Sekunden lang still ist.`, 'level' kann DEBUG, INFO, WARNING, ERROR sein. 'ffmpeg_level' kann INFO, WARNING, ERROR sein. 'detect_silence' protokolliert eine Fehlermeldung, wenn die Audiospur während des Validierungsprozesses 15 Sekunden lang still ist.`,

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "ffplayout-frontend", "name": "ffplayout-frontend",
"version": "0.8.0", "version": "0.8.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ffplayout-frontend", "name": "ffplayout-frontend",
"version": "0.8.0", "version": "0.8.1",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@nuxtjs/color-mode": "^3.4.0", "@nuxtjs/color-mode": "^3.4.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "ffplayout-frontend", "name": "ffplayout-frontend",
"version": "0.8.0", "version": "0.8.1",
"description": "Web GUI for ffplayout", "description": "Web GUI for ffplayout",
"author": "Jonathan Baecker", "author": "Jonathan Baecker",
"private": true, "private": true,

View File

@ -38,7 +38,7 @@
</option> </option>
</select> </select>
<label class="join-item btn btn-primary swap swap-rotate me-2"> <label class="join-item btn btn-primary swap swap-rotate me-2">
<input type="checkbox" :checked="indexStore.darkMode" @change="toggleDarkTheme" > <input type="checkbox" :checked="indexStore.darkMode" @change="toggleDarkTheme" />
<SvgIcon name="swap-on" classes="w-5 h-5" /> <SvgIcon name="swap-on" classes="w-5 h-5" />
<SvgIcon name="swap-off" classes="w-5 h-5" /> <SvgIcon name="swap-off" classes="w-5 h-5" />
</label> </label>
@ -54,7 +54,7 @@
:placeholder="$t('input.username')" :placeholder="$t('input.username')"
class="input input-bordered w-full" class="input input-bordered w-full"
required required
> />
<input <input
v-model="formPassword" v-model="formPassword"
@ -62,7 +62,7 @@
:placeholder="$t('input.password')" :placeholder="$t('input.password')"
class="input input-bordered w-full mt-5" class="input input-bordered w-full mt-5"
required required
> />
<div class="w-full mt-4 grid grid-flow-row-dense grid-cols-12 grid-rows-1 gap-2"> <div class="w-full mt-4 grid grid-flow-row-dense grid-cols-12 grid-rows-1 gap-2">
<div class="col-span-3"> <div class="col-span-3">

View File

@ -205,7 +205,7 @@
> >
<div class="w-[1024px] max-w-full aspect-video"> <div class="w-[1024px] max-w-full aspect-video">
<VideoPlayer v-if="isVideo && previewOpt" reference="previewPlayer" :options="previewOpt" /> <VideoPlayer v-if="isVideo && previewOpt" reference="previewPlayer" :options="previewOpt" />
<img v-else :src="previewUrl" class="img-fluid" :alt="previewName" > <img v-else :src="previewUrl" class="img-fluid" :alt="previewName" />
</div> </div>
</GenericModal> </GenericModal>
@ -214,7 +214,7 @@
<div class="label"> <div class="label">
<span class="label-text">{{ $t('media.newFile') }}</span> <span class="label-text">{{ $t('media.newFile') }}</span>
</div> </div>
<input v-model="renameNewName" type="text" class="input input-bordered w-full" > <input v-model="renameNewName" type="text" class="input input-bordered w-full" />
</label> </label>
</GenericModal> </GenericModal>
@ -223,7 +223,7 @@
<div class="label"> <div class="label">
<span class="label-text">{{ $t('media.foldername') }}</span> <span class="label-text">{{ $t('media.foldername') }}</span>
</div> </div>
<input v-model="folderName.name" type="text" class="input input-bordered w-full" > <input v-model="folderName.name" type="text" class="input input-bordered w-full" />
</label> </label>
</GenericModal> </GenericModal>
@ -236,7 +236,7 @@
:accept="extensions" :accept="extensions"
multiple multiple
@change="onFileChange" @change="onFileChange"
> />
<label class="form-control w-full mt-3"> <label class="form-control w-full mt-3">
<div class="label"> <div class="label">
@ -257,7 +257,7 @@
<div class="label"> <div class="label">
<span class="label-text">{{ $t('media.uploading') }}:</span> <span class="label-text">{{ $t('media.uploading') }}:</span>
</div> </div>
<input v-model="uploadTask" type="text" class="input input-sm input-bordered w-full" disabled > <input v-model="uploadTask" type="text" class="input input-sm input-bordered w-full" disabled />
</label> </label>
</div> </div>
</GenericModal> </GenericModal>
@ -313,7 +313,7 @@ const lastPath = ref('')
const xhr = ref(new XMLHttpRequest()) const xhr = ref(new XMLHttpRequest())
onMounted(async () => { onMounted(async () => {
let config_extensions = configStore.configPlayout.storage.extensions let config_extensions = configStore.playout.storage.extensions
let extra_extensions = configStore.configGui[configStore.configID].extra_extensions let extra_extensions = configStore.configGui[configStore.configID].extra_extensions
if (typeof config_extensions === 'string') { if (typeof config_extensions === 'string') {
@ -428,7 +428,7 @@ function setPreviewData(path: string) {
? 'application/x-mpegURL' ? 'application/x-mpegURL'
: `video/${ext}` : `video/${ext}`
if (configStore.configPlayout.storage.extensions.includes(`${ext}`)) { if (configStore.playout.storage.extensions.includes(`${ext}`)) {
isVideo.value = true isVideo.value = true
previewOpt.value = { previewOpt.value = {
liveui: false, liveui: false,

View File

@ -58,7 +58,7 @@
type="text" type="text"
placeholder="X" placeholder="X"
required required
> />
</label> </label>
</div> </div>
@ -73,7 +73,7 @@
type="text" type="text"
placeholder="Y" placeholder="Y"
required required
> />
</label> </label>
</div> </div>
</div> </div>
@ -86,7 +86,7 @@
v-model="form.showBox" v-model="form.showBox"
type="checkbox" type="checkbox"
class="checkbox checkbox-xs rounded-sm" class="checkbox checkbox-xs rounded-sm"
> />
</label> </label>
</div> </div>
@ -99,7 +99,7 @@
type="color" type="color"
class="input input-sm input-bordered w-full p-1" class="input input-sm input-bordered w-full p-1"
required required
> />
</label> </label>
</div> </div>
<label class="form-control w-full xs:mt-[68px]"> <label class="form-control w-full xs:mt-[68px]">
@ -114,7 +114,7 @@
step="0.01" step="0.01"
class="input input-sm input-bordered w-full" class="input input-sm input-bordered w-full"
required required
> />
</label> </label>
</div> </div>
<div class="grid xs:grid-cols-[150px_150px_auto] gap-4 mt-2"> <div class="grid xs:grid-cols-[150px_150px_auto] gap-4 mt-2">
@ -128,7 +128,7 @@
type="number" type="number"
class="input input-sm input-bordered w-full" class="input input-sm input-bordered w-full"
required required
> />
</label> </label>
<label class="form-control w-full mt-2"> <label class="form-control w-full mt-2">
@ -140,7 +140,7 @@
type="color" type="color"
class="input input-sm input-bordered w-full p-1" class="input input-sm input-bordered w-full p-1"
required required
> />
</label> </label>
</div> </div>
<div> <div>
@ -153,7 +153,7 @@
type="number" type="number"
class="input input-sm input-bordered w-full" class="input input-sm input-bordered w-full"
required required
> />
</label> </label>
<label class="form-control w-full mt-2"> <label class="form-control w-full mt-2">
<div class="label"> <div class="label">
@ -167,7 +167,7 @@
max="1" max="1"
step="0.01" step="0.01"
required required
> />
</label> </label>
</div> </div>
@ -181,7 +181,7 @@
type="text" type="text"
class="input input-sm input-bordered w-full" class="input input-sm input-bordered w-full"
required required
> />
</label> </label>
<label class="form-control w-full xs:max-w-[150px] mt-2"> <label class="form-control w-full xs:max-w-[150px] mt-2">
<div class="label"> <div class="label">
@ -192,7 +192,7 @@
type="number" type="number"
class="input input-sm input-bordered w-full" class="input input-sm input-bordered w-full"
required required
> />
</label> </label>
</div> </div>
</div> </div>
@ -209,7 +209,7 @@
<div class="label"> <div class="label">
<span class="label-text">{{ $t('message.name') }}</span> <span class="label-text">{{ $t('message.name') }}</span>
</div> </div>
<input v-model="newPresetName" type="text" class="input input-bordered w-full" > <input v-model="newPresetName" type="text" class="input input-bordered w-full" />
</label> </label>
</GenericModal> </GenericModal>

View File

@ -44,7 +44,7 @@
<i class="bi-files" /> <i class="bi-files" />
</button> </button>
<button <button
v-if="!configStore.configPlayout.playlist.loop" v-if="!configStore.playout.playlist.loop"
class="btn btn-sm btn-primary join-item" class="btn btn-sm btn-primary join-item"
:title="$t('player.loop')" :title="$t('player.loop')"
@click="loopClips()" @click="loopClips()"
@ -72,7 +72,11 @@
> >
<i class="bi-sort-down-alt" /> <i class="bi-sort-down-alt" />
</button> </button>
<button class="btn btn-sm btn-primary join-item" :title="$t('player.reset')" @click=";(playlistStore.playlist.length = 0), getPlaylist()"> <button
class="btn btn-sm btn-primary join-item"
:title="$t('player.reset')"
@click=";(playlistStore.playlist.length = 0), getPlaylist()"
>
<i class="bi-arrow-counterclockwise" /> <i class="bi-arrow-counterclockwise" />
</button> </button>
<button <button
@ -99,7 +103,7 @@
> >
<div class="w-[1024px] max-w-full aspect-video"> <div class="w-[1024px] max-w-full aspect-video">
<VideoPlayer v-if="isVideo && previewOpt" reference="previewPlayer" :options="previewOpt" /> <VideoPlayer v-if="isVideo && previewOpt" reference="previewPlayer" :options="previewOpt" />
<img v-else :src="previewUrl" class="img-fluid" :alt="previewName" > <img v-else :src="previewUrl" class="img-fluid" :alt="previewName" />
</div> </div>
</GenericModal> </GenericModal>
@ -109,14 +113,14 @@
<div class="label"> <div class="label">
<span class="label-text">In</span> <span class="label-text">In</span>
</div> </div>
<input v-model.number="newSource.in" type="number" class="input input-sm input-bordered w-full" > <input v-model.number="newSource.in" type="number" class="input input-sm input-bordered w-full" />
</label> </label>
<label class="form-control w-full mt-3"> <label class="form-control w-full mt-3">
<div class="label"> <div class="label">
<span class="label-text">Out</span> <span class="label-text">Out</span>
</div> </div>
<input v-model.number="newSource.out" type="number" class="input input-sm input-bordered w-full" > <input v-model.number="newSource.out" type="number" class="input input-sm input-bordered w-full" />
</label> </label>
<label class="form-control w-full mt-3"> <label class="form-control w-full mt-3">
@ -127,34 +131,34 @@
v-model.number="newSource.duration" v-model.number="newSource.duration"
type="number" type="number"
class="input input-sm input-bordered w-full" class="input input-sm input-bordered w-full"
> />
</label> </label>
<label class="form-control w-full mt-3"> <label class="form-control w-full mt-3">
<div class="label"> <div class="label">
<span class="label-text">Source</span> <span class="label-text">Source</span>
</div> </div>
<input v-model="newSource.source" type="text" class="input input-sm input-bordered w-full" > <input v-model="newSource.source" type="text" class="input input-sm input-bordered w-full" />
</label> </label>
<label class="form-control w-full mt-3"> <label class="form-control w-full mt-3">
<div class="label"> <div class="label">
<span class="label-text">Audio</span> <span class="label-text">Audio</span>
</div> </div>
<input v-model="newSource.audio" type="text" class="input input-sm input-bordered w-full" > <input v-model="newSource.audio" type="text" class="input input-sm input-bordered w-full" />
</label> </label>
<label class="form-control w-full mt-3"> <label class="form-control w-full mt-3">
<div class="label"> <div class="label">
<span class="label-text">Custom Filter</span> <span class="label-text">Custom Filter</span>
</div> </div>
<input v-model="newSource.custom_filter" type="text" class="input input-sm input-bordered w-full" > <input v-model="newSource.custom_filter" type="text" class="input input-sm input-bordered w-full" />
</label> </label>
<div class="form-control"> <div class="form-control">
<label class="cursor-pointer label"> <label class="cursor-pointer label">
<span class="label-text">Advertisement</span> <span class="label-text">Advertisement</span>
<input type="checkbox" class="checkbox checkbox-sm" @click="isAd" > <input type="checkbox" class="checkbox checkbox-sm" @click="isAd" />
</label> </label>
</div> </div>
</div> </div>
@ -166,11 +170,11 @@
class="file-input file-input-sm file-input-bordered w-full" class="file-input file-input-sm file-input-bordered w-full"
multiple multiple
@change="onFileChange" @change="onFileChange"
> />
</GenericModal> </GenericModal>
<GenericModal :show="showCopyModal" :title="`Copy Program ${listDate}`" :modal-action="savePlaylist"> <GenericModal :show="showCopyModal" :title="`Copy Program ${listDate}`" :modal-action="savePlaylist">
<input v-model="targetDate" type="date" class="input input-sm input-bordered w-full" > <input v-model="targetDate" type="date" class="input input-sm input-bordered w-full" />
</GenericModal> </GenericModal>
<GenericModal :show="showDeleteModal" title="Delete Program" :modal-action="deletePlaylist"> <GenericModal :show="showDeleteModal" title="Delete Program" :modal-action="deletePlaylist">
@ -271,7 +275,7 @@ function closePlayer() {
function setPreviewData(path: string) { function setPreviewData(path: string) {
let fullPath = path let fullPath = path
const storagePath = configStore.configPlayout.storage.path const storagePath = configStore.playout.storage.path
const lastIndex = storagePath.lastIndexOf('/') const lastIndex = storagePath.lastIndexOf('/')
if (!path.includes('/')) { if (!path.includes('/')) {
@ -301,7 +305,7 @@ function setPreviewData(path: string) {
? 'application/x-mpegURL' ? 'application/x-mpegURL'
: `video/${ext}` : `video/${ext}`
if (configStore.configPlayout.storage.extensions.includes(`${ext}`)) { if (configStore.playout.storage.extensions.includes(`${ext}`)) {
isVideo.value = true isVideo.value = true
previewOpt.value = { previewOpt.value = {
liveui: false, liveui: false,
@ -327,20 +331,10 @@ function processSource(process: boolean) {
if (process) { if (process) {
if (editId.value === -1) { if (editId.value === -1) {
playlistStore.playlist.push(newSource.value) playlistStore.playlist.push(newSource.value)
playlistStore.playlist = processPlaylist( playlistStore.playlist = processPlaylist(listDate.value, playlistStore.playlist, false)
configStore.startInSec,
configStore.playlistLength,
playlistStore.playlist,
false
)
} else { } else {
playlistStore.playlist[editId.value] = newSource.value playlistStore.playlist[editId.value] = newSource.value
playlistStore.playlist = processPlaylist( playlistStore.playlist = processPlaylist(listDate.value, playlistStore.playlist, false)
configStore.startInSec,
configStore.playlistLength,
playlistStore.playlist,
false
)
} }
} }
@ -399,7 +393,7 @@ function loopClips() {
} }
} }
playlistStore.playlist = processPlaylist(configStore.startInSec, configStore.playlistLength, tempList, false) playlistStore.playlist = processPlaylist(listDate.value, tempList, false)
} }
function onFileChange(evt: any) { function onFileChange(evt: any) {
@ -455,12 +449,7 @@ async function savePlaylist(save: boolean) {
return return
} }
playlistStore.playlist = processPlaylist( playlistStore.playlist = processPlaylist(listDate.value, playlistStore.playlist, true)
configStore.startInSec,
configStore.playlistLength,
playlistStore.playlist,
true
)
const saveList = playlistStore.playlist.map(({ begin, ...item }) => item) const saveList = playlistStore.playlist.map(({ begin, ...item }) => item)
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/`, { await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/`, {

View File

@ -3,12 +3,12 @@ import type { LoDashStatic } from 'lodash'
declare module '#app' { declare module '#app' {
interface NuxtApp { interface NuxtApp {
$_: LoDashStatic; $_: LoDashStatic
} }
} }
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {
interface ComponentCustomProperties { interface ComponentCustomProperties {
$_: LoDashStatic; $_: LoDashStatic
} }
} }

View File

@ -1,4 +1,4 @@
import VueDatePicker from '@vuepic/vue-datepicker'; import VueDatePicker from '@vuepic/vue-datepicker'
import '@vuepic/vue-datepicker/dist/main.css' import '@vuepic/vue-datepicker/dist/main.css'
export default defineNuxtPlugin((nuxtApp) => { export default defineNuxtPlugin((nuxtApp) => {

View File

@ -10,9 +10,8 @@ export const useConfig = defineStore('config', {
contentType: { 'content-type': 'application/json;charset=UTF-8' }, contentType: { 'content-type': 'application/json;charset=UTF-8' },
configGui: [] as GuiConfig[], configGui: [] as GuiConfig[],
configGuiRaw: [] as GuiConfig[], configGuiRaw: [] as GuiConfig[],
startInSec: 0,
playlistLength: 86400.0, playlistLength: 86400.0,
configPlayout: {} as any, playout: {} as any,
currentUser: '', currentUser: '',
configUser: {} as User, configUser: {} as User,
utcOffset: 0, utcOffset: 0,
@ -41,7 +40,7 @@ export const useConfig = defineStore('config', {
method: 'GET', method: 'GET',
headers: authStore.authHeader, headers: authStore.authHeader,
}) })
.then(response => { .then((response) => {
statusCode = response.status statusCode = response.status
return response return response
@ -128,19 +127,14 @@ export const useConfig = defineStore('config', {
}) })
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
if (data.playlist.day_start) { data.playlist.startInSec = timeToSeconds(data.playlist.day_start ?? 0)
this.startInSec = timeToSeconds(data.playlist.day_start) data.playlist.lengthInSec = timeToSeconds(data.playlist.length ?? this.playlistLength)
}
if (data.playlist.length) {
this.playlistLength = timeToSeconds(data.playlist.length)
}
if (data.storage.extensions) { if (data.storage.extensions) {
data.storage.extensions = data.storage.extensions.join(',') data.storage.extensions = data.storage.extensions.join(',')
} }
this.configPlayout = data this.playout = data
}) })
.catch(() => { .catch(() => {
indexStore.msgAlert('error', 'No playout config found!', 3) indexStore.msgAlert('error', 'No playout config found!', 3)
@ -151,8 +145,9 @@ export const useConfig = defineStore('config', {
const authStore = useAuth() const authStore = useAuth()
const channel = this.configGui[this.configID].id const channel = this.configGui[this.configID].id
this.startInSec = timeToSeconds(obj.playlist.day_start)
this.playlistLength = timeToSeconds(obj.playlist.length) this.playlistLength = timeToSeconds(obj.playlist.length)
this.playout.playlist.startInSec = timeToSeconds(obj.playlist.day_start)
this.playout.playlist.lengthInSec = timeToSeconds(obj.playlist.length)
if (typeof obj.storage.extensions === 'string') { if (typeof obj.storage.extensions === 'string') {
obj.storage.extensions = obj.storage.extensions.replace(' ', '').split(/,|;/) obj.storage.extensions = obj.storage.extensions.replace(' ', '').split(/,|;/)

View File

@ -7,7 +7,6 @@ import { defineStore } from 'pinia'
dayjs.extend(utc) dayjs.extend(utc)
dayjs.extend(timezone) dayjs.extend(timezone)
// const { timeToSeconds } = stringFormatter()
const { processPlaylist } = playlistOperations() const { processPlaylist } = playlistOperations()
export const usePlaylist = defineStore('playlist', { export const usePlaylist = defineStore('playlist', {
@ -34,15 +33,8 @@ export const usePlaylist = defineStore('playlist', {
const authStore = useAuth() const authStore = useAuth()
const configStore = useConfig() const configStore = useConfig()
const indexStore = useIndex() const indexStore = useIndex()
let statusCode = 0
// const timeInSec = timeToSeconds(dayjs().utcOffset(configStore.utcOffset).format('HH:mm:ss'))
const channel = configStore.configGui[configStore.configID].id const channel = configStore.configGui[configStore.configID].id
// let dateToday = dayjs().utcOffset(configStore.utcOffset).format('YYYY-MM-DD') let statusCode = 0
// if (configStore.startInSec > timeInSec) {
// dateToday = dayjs(dateToday).utcOffset(configStore.utcOffset).subtract(1, 'day').format('YYYY-MM-DD')
// }
await fetch(`/api/playlist/${channel}?date=${date}`, { await fetch(`/api/playlist/${channel}?date=${date}`, {
method: 'GET', method: 'GET',
@ -55,32 +47,29 @@ export const usePlaylist = defineStore('playlist', {
}) })
.then((data) => { .then((data) => {
if (data.program) { if (data.program) {
const programData = processPlaylist( const programData = processPlaylist(date, data.program, false)
configStore.startInSec,
configStore.playlistLength,
data.program,
false
)
if ( if (
this.playlist.length > 0 && this.playlist.length > 0 &&
programData.length > 0 &&
this.playlist[0].date === date &&
$_.differenceWith(this.playlist, programData, (a, b) => { $_.differenceWith(this.playlist, programData, (a, b) => {
return $_.isEqual($_.omit(a, ['uid']), $_.omit(b, ['uid'])) return $_.isEqual($_.omit(a, ['uid']), $_.omit(b, ['uid']))
}).length > 0 }).length > 0
) { ) {
indexStore.msgAlert('warning', $i18n.t('player.unsavedProgram'), 3) indexStore.msgAlert('warning', $i18n.t('player.unsavedProgram'), 3)
} else if (this.playlist.length === 0) { } else {
this.playlist = programData this.playlist = programData ?? []
} }
} }
}) })
.catch((e) => { .catch((e) => {
if (statusCode > 0 && statusCode !== 204) { if (statusCode >= 400) {
indexStore.msgAlert('error', e, 3) indexStore.msgAlert('error', e, 3)
} } else if (this.playlist.length > 0 && this.playlist[0].date === date) {
if (this.playlist.length > 0) {
indexStore.msgAlert('warning', $i18n.t('player.unsavedProgram'), 3) indexStore.msgAlert('warning', $i18n.t('player.unsavedProgram'), 3)
} else {
this.playlist = []
} }
}) })
}, },

View File

@ -6,7 +6,7 @@ module.exports = {
}, },
boxShadow: { boxShadow: {
'3xl': '0 1em 5em rgba(0, 0, 0, 0.3)', '3xl': '0 1em 5em rgba(0, 0, 0, 0.3)',
'glow': '0 0 10px rgba(0, 0, 0, 0.3)', glow: '0 0 10px rgba(0, 0, 0, 0.3)',
}, },
colors: { colors: {
'my-gray': 'var(--my-gray)', 'my-gray': 'var(--my-gray)',

5
types/index.d.ts vendored
View File

@ -48,6 +48,7 @@ declare global {
} }
interface PlaylistItem { interface PlaylistItem {
date?: string
uid: string uid: string
begin: number begin: number
source: string source: string
@ -57,7 +58,7 @@ declare global {
audio?: string audio?: string
category?: string category?: string
custom_filter?: string custom_filter?: string
class?: string overtime?: boolean
} }
interface FileObject { interface FileObject {
@ -112,6 +113,6 @@ declare global {
network?: { name: string; current_in: number; current_out: number; total_in: number; total_out: number } network?: { name: string; current_in: number; current_out: number; total_in: number; total_out: number }
storage?: { path: string; total: number; used: number } storage?: { path: string; total: number; used: number }
swap: { total: number; used: number; free: number } swap: { total: number; used: number; free: number }
system: { name?: string; kernel?: string; version?: string, ffp_version?: string } system: { name?: string; kernel?: string; version?: string; ffp_version?: string }
} }
} }