complete translation, cleanup code
This commit is contained in:
parent
7682a45e4f
commit
c35cb6f178
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full max-w-[800px]">
|
<div class="w-full max-w-[800px]">
|
||||||
<h2 class="pt-3 text-3xl">Channel Configuration</h2>
|
<h2 class="pt-3 text-3xl">{{ $t('config.channelConf') }}</h2>
|
||||||
<div class="w-full flex justify-end my-4">
|
<div class="w-full flex justify-end my-4">
|
||||||
<button class="btn btn-sm btn-primary" @click="addChannel()">Add new Channel</button>
|
<button class="btn btn-sm btn-primary" @click="addChannel()">{{ $t('config.addChannel') }}</button>
|
||||||
</div>
|
</div>
|
||||||
<form
|
<form
|
||||||
v-if="configStore.configGui && configStore.configGui[configStore.configID]"
|
v-if="configStore.configGui && configStore.configGui[configStore.configID]"
|
||||||
@ -11,7 +11,7 @@
|
|||||||
>
|
>
|
||||||
<label class="form-control w-full">
|
<label class="form-control w-full">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Name</span>
|
<span class="label-text">{{ $t('config.name') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -23,11 +23,10 @@
|
|||||||
|
|
||||||
<label class="form-control w-full mt-5">
|
<label class="form-control w-full mt-5">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Preview URL</span>
|
<span class="label-text">{{ $t('config.previewUrl') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Type here"
|
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
v-model="configStore.configGui[configStore.configID].preview_url"
|
v-model="configStore.configGui[configStore.configID].preview_url"
|
||||||
/>
|
/>
|
||||||
@ -35,11 +34,10 @@
|
|||||||
|
|
||||||
<label class="form-control w-full mt-5">
|
<label class="form-control w-full mt-5">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Config Path</span>
|
<span class="label-text">{{ $t('config.configPath') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Type here"
|
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
v-model="configStore.configGui[configStore.configID].config_path"
|
v-model="configStore.configGui[configStore.configID].config_path"
|
||||||
/>
|
/>
|
||||||
@ -47,11 +45,10 @@
|
|||||||
|
|
||||||
<label class="form-control w-full mt-5">
|
<label class="form-control w-full mt-5">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Extra Extensions</span>
|
<span class="label-text">{{ $t('config.extensions') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Type here"
|
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
v-model="configStore.configGui[configStore.configID].extra_extensions"
|
v-model="configStore.configGui[configStore.configID].extra_extensions"
|
||||||
/>
|
/>
|
||||||
@ -59,11 +56,10 @@
|
|||||||
|
|
||||||
<label class="form-control w-full mt-5">
|
<label class="form-control w-full mt-5">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Service</span>
|
<span class="label-text">{{ $t('config.service') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Type here"
|
|
||||||
class="input input-bordered w-full !bg-base-100"
|
class="input input-bordered w-full !bg-base-100"
|
||||||
v-model="configStore.configGui[configStore.configID].service"
|
v-model="configStore.configGui[configStore.configID].service"
|
||||||
disabled
|
disabled
|
||||||
@ -71,13 +67,13 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="join my-4">
|
<div class="join my-4">
|
||||||
<button class="join-item btn btn-primary" type="submit">Save</button>
|
<button class="join-item btn btn-primary" type="submit">{{ $t('config.save') }}</button>
|
||||||
<button
|
<button
|
||||||
class="join-item btn btn-primary"
|
class="join-item btn btn-primary"
|
||||||
v-if="configStore.configGui.length > 1 && configStore.configGui[configStore.configID].id > 1"
|
v-if="configStore.configGui.length > 1 && configStore.configGui[configStore.configID].id > 1"
|
||||||
@click="deleteChannel()"
|
@click="deleteChannel()"
|
||||||
>
|
>
|
||||||
Delete
|
{{ $t('config.delete') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -86,6 +82,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { $_ } = useNuxtApp()
|
const { $_ } = useNuxtApp()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const authStore = useAuth()
|
const authStore = useAuth()
|
||||||
const configStore = useConfig()
|
const configStore = useConfig()
|
||||||
const indexStore = useIndex()
|
const indexStore = useIndex()
|
||||||
@ -114,9 +112,9 @@ async function onSubmitGui() {
|
|||||||
const update = await configStore.setGuiConfig(configStore.configGui[configStore.configID])
|
const update = await configStore.setGuiConfig(configStore.configGui[configStore.configID])
|
||||||
|
|
||||||
if (update.status) {
|
if (update.status) {
|
||||||
indexStore.msgAlert('success', 'Update GUI config success!', 2)
|
indexStore.msgAlert('success', t('config.updateChannelSuccess'), 2)
|
||||||
} else {
|
} else {
|
||||||
indexStore.msgAlert('error', 'Update GUI config failed!', 2)
|
indexStore.msgAlert('error', t('config.updateChannelFailed'), 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +123,7 @@ async function deleteChannel() {
|
|||||||
const id = config[configStore.configID].id
|
const id = config[configStore.configID].id
|
||||||
|
|
||||||
if (id === 1) {
|
if (id === 1) {
|
||||||
indexStore.msgAlert('warning', 'First channel can not be deleted!', 2)
|
indexStore.msgAlert('warning', t('config.errorChannelDelete'), 2)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,9 +138,9 @@ async function deleteChannel() {
|
|||||||
await configStore.getPlayoutConfig()
|
await configStore.getPlayoutConfig()
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
indexStore.msgAlert('success', 'Delete GUI config success!', 2)
|
indexStore.msgAlert('success', t('config.errorChannelDelete'), 2)
|
||||||
} else {
|
} else {
|
||||||
indexStore.msgAlert('error', 'Delete GUI config failed!', 2)
|
indexStore.msgAlert('error', t('config.deleteChannelFailed'), 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,27 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="max-w-[1200px] pe-8">
|
<div class="max-w-[1200px] pe-8">
|
||||||
<h2 class="pt-3 text-3xl">Playout Configuration</h2>
|
<h2 class="pt-3 text-3xl">{{ $t('config.playoutConf') }}</h2>
|
||||||
<form
|
<form
|
||||||
v-if="configStore.configPlayout"
|
v-if="configStore.configPlayout"
|
||||||
@submit.prevent="onSubmitPlayout"
|
@submit.prevent="onSubmitPlayout"
|
||||||
class="mt-10 grid md:grid-cols-[140px_auto] gap-4"
|
class="mt-10 grid md:grid-cols-[180px_auto] gap-5"
|
||||||
>
|
>
|
||||||
<template v-for="(item, key) in configStore.configPlayout" :key="key">
|
<template v-for="(item, key, _) in configStore.configPlayout" :key="key">
|
||||||
<div class="text-xl pt-3">{{ key }}:</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
|
||||||
v-for="(prop, name) in (item as Record<string, any>)"
|
v-for="(prop, name) in (item as Record<string, any>)"
|
||||||
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']"
|
||||||
>
|
>
|
||||||
<div 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>
|
||||||
<div v-if="name.toString() === 'help_text'">{{ prop }}</div>
|
<div v-if="name.toString() === 'help_text'" class="whitespace-pre-line">
|
||||||
|
{{ setHelp(key.toString(), prop) }}
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
v-else-if="name.toString() === 'sender_pass'"
|
v-else-if="name.toString() === 'sender_pass'"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Password"
|
:placeholder="$t('config.placeholderPass')"
|
||||||
class="input input-sm input-bordered w-full"
|
class="input input-sm input-bordered w-full"
|
||||||
v-model="item[name]"
|
v-model="item[name]"
|
||||||
/>
|
/>
|
||||||
@ -73,16 +75,21 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal title="Restart Playout" text="Restart ffplayout to apply changes?" :show="showModal" :modalAction="restart" />
|
<Modal
|
||||||
|
:title="$t('config.restartTile')"
|
||||||
|
:text="$t('config.restartText')"
|
||||||
|
:show="showModal"
|
||||||
|
:modalAction="restart"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const authStore = useAuth()
|
const authStore = useAuth()
|
||||||
const configStore = useConfig()
|
const configStore = useConfig()
|
||||||
const indexStore = useIndex()
|
const indexStore = useIndex()
|
||||||
|
|
||||||
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
|
|
||||||
|
|
||||||
const showModal = ref(false)
|
const showModal = ref(false)
|
||||||
|
|
||||||
const formatIgnoreLines = computed({
|
const formatIgnoreLines = computed({
|
||||||
@ -95,6 +102,64 @@ const formatIgnoreLines = computed({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function setTitle(input: string): String {
|
||||||
|
switch (input) {
|
||||||
|
case 'general':
|
||||||
|
return t('config.general')
|
||||||
|
case 'rpc_server':
|
||||||
|
return t('config.rpcServer')
|
||||||
|
case 'mail':
|
||||||
|
return t('config.mail')
|
||||||
|
case 'logging':
|
||||||
|
return t('config.logging')
|
||||||
|
case 'processing':
|
||||||
|
return t('config.processing')
|
||||||
|
case 'ingest':
|
||||||
|
return t('config.ingest')
|
||||||
|
case 'playlist':
|
||||||
|
return t('config.playlist')
|
||||||
|
case 'storage':
|
||||||
|
return t('config.storage')
|
||||||
|
case 'text':
|
||||||
|
return t('config.text')
|
||||||
|
case 'task':
|
||||||
|
return t('config.task')
|
||||||
|
case 'out':
|
||||||
|
return t('config.out')
|
||||||
|
default:
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setHelp(key: string, text: string): String {
|
||||||
|
switch (key) {
|
||||||
|
case 'general':
|
||||||
|
return t('config.generalText')
|
||||||
|
case 'rpc_server':
|
||||||
|
return t('config.rpcText')
|
||||||
|
case 'mail':
|
||||||
|
return t('config.mailText')
|
||||||
|
case 'logging':
|
||||||
|
return t('config.logText')
|
||||||
|
case 'processing':
|
||||||
|
return t('config.processingText')
|
||||||
|
case 'ingest':
|
||||||
|
return t('config.ingestText')
|
||||||
|
case 'playlist':
|
||||||
|
return t('config.playlistText')
|
||||||
|
case 'storage':
|
||||||
|
return t('config.storageText')
|
||||||
|
case 'text':
|
||||||
|
return t('config.textText')
|
||||||
|
case 'task':
|
||||||
|
return t('config.taskText')
|
||||||
|
case 'out':
|
||||||
|
return t('config.outText')
|
||||||
|
default:
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function onSubmitPlayout() {
|
async function onSubmitPlayout() {
|
||||||
const update = await configStore.setPlayoutConfig(configStore.configPlayout)
|
const update = await configStore.setPlayoutConfig(configStore.configPlayout)
|
||||||
|
|
||||||
@ -105,7 +170,7 @@ async function onSubmitPlayout() {
|
|||||||
|
|
||||||
await $fetch(`/api/control/${channel}/process/`, {
|
await $fetch(`/api/control/${channel}/process/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify({ command: 'status' }),
|
body: JSON.stringify({ command: 'status' }),
|
||||||
}).then((response: any) => {
|
}).then((response: any) => {
|
||||||
if (response === 'active') {
|
if (response === 'active') {
|
||||||
@ -123,7 +188,7 @@ async function restart(res: boolean) {
|
|||||||
|
|
||||||
await $fetch(`/api/control/${channel}/process/`, {
|
await $fetch(`/api/control/${channel}/process/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify({ command: 'restart' }),
|
body: JSON.stringify({ command: 'restart' }),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full max-w-[800px] pe-8">
|
<div class="w-full max-w-[800px] pe-8">
|
||||||
<h2 class="pt-3 text-3xl">User Configuration</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 class="select select-bordered w-full max-w-xs" v-model="selected" @change="onChange($event)">
|
||||||
@ -9,21 +9,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-none join">
|
<div class="flex-none join">
|
||||||
<button class="join-item btn btn-primary" title="Add new User" @click="showUserModal = true">
|
<button class="join-item btn btn-primary" title="Add new User" @click="showUserModal = true">
|
||||||
Add User
|
{{ $t('user.add') }}
|
||||||
</button>
|
</button>
|
||||||
<button class="join-item btn btn-primary" title="Delete selected user" @click="deleteUser()">
|
<button class="join-item btn btn-primary" title="Delete selected user" @click="deleteUser()">
|
||||||
Delete
|
{{ $t('user.delete') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form v-if="configStore.configUser" @submit.prevent="onSubmitUser">
|
<form v-if="configStore.configUser" @submit.prevent="onSubmitUser">
|
||||||
<label class="form-control w-full max-w-md">
|
<label class="form-control w-full max-w-md">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Username</span>
|
<span class="label-text">{{ $t('user.name') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Name"
|
|
||||||
class="input input-bordered w-full !bg-base-100"
|
class="input input-bordered w-full !bg-base-100"
|
||||||
v-model="configStore.configUser.username"
|
v-model="configStore.configUser.username"
|
||||||
disabled
|
disabled
|
||||||
@ -32,11 +31,10 @@
|
|||||||
|
|
||||||
<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">Mail</span>
|
<span class="label-text">{{ $t('user.mail') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="mail"
|
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
v-model="configStore.configUser.mail"
|
v-model="configStore.configUser.mail"
|
||||||
/>
|
/>
|
||||||
@ -44,11 +42,10 @@
|
|||||||
|
|
||||||
<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">New Password</span>
|
<span class="label-text">{{ $t('user.newPass') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="New password"
|
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
v-model="newPass"
|
v-model="newPass"
|
||||||
/>
|
/>
|
||||||
@ -56,18 +53,17 @@
|
|||||||
|
|
||||||
<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">Confirm Password</span>
|
<span class="label-text">{{ $t('user.confirmPass') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Confirm password"
|
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
v-model="confirmPass"
|
v-model="confirmPass"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button class="btn btn-primary mt-5" type="submit">Save</button>
|
<button class="btn btn-primary mt-5" type="submit">{{ $t('user.save') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -76,25 +72,24 @@
|
|||||||
<div class="w-full max-w-[500px] h-[420px]">
|
<div class="w-full max-w-[500px] h-[420px]">
|
||||||
<label class="form-control w-full">
|
<label class="form-control w-full">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Username</span>
|
<span class="label-text">{{ $t('user.name') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" placeholder="Name" class="input input-bordered w-full" v-model="user.username" />
|
<input type="text" class="input input-bordered w-full" v-model="user.username" />
|
||||||
</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">Mail</span>
|
<span class="label-text">{{ $t('user.mail') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="email" placeholder="Mail" class="input input-bordered w-full" v-model="user.mail" />
|
<input type="email" class="input input-bordered w-full" v-model="user.mail" />
|
||||||
</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">Password</span>
|
<span class="label-text">{{ $t('user.password') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Password"
|
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
v-model="user.password"
|
v-model="user.password"
|
||||||
/>
|
/>
|
||||||
@ -102,19 +97,18 @@
|
|||||||
|
|
||||||
<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">Confirm Password</span>
|
<span class="label-text">{{ $t('user.confirmPass') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Password"
|
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
v-model="user.confirm"
|
v-model="user.confirm"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="form-control mt-3">
|
<div class="form-control mt-3">
|
||||||
<label class="label cursor-pointer w-20">
|
<label class="label cursor-pointer w-1/2">
|
||||||
<span class="label-text">Admin</span>
|
<span class="label-text">{{ $t('user.admin') }}</span>
|
||||||
<input type="checkbox" class="checkbox" v-model.number="user.admin" />
|
<input type="checkbox" class="checkbox" v-model.number="user.admin" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -123,11 +117,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const authStore = useAuth()
|
const authStore = useAuth()
|
||||||
const configStore = useConfig()
|
const configStore = useConfig()
|
||||||
const indexStore = useIndex()
|
const indexStore = useIndex()
|
||||||
|
|
||||||
const selected = ref(null)
|
const selected = ref(null as null | string)
|
||||||
const users = ref([] as User[])
|
const users = ref([] as User[])
|
||||||
const showUserModal = ref(false)
|
const showUserModal = ref(false)
|
||||||
const newPass = ref('')
|
const newPass = ref('')
|
||||||
@ -154,6 +150,8 @@ async function getUsers() {
|
|||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
users.value = data
|
users.value = data
|
||||||
|
|
||||||
|
selected.value = configStore.currentUser
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,20 +181,20 @@ async function getUserConfig() {
|
|||||||
|
|
||||||
async function deleteUser() {
|
async function deleteUser() {
|
||||||
if (configStore.configUser.username === configStore.currentUser) {
|
if (configStore.configUser.username === configStore.currentUser) {
|
||||||
indexStore.msgAlert('error', 'Delete current user not possible!', 2)
|
indexStore.msgAlert('error', t('user.deleteNotPossible'), 2)
|
||||||
} else {
|
} else {
|
||||||
await fetch(`/api/user/${configStore.configUser.username}`, {
|
await fetch(`/api/user/${configStore.configUser.username}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: authStore.authHeader,
|
headers: authStore.authHeader,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
indexStore.msgAlert('success', 'Delete user done!', 2)
|
indexStore.msgAlert('success', t('user.deleteSuccess'), 2)
|
||||||
|
|
||||||
await configStore.getUserConfig()
|
await configStore.getUserConfig()
|
||||||
await getUsers()
|
await getUsers()
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
indexStore.msgAlert('error', `Delete user error: ${e}`, 2)
|
indexStore.msgAlert('error', `${t('user.deleteError')}: ${e}`, 2)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,17 +224,17 @@ async function addUser(add: boolean) {
|
|||||||
showUserModal.value = false
|
showUserModal.value = false
|
||||||
|
|
||||||
if (update.status === 200) {
|
if (update.status === 200) {
|
||||||
indexStore.msgAlert('success', 'Add user success!', 2)
|
indexStore.msgAlert('success', t('user.addSuccess'), 2)
|
||||||
|
|
||||||
await getUsers()
|
await getUsers()
|
||||||
await getUserConfig()
|
await getUserConfig()
|
||||||
} else {
|
} else {
|
||||||
indexStore.msgAlert('error', 'Add user failed!', 2)
|
indexStore.msgAlert('error', t('user.addFailed'), 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
clearUser()
|
clearUser()
|
||||||
} else {
|
} else {
|
||||||
indexStore.msgAlert('error', 'Password mismatch!', 2)
|
indexStore.msgAlert('error', t('user.mismatch'), 2)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showUserModal.value = false
|
showUserModal.value = false
|
||||||
@ -253,9 +251,9 @@ async function onSubmitUser() {
|
|||||||
const update = await configStore.setUserConfig(configStore.configUser)
|
const update = await configStore.setUserConfig(configStore.configUser)
|
||||||
|
|
||||||
if (update.status === 200) {
|
if (update.status === 200) {
|
||||||
indexStore.msgAlert('success', 'Update user profile success!', 2)
|
indexStore.msgAlert('success', t('user.updateSuccess'), 2)
|
||||||
} else {
|
} else {
|
||||||
indexStore.msgAlert('error', 'Update user profile failed!', 2)
|
indexStore.msgAlert('error', t('user.updateFailed'), 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
newPass.value = ''
|
newPass.value = ''
|
||||||
|
@ -162,7 +162,6 @@ const configStore = useConfig()
|
|||||||
const playlistStore = usePlaylist()
|
const playlistStore = usePlaylist()
|
||||||
const { filename, secToHMS, timeToSeconds } = stringFormatter()
|
const { filename, secToHMS, timeToSeconds } = stringFormatter()
|
||||||
const { configID } = storeToRefs(useConfig())
|
const { configID } = storeToRefs(useConfig())
|
||||||
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
|
|
||||||
|
|
||||||
playlistStore.currentClip = 'Es wird kein Clip abgespielt'
|
playlistStore.currentClip = 'Es wird kein Clip abgespielt'
|
||||||
const breakStatusCheck = ref(false)
|
const breakStatusCheck = ref(false)
|
||||||
@ -269,7 +268,7 @@ async function controlProcess(state: string) {
|
|||||||
|
|
||||||
await $fetch(`/api/control/${channel}/process/`, {
|
await $fetch(`/api/control/${channel}/process/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify({ command: state }),
|
body: JSON.stringify({ command: state }),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -289,7 +288,7 @@ async function controlPlayout(state: string) {
|
|||||||
|
|
||||||
await $fetch(`/api/control/${channel}/playout/`, {
|
await $fetch(`/api/control/${channel}/playout/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify({ control: state }),
|
body: JSON.stringify({ control: state }),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
<div class="inline-block">
|
<div class="inline-block">
|
||||||
<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)"><i class="bi bi-x-lg"></i></button>
|
<button v-if="hideButtons" class="btn btn-sm w-8 h-8 rounded-full" @click="modalAction(false)">
|
||||||
|
<i class="bi bi-x-lg"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grow mt-3">
|
<div class="grow mt-3">
|
||||||
@ -17,10 +19,10 @@
|
|||||||
<div v-if="!hideButtons" class="flex justify-end mt-3">
|
<div v-if="!hideButtons" class="flex justify-end mt-3">
|
||||||
<div class="join">
|
<div class="join">
|
||||||
<button class="btn btn-sm bg-base-300 hover:bg-base-300/50 join-item" @click="modalAction(false)">
|
<button class="btn btn-sm bg-base-300 hover:bg-base-300/50 join-item" @click="modalAction(false)">
|
||||||
Cancel
|
{{ $t('cancel') }}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm bg-base-300 hover:bg-base-300/50 join-item" @click="modalAction(true)">
|
<button class="btn btn-sm bg-base-300 hover:bg-base-300/50 join-item" @click="modalAction(true)">
|
||||||
Ok
|
{{ $t('ok') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -277,7 +277,6 @@ const mediaStore = useMedia()
|
|||||||
const playlistStore = usePlaylist()
|
const playlistStore = usePlaylist()
|
||||||
|
|
||||||
const { processPlaylist } = playlistOperations()
|
const { processPlaylist } = playlistOperations()
|
||||||
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
|
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
close: {
|
close: {
|
||||||
@ -328,7 +327,7 @@ async function generatePlaylist() {
|
|||||||
|
|
||||||
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/generate/${playlistStore.listDate}`, {
|
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/generate/${playlistStore.listDate}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
body,
|
body,
|
||||||
})
|
})
|
||||||
.then((response: any) => {
|
.then((response: any) => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="grid grid-cols-1 xs:grid-cols-2 border-4 rounded-md border-primary text-left shadow">
|
<div class="grid grid-cols-1 xs:grid-cols-2 border-4 rounded-md border-primary text-left shadow max-w-[756px]">
|
||||||
<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">
|
||||||
@ -55,7 +55,7 @@
|
|||||||
</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>Device:</strong> {{ sysStat.storage?.path }}</div>
|
<div v-if="sysStat.storage"><strong>{{ $t('system.device') }}:</strong> {{ sysStat.storage?.path }}</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2" v-if="sysStat.storage">
|
<div class="grid grid-cols-2" v-if="sysStat.storage">
|
||||||
<div><strong>{{ $t('system.size') }}:</strong> {{ fileSize(sysStat.storage?.total) }}</div>
|
<div><strong>{{ $t('system.size') }}:</strong> {{ fileSize(sysStat.storage?.total) }}</div>
|
||||||
@ -70,7 +70,6 @@ const { fileSize } = stringFormatter()
|
|||||||
|
|
||||||
const authStore = useAuth()
|
const authStore = useAuth()
|
||||||
const configStore = useConfig()
|
const configStore = useConfig()
|
||||||
const contentType = { 'content-type': 'application/json,charset=UTF-8' }
|
|
||||||
const timer = ref()
|
const timer = ref()
|
||||||
const sysStat = ref({
|
const sysStat = ref({
|
||||||
cpu: { cores: 0.0, usage: 0.0 },
|
cpu: { cores: 0.0, usage: 0.0 },
|
||||||
@ -112,7 +111,7 @@ async function systemStatus() {
|
|||||||
if (!document?.hidden) {
|
if (!document?.hidden) {
|
||||||
await $fetch<SystemStatistics>(`/api/system/${channel}`, {
|
await $fetch<SystemStatistics>(`/api/system/${channel}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
}).then((stat) => {
|
}).then((stat) => {
|
||||||
sysStat.value = stat
|
sysStat.value = stat
|
||||||
})
|
})
|
||||||
|
@ -45,10 +45,10 @@ export const stringFormatter = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function secToHMS(sec: number) {
|
function secToHMS(sec: number) {
|
||||||
let hours = Math.floor(sec / 3600)
|
const hours = Math.floor(sec / 3600)
|
||||||
sec %= 3600
|
sec %= 3600
|
||||||
let minutes = Math.floor(sec / 60)
|
const minutes = Math.floor(sec / 60)
|
||||||
let seconds = Math.round(sec % 60)
|
const seconds = Math.round(sec % 60)
|
||||||
|
|
||||||
const m = String(minutes).padStart(2, '0')
|
const m = String(minutes).padStart(2, '0')
|
||||||
const h = String(hours).padStart(2, '0')
|
const h = String(hours).padStart(2, '0')
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
|
ok: 'Ok',
|
||||||
|
cancel: 'Abbrechen',
|
||||||
alert: {
|
alert: {
|
||||||
wrongLogin: 'Falsche Anmeldedaten!',
|
wrongLogin: 'Falsche Anmeldedaten!',
|
||||||
},
|
},
|
||||||
@ -58,7 +60,7 @@ export default {
|
|||||||
edit: 'Bearbeiten',
|
edit: 'Bearbeiten',
|
||||||
delete: 'Löschen',
|
delete: 'Löschen',
|
||||||
copy: 'Wiedergabeliste kopieren',
|
copy: 'Wiedergabeliste kopieren',
|
||||||
loop: 'Clips in Wiedergabeliste wiederholen',
|
loop: 'Clips in {date} Wiedergabeliste wiederholen',
|
||||||
remote: 'Externe Quelle zur Wiedergabeliste hinzufügen',
|
remote: 'Externe Quelle zur Wiedergabeliste hinzufügen',
|
||||||
import: 'Text-/m3u-Datei importieren',
|
import: 'Text-/m3u-Datei importieren',
|
||||||
generate: 'Einfacher und erweiterter Wiedergabelisten-Generator',
|
generate: 'Einfacher und erweiterter Wiedergabelisten-Generator',
|
||||||
@ -89,4 +91,94 @@ export default {
|
|||||||
uploadError: 'Fehler beim Hochladen',
|
uploadError: 'Fehler beim Hochladen',
|
||||||
fileExists: 'Datei existiert bereits!',
|
fileExists: 'Datei existiert bereits!',
|
||||||
},
|
},
|
||||||
|
message: {
|
||||||
|
savePreset: 'Voreinstellung speichern',
|
||||||
|
newPreset: 'Neue Voreinstellung',
|
||||||
|
delPreset: 'Voreinstellung löschen',
|
||||||
|
delText: 'Sind Sie sicher, dass Sie die Voreinstellung löschen möchten',
|
||||||
|
placeholder: 'Nachricht',
|
||||||
|
xAxis: 'X-Achse',
|
||||||
|
yAxis: 'Y-Achse',
|
||||||
|
showBox: 'Box anzeigen',
|
||||||
|
boxColor: 'Boxfarbe',
|
||||||
|
boxAlpha: 'Box-Transparenz',
|
||||||
|
size: 'Größe',
|
||||||
|
spacing: 'Abstand',
|
||||||
|
overallAlpha: 'Gesamttransparenz',
|
||||||
|
fontColor: 'Schriftfarbe',
|
||||||
|
fontAlpha: 'Schrifttransparenz',
|
||||||
|
borderWidth: 'Rahmenbreite',
|
||||||
|
send: 'Senden',
|
||||||
|
name: 'Name',
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
channel: 'Kanal',
|
||||||
|
user: 'Benutzer',
|
||||||
|
channelConf: 'Kanal-Konfiguration',
|
||||||
|
addChannel: 'Neuen Kanal hinzufügen',
|
||||||
|
name: 'Name',
|
||||||
|
previewUrl: 'Vorschau-URL',
|
||||||
|
configPath: 'Konfigurationspfad',
|
||||||
|
extensions: 'Zusätzliche Erweiterungen',
|
||||||
|
service: 'Dienst',
|
||||||
|
save: 'Speichern',
|
||||||
|
delete: 'Löschen',
|
||||||
|
updateChannelSuccess: 'Kanal-Konfiguration erfolgreich aktualisiert!',
|
||||||
|
updateChannelFailed: 'Fehler beim Aktualisieren der Kanal-Konfiguration!',
|
||||||
|
errorChannelDelete: 'Der erste Kanal kann nicht gelöscht werden!',
|
||||||
|
deleteChannelSuccess: 'GUI-Konfiguration erfolgreich gelöscht!',
|
||||||
|
deleteChannelFailed: 'Fehler beim Löschen der GUI-Konfiguration!',
|
||||||
|
playoutConf: 'Playout-Konfiguration',
|
||||||
|
general: 'Allgemein',
|
||||||
|
rpcServer: 'RPC Server',
|
||||||
|
mail: 'EMail',
|
||||||
|
logging: 'Protokollierung',
|
||||||
|
processing: 'Verarbeitung',
|
||||||
|
ingest: 'Live-Eingang',
|
||||||
|
playlist: 'Wiedergabeliste',
|
||||||
|
storage: 'Speicher',
|
||||||
|
text: 'Text',
|
||||||
|
task: 'Aufgabe',
|
||||||
|
out: 'Ausgabe',
|
||||||
|
placeholderPass: 'Passwort',
|
||||||
|
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.
|
||||||
|
'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.',
|
||||||
|
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.
|
||||||
|
'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.`,
|
||||||
|
processingText: `Standardverarbeitung für alle Clips, um sie einheitlich zu machen. Modus kann "playlist" oder "folder" sein. 'aspect' muss eine Fließkommazahl sein. 'logo' wird nur verwendet, wenn der Pfad existiert.
|
||||||
|
'logo_scale' skaliert das Logo auf die Zielgröße, leer lassen, wenn keine Skalierung erforderlich ist, Format ist 'width:height', zum Beispiel '100:-1' für proportionale Skalierung. Mit 'logo_opacity' können Sie das Logo transparent machen.
|
||||||
|
Mit 'audio_tracks' kann man einstellen, wie viele Audiospuren verarbeitet werden sollen. 'audio_channels' kann verwendet werden, wenn das Audio mehr Kanäle als nur Stereo hat. Mit 'logo_position' im Format 'x:y' wird die Position des Logos festgelegt. Mit 'custom_filter' ist es möglich, zusätzliche Filter anzuwenden. Die Filterausgänge sollten mit [c_v_out] für Videofilter und [c_a_out] für Audiofilter enden.`,
|
||||||
|
ingestText: `Startet einen Server für einen Live-Eingangsstream. Dieser Stream hat Vorrang vor dem normalen Streaming, bis er fertig ist. Es gibt nur einen sehr einfachen Authentifizierungsmechanismus, um zu prüfen, ob der Streamname korrekt ist. 'custom_filter' kann auf die gleiche Weise wie im Abschnitt Verarbeitung verwendet werden.`,
|
||||||
|
playlistText: `'path' kann ein Pfad zu einer einzelnen Datei oder einem Verzeichnis sein. Bei Verzeichnissen geben Sie nur das Stammverzeichnis an, z. B. '/playlists', Unterverzeichnisse werden vom Program gelesen. Unterverzeichnisse benötigen diese Struktur '/playlists/2018/01'.
|
||||||
|
'day_start' ist die Uhrzeit, zu der die Wiedergabeliste beginnen soll; lassen Sie 'day_start' leer, wenn die Wiedergabeliste immer am Anfang beginnen soll. 'length' steht für die Ziellänge der Wiedergabeliste; wenn leer, wird die tatsächliche Länge nicht berücksichtigt. 'infinit: true' arbeitet mit einer einzelnen Wiedergabelistendatei und spielt diese in einer Endlosschleife.`,
|
||||||
|
storageText: `Spielt geordnete oder zufällige Dateien aus dem Pfad ab. 'filler_clip' ist zum Füllen des Endes, um 24 Stunden zu erreichen, es wird eine Schleife abgespielt, wenn nötig. Setzen Sie 'extensions', um nur nach Dateien mit dieser Erweiterung zu suchen. Setzen Sie 'shuffle' auf 'true', um Dateien zufällig auszuwählen.`,
|
||||||
|
textText: `Überlagernder Text in Kombination mit libzmq für die Remote-Textmanipulation. Unter Windows muss der Pfad der Schriftartdatei 'C\\:/WINDOWS/fonts/DejaVuSans.ttf' sein. 'text_from_filename' aktiviert die Textextraktion aus einem Dateinamen. Mit 'style' können Sie die Zeichentext-Parameter wie Position, Farbe, etc. festlegen. Post Text via API wird dies überschreiben. Mit 'regex' können Sie den Dateinamen formatieren, um einen Titel zu erhalten.`,
|
||||||
|
taskText: `Führt ein externes Programm mit einem bestimmten Medienobjekt aus. Das Medienobjekt liegt im json-Format vor und enthält alle Informationen über den aktuellen Clip. Das externe Programm kann ein Skript oder eine Binärdatei sein. oder eine Binärdatei, sollte aber nur für kurze Zeit laufen.`,
|
||||||
|
outText: `Die endgültige Playout-Kompression. Passen Sie die Einstellungen entsprechend Ihren Bedürfnissen an. 'mode' hat die Optionen 'desktop', 'hls', 'null', 'stream'. Verwenden Sie 'stream' und passen Sie die 'output_param:'-Einstellungen an, wenn Sie zu einem rtmp/rtsp/srt/... Server streamen wollen. In der Produktion sollten Sie die hls-Wiedergabeliste nicht mit ffpapi ausliefern, sondern nginx oder einen anderen Webserver verwenden!`,
|
||||||
|
restartTile: 'Playout neustarten',
|
||||||
|
restartText: 'ffplayout neustarten um Einstellungen anzuwenden?',
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
title: 'Benutzer-Konfiguration',
|
||||||
|
add: 'Benutzer hinzufügen',
|
||||||
|
delete: 'Löschen',
|
||||||
|
name: 'Benutzername',
|
||||||
|
mail: 'E-Mail',
|
||||||
|
password: 'Passwort',
|
||||||
|
newPass: 'Neues Passwort',
|
||||||
|
confirmPass: 'Passwort bestätigen',
|
||||||
|
save: 'Speichern',
|
||||||
|
admin: 'Administrator',
|
||||||
|
deleteNotPossible: 'Löschen des aktuellen Benutzers nicht möglich!',
|
||||||
|
deleteSuccess: 'Benutzer erfolgreich gelöscht!',
|
||||||
|
deleteError: 'Fehler beim Löschen des Benutzers',
|
||||||
|
addSuccess: 'Benutzer erfolgreich hinzugefügt!',
|
||||||
|
addFailed: 'Fehler beim Hinzufügen des Benutzers!',
|
||||||
|
mismatch: 'Passwort stimmt nicht überein!',
|
||||||
|
updateSuccess: 'Benutzerprofil erfolgreich aktualisiert!',
|
||||||
|
updateFailed: 'Fehler beim Aktualisieren des Benutzerprofils!',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
|
ok: 'Ok',
|
||||||
|
cancel: 'Cancel',
|
||||||
alert: {
|
alert: {
|
||||||
wrongLogin: 'Incorrect login data!',
|
wrongLogin: 'Incorrect login data!',
|
||||||
},
|
},
|
||||||
@ -89,4 +91,94 @@ export default {
|
|||||||
uploadError: 'Upload error',
|
uploadError: 'Upload error',
|
||||||
fileExists: 'File exists already!',
|
fileExists: 'File exists already!',
|
||||||
},
|
},
|
||||||
|
message: {
|
||||||
|
savePreset: 'Save Preset',
|
||||||
|
newPreset: 'New Preset',
|
||||||
|
delPreset: 'Delete Preset',
|
||||||
|
delText: 'Are you sure that you want to delete preset',
|
||||||
|
placeholder: 'Message',
|
||||||
|
xAxis: 'X Axis',
|
||||||
|
yAxis: 'Y Axis',
|
||||||
|
showBox: 'Show Box',
|
||||||
|
boxColor: 'Box Color',
|
||||||
|
boxAlpha: 'Box Alpha',
|
||||||
|
size: 'Size',
|
||||||
|
spacing: 'Spacing',
|
||||||
|
overallAlpha: 'Overall Alpha',
|
||||||
|
fontColor: 'Font Color',
|
||||||
|
fontAlpha: 'Font Alpha',
|
||||||
|
borderWidth: 'Border Width',
|
||||||
|
send: 'Send',
|
||||||
|
name: 'Name',
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
channel: 'Channel',
|
||||||
|
user: 'User',
|
||||||
|
channelConf: 'Channel Configuration',
|
||||||
|
addChannel: 'Add new Channel',
|
||||||
|
name: 'Name',
|
||||||
|
previewUrl: 'Preview URL',
|
||||||
|
configPath: 'Config Path',
|
||||||
|
extensions: 'Extra Extensions',
|
||||||
|
service: 'Service',
|
||||||
|
save: 'Save',
|
||||||
|
delete: 'Delete',
|
||||||
|
updateChannelSuccess: 'Update channel config success!',
|
||||||
|
updateChannelFailed: 'Update channel config failed!',
|
||||||
|
errorChannelDelete: 'First channel can not be deleted!',
|
||||||
|
deleteChannelSuccess: 'Delete GUI config success!',
|
||||||
|
deleteChannelFailed: 'Delete GUI config failed!',
|
||||||
|
playoutConf: 'Playout Configuration',
|
||||||
|
general: 'General',
|
||||||
|
rpcServer: 'RPC Server',
|
||||||
|
mail: 'Email',
|
||||||
|
logging: 'Logging',
|
||||||
|
processing: 'Processing',
|
||||||
|
ingest: 'Ingest',
|
||||||
|
playlist: 'Playlist',
|
||||||
|
storage: 'Storage',
|
||||||
|
text: 'Text',
|
||||||
|
task: 'Task',
|
||||||
|
out: 'Out',
|
||||||
|
placeholderPass: 'Password',
|
||||||
|
help: 'Help',
|
||||||
|
generalText: `Sometimes it can happen that a file is corrupted but still playable, this can cause a streaming error on all following files. The only way to fix this is to stop and restart ffplayout. Here we only say when to stop, the starting process is up to you. The best way is a systemd service on Linux.
|
||||||
|
'stop_threshold' will stop ffplayout if it is async in time above this value. A number less than 3 may cause unexpected errors.`,
|
||||||
|
rpcText: 'Run a JSON RPC server to get information about what is playing and for some control functions.',
|
||||||
|
mailText: `Send error messages to the email address, such as missing playlist; invalid json format; missing clip path. Leave the recipient blank if you don't need it. 'mail_level' can be INFO, WARNING or ERROR. 'interval' means seconds until a new mail is sent.`,
|
||||||
|
logText: `If 'log_to_file' is true, log to file, if false, log to console. 'backup_count' says how long log files will be saved in days. 'local_time' to false will set log timestamps to UTC. Path to /var/log/ only if you are running this as a daemon.
|
||||||
|
'level' can be DEBUG, INFO, WARNING, ERROR. 'ffmpeg_level' can be INFO, WARNING, ERROR. 'detect_silence' logs an error message if the audio line is silent for 15 seconds during the validation process.`,
|
||||||
|
processingText: `Default processing for all clips to make them unique. Mode can be Playlist or Folder. 'aspect' must be a float number. 'logo' is only used if the path exists.
|
||||||
|
'logo_scale' scales the logo to the target size, leave empty if no scaling is needed, format is 'width:height', for example '100:-1' for proportional scaling. With 'logo_opacity' you can make the logo transparent.
|
||||||
|
With 'audio_tracks' it is possible to configure how many audio tracks should be processed. 'audio_channels' can be used if the audio has more channels than just stereo. With 'logo_position' in 'x:y' format you set the logo position. With 'custom_filter' it is possible to apply additional filters. The filter outputs should end with [c_v_out] for video filters and [c_a_out] for audio filters.`,
|
||||||
|
ingestText: `Run a server for an ingest stream. This stream will override the normal streaming until it is done. There is only a very simple authentication mechanism to check if the stream name is correct. 'custom_filter' can be used in the same way as in the process section.`,
|
||||||
|
playlistText: `'path' can be a path to a single file or a directory. For directory specify only the root folder, for example '/playlists', subdirectories will be read by the program. Subdirectories need this structure '/playlists/2018/01'.
|
||||||
|
'day_start' is the time at which the playlist should start, leave 'day_start' empty if the playlist should always start at the beginning. 'length' represents the target length of the playlist, if empty, real length will not be considered. 'infinit: true' works with single playlist file and loops it infinitely.`,
|
||||||
|
storageText: `Play ordered or random files from path. 'filler_clip' is for filling the end to reach 24 hours, it will loop when necessary. Set 'extensions' to search only for files with that extension. Set 'shuffle' to 'true' to select files randomly.`,
|
||||||
|
textText: `Overlay text in combination with libzmq for remote text manipulation. On Windows, the font file path must be 'C\\:/WINDOWS/fonts/DejaVuSans.ttf'. 'text_from_filename' activates text extraction from a filename. With 'style' you can set the drawtext parameters like position, color, etc. Post Text via API will override this. With 'regex' you can format the filename to get a title.`,
|
||||||
|
taskText: `Run an external program with a given media object. The media object is in json format and contains all the information about the current clip. The external program can be a script or a binary. or a binary, but should only run for a short time.`,
|
||||||
|
outText: `The final playout compression. Adjust the settings according to your needs. 'mode' has the options 'desktop', 'hls', 'null', 'stream'. Use 'stream' and adjust the 'output_param:' settings if you want to stream to an rtmp/rtsp/srt/... server. In production don't serve hls playlist with ffpapi, use nginx or another web server!`,
|
||||||
|
restartTile: 'Restart Playout',
|
||||||
|
restartText: 'Restart ffplayout to apply changes?',
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
title: 'User Configuration',
|
||||||
|
add: 'Add User',
|
||||||
|
delete: 'Delete',
|
||||||
|
name: 'Username',
|
||||||
|
mail: 'Email',
|
||||||
|
password: 'Password',
|
||||||
|
newPass: 'New Password',
|
||||||
|
confirmPass: 'Confirm Password',
|
||||||
|
save: 'Save',
|
||||||
|
admin: 'Admin',
|
||||||
|
deleteNotPossible: 'Delete current user not possible!',
|
||||||
|
deleteSuccess: 'Delete user done!',
|
||||||
|
deleteError: 'Delete user error',
|
||||||
|
addSuccess: 'Add user success!',
|
||||||
|
addFailed: 'Add user failed!',
|
||||||
|
mismatch: 'Password mismatch!',
|
||||||
|
updateSuccess: 'Update user profile success!',
|
||||||
|
updateFailed: 'Update user profile failed!',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
:class="activeConf === 1 && 'btn-secondary'"
|
:class="activeConf === 1 && 'btn-secondary'"
|
||||||
@click="activeConf = 1"
|
@click="activeConf = 1"
|
||||||
>
|
>
|
||||||
GUI
|
{{ $t('config.channel') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="join-item w-full btn btn-sm btn-primary mt-1 duration-500"
|
class="join-item w-full btn btn-sm btn-primary mt-1 duration-500"
|
||||||
@ -20,7 +20,7 @@
|
|||||||
:class="activeConf === 3 && 'btn-secondary'"
|
:class="activeConf === 3 && 'btn-secondary'"
|
||||||
@click="activeConf = 3"
|
@click="activeConf = 3"
|
||||||
>
|
>
|
||||||
User
|
{{ $t('config.user') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-[calc(100%-70px)] mt-10 px-6">
|
<div class="w-[calc(100%-70px)] mt-10 px-6">
|
||||||
@ -40,10 +40,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Configuration | ffplayout',
|
title: `${t('button.configure')} | ffplayout`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const activeConf = ref(1)
|
const activeConf = ref(1)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full min-h-screen xs:h-full flex justify-center items-center">
|
<div class="w-full min-h-screen xs:h-full flex justify-center items-center">
|
||||||
<div v-if="authStore.isLogin" class="text-center w-full max-w-[800px] p-5">
|
<div v-if="authStore.isLogin" class="flex flex-wrap justify-center text-center w-full max-w-[1024px] p-5">
|
||||||
<SystemStats v-if="configStore.configGui.length > 0" />
|
<SystemStats v-if="configStore.configGui.length > 0" />
|
||||||
<div class="flex flex-wrap justify-center gap-1 md:gap-0 md:join mt-5">
|
<div class="flex flex-wrap justify-center gap-1 md:gap-0 md:join mt-5">
|
||||||
<NuxtLink :to="localePath({ name: 'player' })" class="btn join-item btn-primary px-2">
|
<NuxtLink :to="localePath({ name: 'player' })" class="btn join-item btn-primary px-2">
|
||||||
|
@ -25,10 +25,10 @@
|
|||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const { locale } = useI18n()
|
const { locale, t } = useI18n()
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Logging | ffplayout',
|
title: `${t('button.logging')} | ffplayout`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { configID } = storeToRefs(useConfig())
|
const { configID } = storeToRefs(useConfig())
|
||||||
|
@ -22,14 +22,17 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="relative h-[calc(100%-34px)] min-h-[300px] bg-base-100">
|
<div class="relative h-[calc(100%-34px)] min-h-[300px] bg-base-100">
|
||||||
<div
|
<div v-if="mediaStore.isLoading" class="w-full h-full absolute z-10 flex justify-center bg-base-100/70">
|
||||||
v-if="mediaStore.isLoading"
|
|
||||||
class="w-full h-full absolute z-10 flex justify-center bg-base-100/70"
|
|
||||||
>
|
|
||||||
<span class="loading loading-spinner loading-lg"></span>
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
</div>
|
</div>
|
||||||
<splitpanes :horizontal="horizontal" class="border border-my-gray rounded shadow">
|
<splitpanes :horizontal="horizontal" class="border border-my-gray rounded shadow">
|
||||||
<pane min-size="14" max-size="80" size="20" class="h-full pb-1 !bg-base-300" :class="horizontal ? 'rounded-t' : 'rounded-s'">
|
<pane
|
||||||
|
min-size="14"
|
||||||
|
max-size="80"
|
||||||
|
size="20"
|
||||||
|
class="h-full pb-1 !bg-base-300"
|
||||||
|
:class="horizontal ? 'rounded-t' : 'rounded-s'"
|
||||||
|
>
|
||||||
<ul v-if="mediaStore.folderTree.parent" class="overflow-auto h-full m-1" v-on:dragover.prevent>
|
<ul v-if="mediaStore.folderTree.parent" class="overflow-auto h-full m-1" v-on:dragover.prevent>
|
||||||
<li
|
<li
|
||||||
v-if="mediaStore.folderTree.parent_folders.length > 0"
|
v-if="mediaStore.folderTree.parent_folders.length > 0"
|
||||||
@ -158,10 +161,18 @@
|
|||||||
|
|
||||||
<div class="flex justify-end py-4 pe-2">
|
<div class="flex justify-end py-4 pe-2">
|
||||||
<div class="join">
|
<div class="join">
|
||||||
<button class="btn btn-sm btn-primary join-item" :title="$t('media.create')" @click="showCreateModal = true">
|
<button
|
||||||
|
class="btn btn-sm btn-primary join-item"
|
||||||
|
:title="$t('media.create')"
|
||||||
|
@click="showCreateModal = true"
|
||||||
|
>
|
||||||
<i class="bi-folder-plus" />
|
<i class="bi-folder-plus" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-primary join-item" :title="$t('media.upload')" @click="showUploadModal = true">
|
<button
|
||||||
|
class="btn btn-sm btn-primary join-item"
|
||||||
|
:title="$t('media.upload')"
|
||||||
|
@click="showUploadModal = true"
|
||||||
|
>
|
||||||
<i class="bi-upload" />
|
<i class="bi-upload" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -220,7 +231,9 @@
|
|||||||
|
|
||||||
<label class="form-control w-full mt-1">
|
<label class="form-control w-full mt-1">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">{{ $t('media.overall') }} ({{ currentNumber }}/{{ inputFiles.length }}):</span>
|
<span class="label-text"
|
||||||
|
>{{ $t('media.overall') }} ({{ currentNumber }}/{{ inputFiles.length }}):</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<progress class="progress progress-accent" :value="overallProgress" max="100" />
|
<progress class="progress progress-accent" :value="overallProgress" max="100" />
|
||||||
</label>
|
</label>
|
||||||
@ -244,12 +257,10 @@ const configStore = useConfig()
|
|||||||
const indexStore = useIndex()
|
const indexStore = useIndex()
|
||||||
const mediaStore = useMedia()
|
const mediaStore = useMedia()
|
||||||
const { toMin, mediaType, filename, parent } = stringFormatter()
|
const { toMin, mediaType, filename, parent } = stringFormatter()
|
||||||
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
|
|
||||||
|
|
||||||
const { configID } = storeToRefs(useConfig())
|
const { configID } = storeToRefs(useConfig())
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Media | ffplayout',
|
title: `${t('button.media')} | ffplayout`,
|
||||||
})
|
})
|
||||||
|
|
||||||
watch([width], () => {
|
watch([width], () => {
|
||||||
@ -361,11 +372,15 @@ async function handleDrop(event: any, targetFolder: any, isParent: boolean | nul
|
|||||||
if (source !== target) {
|
if (source !== target) {
|
||||||
await fetch(`/api/file/${configStore.configGui[configStore.configID].id}/rename/`, {
|
await fetch(`/api/file/${configStore.configGui[configStore.configID].id}/rename/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify({ source, target }),
|
body: JSON.stringify({ source, target }),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(async (res) => {
|
||||||
mediaStore.getTree(mediaStore.folderTree.source)
|
if (res.status >= 400) {
|
||||||
|
indexStore.msgAlert('error', await res.json(), 3)
|
||||||
|
} else {
|
||||||
|
mediaStore.getTree(mediaStore.folderTree.source)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
indexStore.msgAlert('error', `${t('media.moveError')}: ${e}`, 3)
|
indexStore.msgAlert('error', `${t('media.moveError')}: ${e}`, 3)
|
||||||
@ -425,7 +440,7 @@ async function deleteFileOrFolder(del: boolean) {
|
|||||||
if (del) {
|
if (del) {
|
||||||
await fetch(`/api/file/${configStore.configGui[configStore.configID].id}/remove/`, {
|
await fetch(`/api/file/${configStore.configGui[configStore.configID].id}/remove/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify({ source: deleteName.value }),
|
body: JSON.stringify({ source: deleteName.value }),
|
||||||
})
|
})
|
||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
@ -453,14 +468,18 @@ async function renameFile(ren: boolean) {
|
|||||||
*/
|
*/
|
||||||
showRenameModal.value = false
|
showRenameModal.value = false
|
||||||
|
|
||||||
if (ren) {
|
if (ren && renameOldName.value !== renameNewName.value) {
|
||||||
await fetch(`/api/file/${configStore.configGui[configStore.configID].id}/rename/`, {
|
await fetch(`/api/file/${configStore.configGui[configStore.configID].id}/rename/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify({ source: renameOldName.value, target: renameNewName.value }),
|
body: JSON.stringify({ source: renameOldName.value, target: renameNewName.value }),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(async (res) => {
|
||||||
mediaStore.getTree(mediaStore.folderTree.source)
|
if (res.status >= 400) {
|
||||||
|
indexStore.msgAlert('error', await res.text(), 3)
|
||||||
|
} else {
|
||||||
|
mediaStore.getTree(mediaStore.folderTree.source)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
indexStore.msgAlert('error', `${t('media.moveError')}: ${e}`, 3)
|
indexStore.msgAlert('error', `${t('media.moveError')}: ${e}`, 3)
|
||||||
@ -491,7 +510,7 @@ async function createFolder(create: boolean) {
|
|||||||
|
|
||||||
await $fetch(`/api/file/${configStore.configGui[configStore.configID].id}/create-folder/`, {
|
await $fetch(`/api/file/${configStore.configGui[configStore.configID].id}/create-folder/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify({ source: path }),
|
body: JSON.stringify({ source: path }),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -12,15 +12,23 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="join">
|
<div class="join">
|
||||||
<button class="btn btn-sm join-item btn-primary" title="Save Preset" @click="savePreset()">
|
<button
|
||||||
|
class="btn btn-sm join-item btn-primary"
|
||||||
|
:title="$t('message.savePreset')"
|
||||||
|
@click="savePreset()"
|
||||||
|
>
|
||||||
<i class="bi-cloud-upload" />
|
<i class="bi-cloud-upload" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm join-item btn-primary" title="New Preset" @click="showCreateModal = true">
|
<button
|
||||||
|
class="btn btn-sm join-item btn-primary"
|
||||||
|
:title="$t('message.newPreset')"
|
||||||
|
@click="showCreateModal = true"
|
||||||
|
>
|
||||||
<i class="bi-file-plus" />
|
<i class="bi-file-plus" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm join-item btn-primary"
|
class="btn btn-sm join-item btn-primary"
|
||||||
title="Delete Preset"
|
:title="$t('message.delPreset')"
|
||||||
@click="showDeleteModal = true"
|
@click="showDeleteModal = true"
|
||||||
>
|
>
|
||||||
<i class="bi-file-minus" />
|
<i class="bi-file-minus" />
|
||||||
@ -33,41 +41,53 @@
|
|||||||
class="textarea textarea-bordered w-full"
|
class="textarea textarea-bordered w-full"
|
||||||
v-model="form.text"
|
v-model="form.text"
|
||||||
rows="4"
|
rows="4"
|
||||||
placeholder="Message"
|
:placeholder="$t('message.placeholder')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="mt-5 grid xs:grid-cols-[auto_150px_150px] gap-4">
|
<div class="mt-2 grid xs:grid-cols-[auto_150px_150px] gap-4">
|
||||||
<div class="grow">
|
<div class="grow">
|
||||||
<input
|
<div class="form-control">
|
||||||
class="input input-sm input-bordered w-full"
|
<label class="cursor-pointer p-0">
|
||||||
v-model="form.x"
|
<div class="label">
|
||||||
type="text"
|
<span class="label-text">{{ $t('message.xAxis') }}</span>
|
||||||
title="X Axis"
|
</div>
|
||||||
placeholder="X"
|
<input
|
||||||
required
|
class="input input-sm input-bordered w-full"
|
||||||
/>
|
v-model="form.x"
|
||||||
<input
|
type="text"
|
||||||
class="input input-sm input-bordered w-full mt-6"
|
placeholder="X"
|
||||||
v-model="form.y"
|
required
|
||||||
type="text"
|
/>
|
||||||
title="Y Axis"
|
</label>
|
||||||
data-tooltip="tooltip"
|
</div>
|
||||||
placeholder="Y"
|
|
||||||
required
|
<div class="form-control">
|
||||||
/>
|
<label class="cursor-pointer p-0">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">{{ $t('message.yAxis') }}</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="input input-sm input-bordered w-full"
|
||||||
|
v-model="form.y"
|
||||||
|
type="text"
|
||||||
|
placeholder="Y"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="xs:mt-10">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label cursor-pointer p-0">
|
<label class="label cursor-pointer p-0">
|
||||||
<span class="label-text">Show Box</span>
|
<span class="label-text">{{ $t('message.showBox') }}</span>
|
||||||
<input type="checkbox" v-model="form.showBox" class="checkbox checkbox-xs rounded-sm" />
|
<input type="checkbox" v-model="form.showBox" class="checkbox checkbox-xs rounded-sm" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label class="form-control w-full">
|
<label class="mt-2 form-control w-full">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Box Color</span>
|
<span class="label-text">{{ $t('message.boxColor') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="color"
|
type="color"
|
||||||
@ -77,9 +97,9 @@
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<label class="form-control w-full mt-5">
|
<label class="form-control w-full xs:mt-[68px]">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Box Alpha</span>
|
<span class="label-text">{{ $t('message.boxAlpha') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@ -96,7 +116,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<label class="form-control w-full">
|
<label class="form-control w-full">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Size</span>
|
<span class="label-text">{{ $t('message.size') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@ -108,7 +128,7 @@
|
|||||||
|
|
||||||
<label class="form-control w-full mt-2">
|
<label class="form-control w-full mt-2">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Font Color</span>
|
<span class="label-text">{{ $t('message.fontColor') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="color"
|
type="color"
|
||||||
@ -121,7 +141,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<label class="form-control w-full">
|
<label class="form-control w-full">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Spacing</span>
|
<span class="label-text">{{ $t('message.spacing') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@ -132,7 +152,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<label class="form-control w-full mt-2">
|
<label class="form-control w-full mt-2">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Font Alpha</span>
|
<span class="label-text">{{ $t('message.fontAlpha') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@ -149,7 +169,7 @@
|
|||||||
<div class="grow">
|
<div class="grow">
|
||||||
<label class="form-control w-full">
|
<label class="form-control w-full">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Overall Alpha</span>
|
<span class="label-text">{{ $t('message.overallAlpha') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -160,7 +180,7 @@
|
|||||||
</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">
|
||||||
<span class="label-text">Border Width</span>
|
<span class="label-text">{{ $t('message.borderWidth') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@ -173,46 +193,25 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<button class="btn btn-primary send-btn" type="submit">Send</button>
|
<button class="btn btn-primary send-btn" type="submit">{{ $t('message.send') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<Modal :show="showCreateModal" :title="$t('message.newPreset')" :modalAction="createNewPreset">
|
||||||
v-if="showCreateModal"
|
<label class="form-control w-full">
|
||||||
class="z-50 fixed top-0 bottom-0 left-0 right-0 flex justify-center items-center bg-black/30"
|
<div class="label">
|
||||||
>
|
<span class="label-text">{{ $t('message.name') }}</span>
|
||||||
<div class="flex flex-col bg-base-100 w-[400px] h-[200px] mt-[10%] rounded-md p-5 shadow-xl">
|
|
||||||
<div class="font-bold text-lg">New Preset</div>
|
|
||||||
|
|
||||||
<label class="form-control w-full">
|
|
||||||
<div class="label">
|
|
||||||
<span class="label-text">Name</span>
|
|
||||||
</div>
|
|
||||||
<input type="text" class="input input-bordered w-full" v-model="newPresetName" />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="mt-4 flex justify-end">
|
|
||||||
<div class="join">
|
|
||||||
<button
|
|
||||||
class="btn btn-sm bg-base-300 hover:bg-base-300/50 join-item"
|
|
||||||
@click=";(newPresetName = ''), (showCreateModal = false)"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-sm bg-base-300 hover:bg-base-300/50 join-item" @click="createNewPreset()">
|
|
||||||
Ok
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<input type="text" class="input input-bordered w-full" v-model="newPresetName" />
|
||||||
</div>
|
</label>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
:show="showDeleteModal"
|
:show="showDeleteModal"
|
||||||
title="Delete Preset"
|
:title="$t('message.delPreset')"
|
||||||
:text="`Are you sure that you want to delete preset: <strong> ${selected}</strong>?`"
|
:text="`${$t('message.delText')}: <strong> ${selected}</strong>?`"
|
||||||
:modalAction="deletePreset"
|
:modalAction="deletePreset"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -222,7 +221,6 @@ const authStore = useAuth()
|
|||||||
const configStore = useConfig()
|
const configStore = useConfig()
|
||||||
const indexStore = useIndex()
|
const indexStore = useIndex()
|
||||||
const { numberToHex, hexToNumber } = stringFormatter()
|
const { numberToHex, hexToNumber } = stringFormatter()
|
||||||
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
|
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Messages | ffplayout',
|
title: 'Messages | ffplayout',
|
||||||
@ -347,7 +345,7 @@ async function savePreset() {
|
|||||||
|
|
||||||
const response = await fetch(`/api/presets/${form.value.id}`, {
|
const response = await fetch(`/api/presets/${form.value.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify(preset),
|
body: JSON.stringify(preset),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -359,42 +357,46 @@ async function savePreset() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createNewPreset() {
|
async function createNewPreset(create: boolean) {
|
||||||
showCreateModal.value = false
|
showCreateModal.value = false
|
||||||
|
|
||||||
const preset = {
|
if (create) {
|
||||||
name: newPresetName.value,
|
const preset = {
|
||||||
text: form.value.text,
|
name: newPresetName.value,
|
||||||
x: form.value.x.toString(),
|
text: form.value.text,
|
||||||
y: form.value.y.toString(),
|
x: form.value.x.toString(),
|
||||||
fontsize: form.value.fontSize.toString(),
|
y: form.value.y.toString(),
|
||||||
line_spacing: form.value.fontSpacing.toString(),
|
fontsize: form.value.fontSize.toString(),
|
||||||
fontcolor:
|
line_spacing: form.value.fontSpacing.toString(),
|
||||||
form.value.fontAlpha === 1
|
fontcolor:
|
||||||
? form.value.fontColor
|
form.value.fontAlpha === 1
|
||||||
: form.value.fontColor + '@' + numberToHex(form.value.fontAlpha),
|
? form.value.fontColor
|
||||||
box: form.value.showBox ? '1' : '0',
|
: form.value.fontColor + '@' + numberToHex(form.value.fontAlpha),
|
||||||
boxcolor:
|
box: form.value.showBox ? '1' : '0',
|
||||||
form.value.boxAlpha === 1
|
boxcolor:
|
||||||
? form.value.boxColor
|
form.value.boxAlpha === 1
|
||||||
: form.value.boxColor + '@' + numberToHex(form.value.boxAlpha),
|
? form.value.boxColor
|
||||||
boxborderw: form.value.border.toString(),
|
: form.value.boxColor + '@' + numberToHex(form.value.boxAlpha),
|
||||||
alpha: form.value.overallAlpha.toString(),
|
boxborderw: form.value.border.toString(),
|
||||||
channel_id: configStore.configGui[configStore.configID].id,
|
alpha: form.value.overallAlpha.toString(),
|
||||||
|
channel_id: configStore.configGui[configStore.configID].id,
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/presets/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
|
body: JSON.stringify(preset),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
indexStore.msgAlert('success', 'Save Preset done!', 2)
|
||||||
|
getPreset(-1)
|
||||||
|
} else {
|
||||||
|
indexStore.msgAlert('error', 'Save Preset failed!', 2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch('/api/presets/', {
|
newPresetName.value = ''
|
||||||
method: 'POST',
|
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
|
||||||
body: JSON.stringify(preset),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
|
||||||
indexStore.msgAlert('success', 'Save Preset done!', 2)
|
|
||||||
getPreset(-1)
|
|
||||||
} else {
|
|
||||||
indexStore.msgAlert('error', 'Save Preset failed!', 2)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deletePreset(del: boolean) {
|
async function deletePreset(del: boolean) {
|
||||||
@ -426,7 +428,7 @@ async function submitMessage() {
|
|||||||
|
|
||||||
const response = await fetch(`/api/control/${configStore.configGui[configStore.configID].id}/text/`, {
|
const response = await fetch(`/api/control/${configStore.configGui[configStore.configID].id}/text/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify(obj),
|
body: JSON.stringify(obj),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -187,12 +187,11 @@
|
|||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const { locale } = useI18n()
|
const { locale, t } = useI18n()
|
||||||
const { $_, $dayjs } = useNuxtApp()
|
const { $_, $dayjs } = useNuxtApp()
|
||||||
const { width } = useWindowSize({ initialWidth: 800 })
|
const { width } = useWindowSize({ initialWidth: 800 })
|
||||||
const { mediaType } = stringFormatter()
|
const { mediaType } = stringFormatter()
|
||||||
const { processPlaylist, genUID } = playlistOperations()
|
const { processPlaylist, genUID } = playlistOperations()
|
||||||
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
|
|
||||||
|
|
||||||
const authStore = useAuth()
|
const authStore = useAuth()
|
||||||
const configStore = useConfig()
|
const configStore = useConfig()
|
||||||
@ -201,7 +200,7 @@ const mediaStore = useMedia()
|
|||||||
const playlistStore = usePlaylist()
|
const playlistStore = usePlaylist()
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Player | ffplayout',
|
title: `${t('button.player')} | ffplayout`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { listDate } = storeToRefs(usePlaylist())
|
const { listDate } = storeToRefs(usePlaylist())
|
||||||
@ -279,9 +278,7 @@ function setPreviewData(path: string) {
|
|||||||
const parent = mediaStore.folderTree.parent ? mediaStore.folderTree.parent : ''
|
const parent = mediaStore.folderTree.parent ? mediaStore.folderTree.parent : ''
|
||||||
fullPath = `/${parent}/${mediaStore.folderTree.source}/${path}`.replace(/\/[/]+/g, '/')
|
fullPath = `/${parent}/${mediaStore.folderTree.source}/${path}`.replace(/\/[/]+/g, '/')
|
||||||
} else if (lastIndex !== -1) {
|
} else if (lastIndex !== -1) {
|
||||||
let pathPrefix = storagePath.substring(0, lastIndex)
|
fullPath = path.replace(storagePath.substring(0, lastIndex), '')
|
||||||
|
|
||||||
fullPath = path.replace(pathPrefix, '')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
previewName.value = fullPath.split('/').slice(-1)[0]
|
previewName.value = fullPath.split('/').slice(-1)[0]
|
||||||
@ -468,7 +465,7 @@ async function savePlaylist(save: boolean) {
|
|||||||
|
|
||||||
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/`, {
|
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
channel: configStore.configGui[configStore.configID].name,
|
channel: configStore.configGui[configStore.configID].name,
|
||||||
date: targetDate.value,
|
date: targetDate.value,
|
||||||
@ -494,7 +491,7 @@ async function deletePlaylist(del: boolean) {
|
|||||||
if (del) {
|
if (del) {
|
||||||
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/${listDate.value}`, {
|
await $fetch(`/api/playlist/${configStore.configGui[configStore.configID].id}/${listDate.value}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
playlistStore.playlist = []
|
playlistStore.playlist = []
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ export const useAuth = defineStore('auth', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
inspectToken() {
|
inspectToken() {
|
||||||
let token = useCookie('token').value
|
const token = useCookie('token').value
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
this.updateToken(token)
|
this.updateToken(token)
|
||||||
|
@ -22,14 +22,13 @@ export const useMedia = defineStore('media', {
|
|||||||
const authStore = useAuth()
|
const authStore = useAuth()
|
||||||
const configStore = useConfig()
|
const configStore = useConfig()
|
||||||
const indexStore = useIndex()
|
const indexStore = useIndex()
|
||||||
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
|
|
||||||
const channel = configStore.configGui[configStore.configID].id
|
const channel = configStore.configGui[configStore.configID].id
|
||||||
const crumbs: Crumb[] = []
|
const crumbs: Crumb[] = []
|
||||||
let root = '/'
|
let root = '/'
|
||||||
|
|
||||||
await fetch(`/api/file/${channel}/browse/`, {
|
await fetch(`/api/file/${channel}/browse/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...configStore.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify({ source: path, folders_only: foldersOnly }),
|
body: JSON.stringify({ source: path, folders_only: foldersOnly }),
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user