diff --git a/Cargo.lock b/Cargo.lock index 10433b92..42e76d1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/engine/Cargo.toml b/engine/Cargo.toml index c00355e4..d43362e2 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -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" diff --git a/engine/src/api/routes.rs b/engine/src/api/routes.rs index 978f87eb..0f70b115 100644 --- a/engine/src/api/routes.rs +++ b/engine/src/api/routes.rs @@ -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) { diff --git a/engine/src/player/controller.rs b/engine/src/player/controller.rs index 146ccb48..04d5ad06 100644 --- a/engine/src/player/controller.rs +++ b/engine/src/player/controller.rs @@ -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: {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: {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: {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: {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> { Ok(m3u8_files) } +/// Check if segment is in playlist, if not, delete it. fn delete_old_segments + Clone + std::fmt::Debug>( path: P, pl_segments: &[String], diff --git a/frontend/components/ConfigChannel.vue b/frontend/components/ConfigChannel.vue index ef806ae1..654e4073 100644 --- a/frontend/components/ConfigChannel.vue +++ b/frontend/components/ConfigChannel.vue @@ -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'" /> @@ -27,8 +28,9 @@ @@ -39,8 +41,10 @@ @@ -88,7 +92,7 @@ -
+
@@ -218,7 +222,10 @@ async function addUpdateChannel() { await updateChannel() } - await configStore.getAdvancedConfig() + 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 + + if (authStore.role === 'GlobalAdmin') { + await configStore.getAdvancedConfig() + } + await configStore.getChannelConfig() - await configStore.getAdvancedConfig() await configStore.getPlayoutConfig() await configStore.getUserConfig() await mediaStore.getTree('') diff --git a/frontend/components/ConfigPlayout.vue b/frontend/components/ConfigPlayout.vue index c55ed05d..cf003283 100644 --- a/frontend/components/ConfigPlayout.vue +++ b/frontend/components/ConfigPlayout.vue @@ -705,6 +705,11 @@ class="textarea textarea-bordered" rows="6" /> +
+ + {{ t('config.outputParam') }} + +
diff --git a/frontend/components/ConfigUser.vue b/frontend/components/ConfigUser.vue index dbbce716..8d902830 100644 --- a/frontend/components/ConfigUser.vue +++ b/frontend/components/ConfigUser.vue @@ -149,7 +149,9 @@ const user = ref({ } as User) onMounted(() => { - getUsers() + if (authStore.role === 'GlobalAdmin') { + getUsers() + } }) async function getUsers() { diff --git a/frontend/components/HeaderMenu.vue b/frontend/components/HeaderMenu.vue index 740eadac..2588b2b9 100644 --- a/frontend/components/HeaderMenu.vue +++ b/frontend/components/HeaderMenu.vue @@ -15,18 +15,26 @@
-
+
+
+
@@ -88,7 +87,16 @@ watch([listDate, i], () => { }) const calendarFormat = (date: Date) => { - return $dayjs(date).locale(locale.value).format('ddd L') + 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 { @@ -121,6 +129,10 @@ async function getLog() { .then((response) => response.text()) .then((data) => { currentLog.value = data + + nextTick(() => { + scrollTo() + }) }) .catch(() => { currentLog.value = '' diff --git a/frontend/pages/media.vue b/frontend/pages/media.vue index be8022a9..dc93e634 100644 --- a/frontend/pages/media.vue +++ b/frontend/pages/media.vue @@ -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('') }) diff --git a/frontend/stores/config.ts b/frontend/stores/config.ts index 2aa925bd..6e751365 100644 --- a/frontend/stores/config.ts +++ b/frontend/stores/config.ts @@ -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() } }) diff --git a/frontend/stores/playlist.ts b/frontend/stores/playlist.ts index 5c530341..c7c8286b 100644 --- a/frontend/stores/playlist.ts +++ b/frontend/stores/playlist.ts @@ -24,6 +24,7 @@ export const usePlaylist = defineStore('playlist', { playoutIsRunning: false, last_channel: 0, firstLoad: true, + scrollToItem: false, }), getters: {},