Merge pull request #810 from jb-alvarado/master
fix user permissions, remove signal term, scroll down on logs, scroll to item on playlist generation
This commit is contained in:
commit
4e6c33da02
49
Cargo.lock
generated
49
Cargo.lock
generated
@ -341,9 +341,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "actix-web-lab"
|
||||
version = "0.22.0"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a965e3e826aa4737af33666aa09ed949aa1837706fda2adee07039347be50d6"
|
||||
checksum = "ee75923689132fc5fb57ccc5bb98d25bb214796a29cd505844eb3b42daf11df0"
|
||||
dependencies = [
|
||||
"actix-http",
|
||||
"actix-router",
|
||||
@ -366,7 +366,6 @@ dependencies = [
|
||||
"local-channel",
|
||||
"mediatype",
|
||||
"mime",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"regex",
|
||||
"serde",
|
||||
@ -380,9 +379,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "actix-web-lab-derive"
|
||||
version = "0.22.0"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "008f98f5a68eeacf5e6d44ed74ce03c1b906baa53eabfb41faf0f5f40bd685f8"
|
||||
checksum = "4c221da13534b9352f3f79fcbbd6095f6d8aee63bdf1da8a73d36f9eeea17d5a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -720,9 +719,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.7.2"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
|
||||
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
|
||||
|
||||
[[package]]
|
||||
name = "bytestring"
|
||||
@ -1233,7 +1232,6 @@ dependencies = [
|
||||
"ffprobe",
|
||||
"flexi_logger",
|
||||
"futures-util",
|
||||
"home",
|
||||
"jsonwebtoken",
|
||||
"lazy_static",
|
||||
"lettre",
|
||||
@ -1259,7 +1257,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"shlex",
|
||||
"signal-child",
|
||||
"sqlx",
|
||||
"static-files",
|
||||
"sysinfo",
|
||||
@ -1326,9 +1323,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flexi_logger"
|
||||
version = "0.29.3"
|
||||
version = "0.29.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719236bdbcf6033a3395165f797076b31056018e6723ccff616eb25fc9c99de1"
|
||||
checksum = "0bc6a1594377eb9de4205e15e33e222c996de8dc047f7c998cc477030bfac48a"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"log",
|
||||
@ -2592,9 +2589,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.88"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -3011,18 +3008,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.210"
|
||||
version = "1.0.213"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||
checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.210"
|
||||
version = "1.0.213"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||
checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -3186,12 +3183,6 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-child"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3184fa464a0128cbcc353100ae752a848bc0067dd5715a50550f31570051150"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
@ -3715,18 +3706,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.64"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.64"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -3791,9 +3782,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.40.0"
|
||||
version = "1.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
|
||||
checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
|
@ -18,7 +18,7 @@ actix-multipart = "0.7"
|
||||
actix-web = "4"
|
||||
actix-web-grants = "4"
|
||||
actix-web-httpauth = "0.8"
|
||||
actix-web-lab = "0.22"
|
||||
actix-web-lab = "0.23"
|
||||
actix-web-static-files = "4.0"
|
||||
argon2 = "0.5"
|
||||
chrono = { version = "0.4", default-features = false, features = ["clock", "std", "serde"] }
|
||||
@ -29,7 +29,6 @@ faccess = "0.2"
|
||||
ffprobe = "0.4"
|
||||
flexi_logger = { version = "0.29", features = ["kv", "colors"] }
|
||||
futures-util = { version = "0.3", default-features = false, features = ["std"] }
|
||||
home = "0.5"
|
||||
jsonwebtoken = "9"
|
||||
lazy_static = "1.4"
|
||||
lettre = { version = "0.11", features = ["builder", "rustls-tls", "smtp-transport", "tokio1", "tokio1-rustls-tls"], default-features = false }
|
||||
@ -70,9 +69,6 @@ zeromq = { version = "0.4", default-features = false, features = [
|
||||
"tcp-transport",
|
||||
] }
|
||||
|
||||
[target.'cfg(not(target_arch = "windows"))'.dependencies]
|
||||
signal-child = "1"
|
||||
|
||||
[build-dependencies]
|
||||
static-files = "0.2"
|
||||
|
||||
|
@ -467,8 +467,7 @@ async fn get_all_channels(
|
||||
/// ```
|
||||
#[patch("/channel/{id}")]
|
||||
#[protect(
|
||||
"Role::GlobalAdmin",
|
||||
"Role::ChannelAdmin",
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin"),
|
||||
ty = "Role",
|
||||
expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
@ -984,10 +983,10 @@ pub async fn process_control(
|
||||
}
|
||||
ProcessCtl::Stop => {
|
||||
manager.channel.lock().unwrap().active = false;
|
||||
manager.async_stop().await;
|
||||
manager.async_stop().await?;
|
||||
}
|
||||
ProcessCtl::Restart => {
|
||||
manager.async_stop().await;
|
||||
manager.async_stop().await?;
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1500)).await;
|
||||
|
||||
if !manager.is_alive.load(Ordering::SeqCst) {
|
||||
|
@ -11,9 +11,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use signal_child::Signalable;
|
||||
|
||||
use actix_web::web;
|
||||
use log::*;
|
||||
use m3u8_rs::Playlist;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -26,7 +24,7 @@ use crate::player::{
|
||||
};
|
||||
use crate::utils::{
|
||||
config::{OutputMode::*, PlayoutConfig},
|
||||
errors::ProcessError,
|
||||
errors::{ProcessError, ServiceError},
|
||||
};
|
||||
use crate::ARGS;
|
||||
use crate::{
|
||||
@ -203,11 +201,6 @@ impl ChannelManager {
|
||||
match unit {
|
||||
Decoder => {
|
||||
if let Some(proc) = self.decoder.lock()?.as_mut() {
|
||||
#[cfg(not(windows))]
|
||||
proc.term()
|
||||
.map_err(|e| ProcessError::Custom(format!("Decoder: {e}")))?;
|
||||
|
||||
#[cfg(windows)]
|
||||
proc.kill()
|
||||
.map_err(|e| ProcessError::Custom(format!("Decoder: {e}")))?;
|
||||
}
|
||||
@ -258,36 +251,48 @@ impl ChannelManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn async_stop(&self) {
|
||||
pub async fn async_stop(&self) -> Result<(), ServiceError> {
|
||||
let channel_id = self.channel.lock().unwrap().id;
|
||||
|
||||
if self.is_alive.load(Ordering::SeqCst) {
|
||||
debug!(target: Target::all(), channel = channel_id; "Deactivate playout and stop all child processes from channel: <yellow>{channel_id}</>");
|
||||
}
|
||||
|
||||
self.is_terminated.store(true, Ordering::SeqCst);
|
||||
self.is_alive.store(false, Ordering::SeqCst);
|
||||
self.ingest_is_running.store(false, Ordering::SeqCst);
|
||||
self.run_count.fetch_sub(1, Ordering::SeqCst);
|
||||
let pool = self.db_pool.clone().unwrap();
|
||||
let channel_id = self.channel.lock().unwrap().id;
|
||||
debug!(target: Target::all(), channel = channel_id; "Deactivate playout and stop all child processes from channel: <yellow>{channel_id}</>");
|
||||
|
||||
if let Err(e) = handles::update_player(&pool, channel_id, false).await {
|
||||
error!(target: Target::all(), channel = channel_id; "Unable write to player status: {e}");
|
||||
};
|
||||
|
||||
for unit in [Decoder, Encoder, Ingest] {
|
||||
if let Err(e) = self.stop(unit) {
|
||||
let self_clone = self.clone();
|
||||
|
||||
if let Err(e) = web::block(move || self_clone.stop(unit)).await? {
|
||||
if !e.to_string().contains("exited process") {
|
||||
error!(target: Target::all(), channel = channel_id; "{e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// No matter what is running, terminate them all.
|
||||
pub fn stop_all(&self) {
|
||||
let channel_id = self.channel.lock().unwrap().id;
|
||||
|
||||
if self.is_alive.load(Ordering::SeqCst) {
|
||||
debug!(target: Target::all(), channel = channel_id; "Stop all child processes from channel: <yellow>{channel_id}</>");
|
||||
}
|
||||
|
||||
self.is_terminated.store(true, Ordering::SeqCst);
|
||||
self.is_alive.store(false, Ordering::SeqCst);
|
||||
self.ingest_is_running.store(false, Ordering::SeqCst);
|
||||
self.run_count.fetch_sub(1, Ordering::SeqCst);
|
||||
let channel_id = self.channel.lock().unwrap().id;
|
||||
debug!(target: Target::all(), channel = channel_id; "Stop all child processes from channel: <yellow>{channel_id}</>");
|
||||
|
||||
for unit in [Decoder, Encoder, Ingest] {
|
||||
if let Err(e) = self.stop(unit) {
|
||||
@ -400,6 +405,7 @@ fn find_m3u8_files(path: &Path) -> io::Result<Vec<String>> {
|
||||
Ok(m3u8_files)
|
||||
}
|
||||
|
||||
/// Check if segment is in playlist, if not, delete it.
|
||||
fn delete_old_segments<P: AsRef<Path> + Clone + std::fmt::Debug>(
|
||||
path: P,
|
||||
pl_segments: &[String],
|
||||
|
@ -15,8 +15,9 @@
|
||||
v-model="channel.name"
|
||||
type="text"
|
||||
placeholder="Type here"
|
||||
class="input input-bordered w-full"
|
||||
class="input input-bordered w-full !bg-base-100"
|
||||
@keyup="isChanged"
|
||||
:disabled="authStore.role === 'User'"
|
||||
/>
|
||||
</label>
|
||||
|
||||
@ -27,8 +28,9 @@
|
||||
<input
|
||||
v-model="channel.preview_url"
|
||||
type="text"
|
||||
class="input input-bordered w-full"
|
||||
class="input input-bordered w-full !bg-base-100"
|
||||
@keyup="isChanged"
|
||||
:disabled="authStore.role === 'User'"
|
||||
/>
|
||||
</label>
|
||||
|
||||
@ -39,8 +41,10 @@
|
||||
<input
|
||||
v-model="channel.extra_extensions"
|
||||
type="text"
|
||||
class="input input-bordered w-full"
|
||||
class="input input-bordered w-full !bg-base-100"
|
||||
:class="'input-disabled'"
|
||||
@keyup="isChanged"
|
||||
:disabled="authStore.role === 'User'"
|
||||
/>
|
||||
</label>
|
||||
|
||||
@ -88,7 +92,7 @@
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<div class="my-4 flex gap-1">
|
||||
<div v-if="authStore.role !== 'User'" class="my-4 flex gap-1">
|
||||
<button class="btn" :class="saved ? 'btn-primary' : 'btn-error'" @click="addUpdateChannel()">
|
||||
{{ t('config.save') }}
|
||||
</button>
|
||||
@ -218,7 +222,10 @@ async function addUpdateChannel() {
|
||||
await updateChannel()
|
||||
}
|
||||
|
||||
if (authStore.role === 'GlobalAdmin') {
|
||||
await configStore.getAdvancedConfig()
|
||||
}
|
||||
|
||||
await configStore.getPlayoutConfig()
|
||||
await configStore.getUserConfig()
|
||||
await mediaStore.getTree('')
|
||||
@ -242,8 +249,12 @@ async function deleteChannel() {
|
||||
})
|
||||
|
||||
i.value = configStore.i - 1
|
||||
await configStore.getChannelConfig()
|
||||
|
||||
if (authStore.role === 'GlobalAdmin') {
|
||||
await configStore.getAdvancedConfig()
|
||||
}
|
||||
|
||||
await configStore.getChannelConfig()
|
||||
await configStore.getPlayoutConfig()
|
||||
await configStore.getUserConfig()
|
||||
await mediaStore.getTree('')
|
||||
|
@ -705,6 +705,11 @@
|
||||
class="textarea textarea-bordered"
|
||||
rows="6"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="text-sm select-text text-base-content/80">
|
||||
{{ t('config.outputParam') }}
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-5 mb-10">
|
||||
|
@ -149,7 +149,9 @@ const user = ref({
|
||||
} as User)
|
||||
|
||||
onMounted(() => {
|
||||
if (authStore.role === 'GlobalAdmin') {
|
||||
getUsers()
|
||||
}
|
||||
})
|
||||
|
||||
async function getUsers() {
|
||||
|
@ -15,7 +15,14 @@
|
||||
<SvgIcon name="burger" classes="w-5 h-5" />
|
||||
</summary>
|
||||
<ul class="menu menu-sm dropdown-content mt-1 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li v-for="item in menuItems" :key="item.name" class="bg-base-100 rounded-md">
|
||||
<template v-for="item in menuItems" :key="item.name">
|
||||
<li
|
||||
v-if="
|
||||
item.label !== 'message' ||
|
||||
(configStore.playout.text.add_text && !configStore.playout.text.text_from_filename)
|
||||
"
|
||||
class="bg-base-100 rounded-md"
|
||||
>
|
||||
<NuxtLink
|
||||
:to="item.link"
|
||||
class="h-[27px] text-base"
|
||||
@ -27,6 +34,7 @@
|
||||
</span>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</template>
|
||||
<li v-if="configStore.channels.length > 1">
|
||||
<details tabindex="0" @focusout="closeDropdown">
|
||||
<summary>
|
||||
@ -37,7 +45,9 @@
|
||||
<ul class="p-2">
|
||||
<li v-for="(channel, index) in configStore.channels" :key="index">
|
||||
<span>
|
||||
<a class="dropdown-item cursor-pointer" @click="selectChannel(index)">{{ channel.name }}</a>
|
||||
<a class="dropdown-item cursor-pointer" @click="selectChannel(index)">{{
|
||||
channel.name
|
||||
}}</a>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
@ -53,7 +63,14 @@
|
||||
</div>
|
||||
<div class="navbar-end hidden 2sm:flex w-4/5 min-w-[750px]">
|
||||
<ul class="menu menu-sm menu-horizontal px-1">
|
||||
<li v-for="item in menuItems" :key="item.name" class="bg-base-100 rounded-md p-0">
|
||||
<template v-for="item in menuItems" :key="item.name">
|
||||
<li
|
||||
v-if="
|
||||
item.label !== 'message' ||
|
||||
(configStore.playout.text.add_text && !configStore.playout.text.text_from_filename)
|
||||
"
|
||||
class="bg-base-100 rounded-md p-0"
|
||||
>
|
||||
<NuxtLink
|
||||
:to="item.link"
|
||||
class="px-2 h-[27px] relative text-base text-base-content"
|
||||
@ -64,6 +81,8 @@
|
||||
</span>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<li v-if="configStore.channels.length > 1">
|
||||
<details tabindex="0" @focusout="closeDropdown">
|
||||
<summary>
|
||||
@ -112,12 +131,12 @@ const menuDropdown = ref()
|
||||
const isOpen = ref(false)
|
||||
|
||||
const menuItems = ref([
|
||||
{ name: t('button.home'), link: localePath({ name: 'index' }) },
|
||||
{ name: t('button.player'), link: localePath({ name: 'player' }) },
|
||||
{ name: t('button.media'), link: localePath({ name: 'media' }) },
|
||||
{ name: t('button.message'), link: localePath({ name: 'message' }) },
|
||||
{ name: t('button.logging'), link: localePath({ name: 'logging' }) },
|
||||
{ name: t('button.configure'), link: localePath({ name: 'configure' }) },
|
||||
{ label: 'index', name: t('button.home'), link: localePath({ name: 'index' }) },
|
||||
{ label: 'player', name: t('button.player'), link: localePath({ name: 'player' }) },
|
||||
{ label: 'media', name: t('button.media'), link: localePath({ name: 'media' }) },
|
||||
{ label: 'message', name: t('button.message'), link: localePath({ name: 'message' }) },
|
||||
{ label: 'logging', name: t('button.logging'), link: localePath({ name: 'logging' }) },
|
||||
{ label: 'configure', name: t('button.configure'), link: localePath({ name: 'configure' }) },
|
||||
])
|
||||
|
||||
if (colorMode.value === 'dark') {
|
||||
@ -162,7 +181,11 @@ function logout() {
|
||||
|
||||
function selectChannel(index: number) {
|
||||
configStore.i = index
|
||||
|
||||
if (authStore.role === 'GlobalAdmin') {
|
||||
configStore.getAdvancedConfig()
|
||||
}
|
||||
|
||||
configStore.getPlayoutConfig()
|
||||
}
|
||||
|
||||
|
@ -420,6 +420,7 @@ async function generatePlaylist() {
|
||||
resetCheckboxes()
|
||||
resetTemplate()
|
||||
|
||||
playlistStore.scrollToItem = true
|
||||
playlistStore.isLoading = false
|
||||
}
|
||||
</script>
|
||||
|
@ -140,7 +140,7 @@ const playlistContainer = ref()
|
||||
const sortContainer = ref()
|
||||
const todayDate = ref($dayjs().utcOffset(configStore.utcOffset).format('YYYY-MM-DD'))
|
||||
const { i } = storeToRefs(useConfig())
|
||||
const { currentIndex, listDate, playoutIsRunning } = storeToRefs(usePlaylist())
|
||||
const { currentIndex, listDate, playoutIsRunning, scrollToItem } = storeToRefs(usePlaylist())
|
||||
|
||||
const playlistSortOptions = {
|
||||
group: 'playlist',
|
||||
@ -175,10 +175,14 @@ watch([listDate, i], () => {
|
||||
}, 800)
|
||||
})
|
||||
|
||||
watch([playoutIsRunning], () => {
|
||||
watch([playoutIsRunning, scrollToItem], () => {
|
||||
if (playoutIsRunning.value || scrollToItem.value) {
|
||||
setTimeout(() => {
|
||||
scrollTo(currentIndex.value)
|
||||
|
||||
scrollToItem.value = false
|
||||
}, 400)
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
|
@ -1,8 +1,8 @@
|
||||
export default {
|
||||
ok: 'Ok',
|
||||
cancel: 'Abbrechen',
|
||||
socketConnected: 'Event Stream verbunden',
|
||||
socketDisconnected: 'Event Stream nicht verbunden',
|
||||
socketConnected: 'Message-Stream verbunden',
|
||||
socketDisconnected: 'Message-Stream nicht verbunden',
|
||||
alert: {
|
||||
wrongLogin: 'Falsche Anmeldedaten!',
|
||||
},
|
||||
@ -206,7 +206,7 @@ export default {
|
||||
playlistLength: 'Ziel-Länge der Playlist; wenn es leer ist, wird die reale Länge nicht berücksichtigt.',
|
||||
playlistInfinit: 'Eine einzelne Playlist-Datei endlos wiederholen.',
|
||||
storageHelp: 'Speichereinstellungen, die Standorte sind relativ zum Kanal-Speicher.',
|
||||
storageFiller: 'Verwende Füllmaterial, um anstelle einer fehlenden Datei abzuspielen oder um die verbleibende Zeit zu füllen, um eine Gesamtdauer von 24 Stunden zu erreichen. Es kann eine Datei oder ein Ordner sein und wird bei Bedarf wiederholt.',
|
||||
storageFiller: 'Verwenden Sie einen Platzhalter, um eine fehlende Datei abzuspielen oder um die verbleibende Zeit auf insgesamt 24 Stunden zu füllen. Es kann sich um eine Datei oder einen Ordner mit relativem Pfad handeln, der bei Bedarf wiederholt wird.',
|
||||
storageExtension: 'Gib an, welche Dateien gesucht und verwendet werden sollen.',
|
||||
storageShuffle: 'Wähle Dateien zufällig aus (im Ordner-Modus und bei der Playlist-Erstellung).',
|
||||
textHelp: 'Texteinblendung in Kombination mit libzmq für die Fernmanipulation von Text.',
|
||||
@ -217,6 +217,7 @@ export default {
|
||||
taskHelp: 'Führe ein externes Programm mit einem gegebenen Medienobjekt aus. Das Medienobjekt ist im JSON-Format und enthält alle Informationen über den aktuellen Clip. Das externe Programm kann ein Skript oder eine Binärdatei sein, sollte aber nur für kurze Zeit laufen.',
|
||||
taskPath: 'Pfad zur ausführbaren Datei.',
|
||||
outputHelp: `Die endgültige Playout-Codierung, passe die Einstellungen nach deinen Bedürfnissen an. Verwende den 'stream'-Modus und passe den 'Ausgabe-Parameter' an, wenn du zu einem RTMP/RTSP/SRT/...-Server streamen möchtest. Im Produktionsbetrieb verwende kein HLS mit ffplayout; nutze Nginx oder einen anderen Webserver!`,
|
||||
outputParam: 'HLS-Segment- und Playlist-Pfade sind relativ.',
|
||||
restartTile: 'Playout neustarten',
|
||||
restartText: 'ffplayout neustarten um Einstellungen anzuwenden?',
|
||||
updatePlayoutSuccess: 'Update der Playout-Konfiguration erfolgreich!',
|
||||
|
@ -1,8 +1,8 @@
|
||||
export default {
|
||||
ok: 'Ok',
|
||||
cancel: 'Cancel',
|
||||
socketConnected: 'Event stream connected',
|
||||
socketDisconnected: 'Event stream disconnected',
|
||||
socketConnected: 'Message stream connected',
|
||||
socketDisconnected: 'Message stream disconnected',
|
||||
alert: {
|
||||
wrongLogin: 'Incorrect login data!',
|
||||
},
|
||||
@ -206,7 +206,7 @@ export default {
|
||||
playlistLength: 'Target length of the playlist; when it is blank, the real length will not be considered.',
|
||||
playlistInfinit: 'Loop a single playlist file infinitely.',
|
||||
storageHelp: 'Storage settings, locations are relative to channel storage.',
|
||||
storageFiller: 'Use filler to play in place of a missing file or to fill the remaining time to reach a total of 24 hours. It can be a file or folder and will loop when necessary.',
|
||||
storageFiller: 'Use filler to play in place of a missing file or to fill the remaining time to reach a total of 24 hours. It can be a file or folder, with relative path, and will loop when necessary.',
|
||||
storageExtension: 'Specify which files to search and use.',
|
||||
storageShuffle: 'Pick files randomly (in folder mode and playlist generation).',
|
||||
textHelp: 'Overlay text in combination with libzmq for remote text manipulation.',
|
||||
@ -218,6 +218,7 @@ export default {
|
||||
taskPath: 'Path to executable.',
|
||||
outputHelp: `The final playout encoding, set the settings according to your needs. Use 'stream' mode and adjust the 'Output Parameter' when you want to stream to an RTMP/RTSP/SRT/... server.
|
||||
In production, don't serve HLS playlists with ffplayout; use Nginx or another web server!`,
|
||||
outputParam: 'HLS segment and playlist paths are relative.',
|
||||
restartTile: 'Restart Playout',
|
||||
restartText: 'Restart ffplayout to apply changes?',
|
||||
updatePlayoutSuccess: 'Update playout config success!',
|
||||
|
@ -206,7 +206,7 @@ export default {
|
||||
playlistLength: 'Duração alvo da playlist; quando estiver em branco, o comprimento real não será considerado.',
|
||||
playlistInfinit: 'Reproduza infinitamente um único arquivo de playlist.',
|
||||
storageHelp: 'Configurações de armazenamento, os locais são relativos ao armazenamento do canal.',
|
||||
storageFiller: 'Use preenchimento para tocar no lugar de um arquivo ausente ou para preencher o tempo restante para atingir um total de 24 horas. Pode ser um arquivo ou pasta e será repetido quando necessário.',
|
||||
storageFiller: 'Use um preenchimento para reproduzir no lugar de um arquivo ausente ou preencher o tempo restante para alcançar um total de 24 horas. Pode ser um arquivo ou uma pasta com caminho relativo, e será repetido quando necessário.',
|
||||
storageExtension: 'Especifique quais arquivos procurar e usar.',
|
||||
storageShuffle: 'Escolha arquivos aleatoriamente (no modo de pasta e geração de playlist).',
|
||||
textHelp: 'Sobrepor texto em combinação com libzmq para manipulação remota de texto.',
|
||||
@ -217,6 +217,7 @@ export default {
|
||||
taskHelp: 'Execute um programa externo com um objeto de mídia fornecido. O objeto de mídia está em formato JSON e contém todas as informações sobre o clipe atual. O programa externo pode ser um script ou binário, mas deve ser executado apenas por um curto período de tempo.',
|
||||
taskPath: 'Caminho para o executável.',
|
||||
outputHelp: `A codificação final do playout, ajuste as configurações de acordo com suas necessidades. Use o modo 'stream' e ajuste o 'Parâmetro de Saída' quando quiser fazer streaming para um servidor RTMP/RTSP/SRT/... No ambiente de produção, não sirva playlists HLS com ffplayout; use Nginx ou outro servidor web!`,
|
||||
outputParam: 'Os caminhos dos segmentos e playlists HLS são relativos.',
|
||||
restartTile: 'Reiniciar Playout',
|
||||
restartText: 'Reiniciar o ffplayout para aplicar as alterações?',
|
||||
updatePlayoutSuccess: 'Sucesso na atualização da configuração do playout!',
|
||||
|
@ -1,8 +1,8 @@
|
||||
export default {
|
||||
ok: 'ОК',
|
||||
cancel: 'Отмена',
|
||||
socketConnected: 'Event stream connected',
|
||||
socketDisconnected: 'Event stream disconnected',
|
||||
socketConnected: 'Message stream connected',
|
||||
socketDisconnected: 'Message stream disconnected',
|
||||
alert: {
|
||||
wrongLogin: 'Неверные данные для входа!',
|
||||
},
|
||||
@ -206,7 +206,7 @@ export default {
|
||||
playlistLength: 'Target length of the playlist; when it is blank, the real length will not be considered.',
|
||||
playlistInfinit: 'Loop a single playlist file infinitely.',
|
||||
storageHelp: 'Storage settings, locations are relative to channel storage.',
|
||||
storageFiller: 'Use filler to play in place of a missing file or to fill the remaining time to reach a total of 24 hours. It can be a file or folder and will loop when necessary.',
|
||||
storageFiller: 'Use filler to play in place of a missing file or to fill the remaining time to reach a total of 24 hours. It can be a file or folder, with relative path, and will loop when necessary.',
|
||||
storageExtension: 'Specify which files to search and use.',
|
||||
storageShuffle: 'Pick files randomly (in folder mode and playlist generation).',
|
||||
textHelp: 'Overlay text in combination with libzmq for remote text manipulation.',
|
||||
@ -218,6 +218,7 @@ export default {
|
||||
taskPath: 'Path to executable.',
|
||||
outputHelp: `The final playout encoding, set the settings according to your needs. Use 'stream' mode and adjust the 'Output Parameter' when you want to stream to an RTMP/RTSP/SRT/... server.
|
||||
In production, don't serve HLS playlists with ffplayout; use Nginx or another web server!`,
|
||||
outputParam: 'HLS segment and playlist paths are relative.',
|
||||
restartTile: 'Перезапуск Playout',
|
||||
restartText: 'Перезапустить ffplayout для применения изменений?',
|
||||
updatePlayoutSuccess: 'Обновление конфигурации воспроизведения прошло успешно!',
|
||||
|
@ -17,6 +17,7 @@
|
||||
Advanced
|
||||
</button>
|
||||
<button
|
||||
v-if="authStore.role !== 'User'"
|
||||
class="join-item btn btn-sm btn-primary mt-1 duration-500"
|
||||
:class="activeConf === 3 && 'btn-secondary'"
|
||||
@click="activeConf = 3"
|
||||
|
@ -12,17 +12,17 @@
|
||||
<NuxtLink :to="localePath({ name: 'media' })" class="btn join-item btn-primary px-2">
|
||||
{{ t('button.media') }}
|
||||
</NuxtLink>
|
||||
<NuxtLink :to="localePath({ name: 'message' })" class="btn join-item btn-primary px-2">
|
||||
<NuxtLink
|
||||
v-if="configStore.playout.text.add_text && !configStore.playout.text.text_from_filename"
|
||||
:to="localePath({ name: 'message' })"
|
||||
class="btn join-item btn-primary px-2"
|
||||
>
|
||||
{{ t('button.message') }}
|
||||
</NuxtLink>
|
||||
<NuxtLink :to="localePath({ name: 'logging' })" class="btn join-item btn-primary px-2">
|
||||
{{ t('button.logging') }}
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
v-if="authStore.role.toLowerCase().includes('admin')"
|
||||
:to="localePath({ name: 'configure' })"
|
||||
class="btn join-item btn-primary px-2"
|
||||
>
|
||||
<NuxtLink :to="localePath({ name: 'configure' })" class="btn join-item btn-primary px-2">
|
||||
{{ t('button.configure') }}
|
||||
</NuxtLink>
|
||||
<button class="btn join-item btn-primary px-2" @click="logout()">
|
||||
|
@ -34,10 +34,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-3 inline-block h-[calc(100vh-140px)] text-[13px]">
|
||||
<div
|
||||
class="bg-base-300 whitespace-pre h-full font-mono overflow-auto p-3"
|
||||
v-html="filterLogsBySeverity(formatLog(currentLog), errorLevel)"
|
||||
/>
|
||||
<div id="log-container" class="bg-base-300 whitespace-pre h-full font-mono overflow-auto p-3">
|
||||
<div id="log-content" v-html="filterLogsBySeverity(formatLog(currentLog), errorLevel)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -91,6 +90,15 @@ const calendarFormat = (date: Date) => {
|
||||
return $dayjs(date).locale(locale.value).format('ddd L')
|
||||
}
|
||||
|
||||
function scrollTo() {
|
||||
const parent = document.getElementById('log-container')
|
||||
const child = document.getElementById('log-content')
|
||||
|
||||
if (child && parent) {
|
||||
parent.scrollTop = child.scrollHeight
|
||||
}
|
||||
}
|
||||
|
||||
function filterLogsBySeverity(logString: string, minSeverity: string): string {
|
||||
const minLevel = indexStore.severityLevels[minSeverity]
|
||||
const logLines = logString.trim().split(/\r?\n/)
|
||||
@ -121,6 +129,10 @@ async function getLog() {
|
||||
.then((response) => response.text())
|
||||
.then((data) => {
|
||||
currentLog.value = data
|
||||
|
||||
nextTick(() => {
|
||||
scrollTo()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
currentLog.value = ''
|
||||
|
@ -330,14 +330,6 @@ useHead({
|
||||
title: `${t('button.media')} | ffplayout`,
|
||||
})
|
||||
|
||||
watch([width], () => {
|
||||
if (width.value < 640) {
|
||||
horizontal.value = true
|
||||
} else {
|
||||
horizontal.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const horizontal = ref(false)
|
||||
const deleteName = ref('')
|
||||
const recursive = ref(false)
|
||||
@ -384,6 +376,14 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
watch([width], () => {
|
||||
if (width.value < 640) {
|
||||
horizontal.value = true
|
||||
} else {
|
||||
horizontal.value = false
|
||||
}
|
||||
})
|
||||
|
||||
watch([i], () => {
|
||||
mediaStore.getTree('')
|
||||
})
|
||||
|
@ -32,7 +32,7 @@ export const useConfig = defineStore('config', {
|
||||
await this.getPlayoutConfig()
|
||||
await this.getUserConfig()
|
||||
|
||||
if (this.configUser.id === 1) {
|
||||
if (authStore.role === 'GlobalAdmin') {
|
||||
await this.getAdvancedConfig()
|
||||
}
|
||||
})
|
||||
|
@ -24,6 +24,7 @@ export const usePlaylist = defineStore('playlist', {
|
||||
playoutIsRunning: false,
|
||||
last_channel: 0,
|
||||
firstLoad: true,
|
||||
scrollToItem: false,
|
||||
}),
|
||||
|
||||
getters: {},
|
||||
|
Loading…
Reference in New Issue
Block a user