mirror of
https://github.com/euzu/tuliprox.git
synced 2026-09-29 12:32:08 +02:00
Merge pull request #315 from euzu/feature/mapper_script_view
Feature/mapper script view
This commit is contained in:
+1
-1
@@ -11,4 +11,4 @@ docker/
|
||||
frontend/node_modules/
|
||||
frontend/build/
|
||||
|
||||
webui/dist
|
||||
frontend/dist
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
- Added hanging client connection release
|
||||
- Added `replace` built-in function for mapper scripts
|
||||
- New WebUI features
|
||||
- Added `token_ttl_mins` to web_auth config to define auth token expiration duration.
|
||||
|
||||
# 3.1.4 (2025-06-17)
|
||||
- share live stream refactored
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["backend", "webui", "shared"]
|
||||
members = ["backend", "frontend", "shared"]
|
||||
resolver = "2"
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -320,6 +320,7 @@ log:
|
||||
- `enabled` can be deactivated if `enabled` is set to `false`. If not set default is `true`.
|
||||
- `issuer`
|
||||
- `secret` is used for jwt token generation.
|
||||
- `token_ttl_mins` default 30 minutes, setting it to 0 uses a 100-year expiration (effectively no expiration)—not recommended for production. !!CAUTION SECURITY RISK!!!
|
||||
- `userfile` is the file where the ui users are stored. If the filename is not absolute, `tuliprox` will look into the `config_dir`. If `userfile` is not given, the default value is `user.txt`.
|
||||
```yaml
|
||||
web_ui:
|
||||
|
||||
@@ -103,7 +103,7 @@ impl FromStr for ApiStreamContext {
|
||||
Self::TIMESHIFT => Ok(Self::Timeshift),
|
||||
_ => create_tuliprox_error_result!(
|
||||
TuliproxErrorKind::Info,
|
||||
"Unknown CounterModifier: {}",
|
||||
"Unknown ApiStreamContext: {}",
|
||||
s
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::api::model::{ProviderConfig, ProviderConfigConnection, ProviderConfigWrapper, ProviderConnectionChangeSender};
|
||||
use crate::api::model::{ProviderConfig, ProviderConfigConnection, ProviderConfigWrapper, ProviderConnectionChangeCallback, ProviderConnectionChangeSender};
|
||||
use crate::model::{AppConfig, ConfigInput};
|
||||
use arc_swap::ArcSwap;
|
||||
use log::{debug, log_enabled, trace};
|
||||
@@ -194,12 +194,12 @@ struct SingleProviderLineup {
|
||||
}
|
||||
|
||||
impl SingleProviderLineup {
|
||||
fn new<'a, F>(cfg: &ConfigInput, get_connection: Option<F>, connection_change_sender: ProviderConnectionChangeSender) -> Self
|
||||
fn new<'a, F>(cfg: &ConfigInput, get_connection: Option<F>, connection_change: &ProviderConnectionChangeCallback) -> Self
|
||||
where
|
||||
F: Fn(&str) -> Option<&'a ProviderConfigConnection>,
|
||||
{
|
||||
Self {
|
||||
provider: ProviderConfigWrapper::new(ProviderConfig::new(cfg, get_connection, connection_change_sender)),
|
||||
provider: ProviderConfigWrapper::new(ProviderConfig::new(cfg, get_connection, Arc::clone(connection_change))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,19 +264,20 @@ impl ProviderPriorityGroup {
|
||||
#[repr(align(64))]
|
||||
#[derive(Debug)]
|
||||
struct MultiProviderLineup {
|
||||
name: String,
|
||||
providers: Vec<ProviderPriorityGroup>,
|
||||
index: AtomicUsize,
|
||||
}
|
||||
|
||||
impl MultiProviderLineup {
|
||||
pub fn new<'a, F>(input: &ConfigInput, get_connection: Option<F>, connection_change_sender: &ProviderConnectionChangeSender) -> Self
|
||||
pub fn new<'a, F>(cfg_input: &ConfigInput, get_connection: Option<F>, connection_change: &ProviderConnectionChangeCallback) -> Self
|
||||
where
|
||||
F: Fn(&str) -> Option<&'a ProviderConfigConnection> + Copy,
|
||||
{
|
||||
let mut inputs = vec![ProviderConfigWrapper::new(ProviderConfig::new(input, get_connection, connection_change_sender.clone()))];
|
||||
if let Some(aliases) = &input.aliases {
|
||||
let mut inputs = vec![ProviderConfigWrapper::new(ProviderConfig::new(cfg_input, get_connection, Arc::clone(connection_change)))];
|
||||
if let Some(aliases) = &cfg_input.aliases {
|
||||
for alias in aliases {
|
||||
inputs.push(ProviderConfigWrapper::new(ProviderConfig::new_alias(input, alias, get_connection, connection_change_sender.clone())));
|
||||
inputs.push(ProviderConfigWrapper::new(ProviderConfig::new_alias(cfg_input, alias, get_connection, Arc::clone(connection_change))));
|
||||
}
|
||||
}
|
||||
let mut providers = HashMap::new();
|
||||
@@ -297,6 +298,7 @@ impl MultiProviderLineup {
|
||||
}).collect();
|
||||
|
||||
Self {
|
||||
name: cfg_input.name.clone(),
|
||||
providers,
|
||||
index: AtomicUsize::new(0),
|
||||
}
|
||||
@@ -484,6 +486,24 @@ impl MultiProviderLineup {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_total_connections(&self) -> usize {
|
||||
let mut total_connections = 0;
|
||||
|
||||
for group in &self.providers {
|
||||
match group {
|
||||
ProviderPriorityGroup::SingleProviderGroup(provider) => {
|
||||
total_connections += provider.get_current_connections().await;
|
||||
},
|
||||
ProviderPriorityGroup::MultiProviderGroup(_, providers) => {
|
||||
for provider in providers {
|
||||
total_connections += provider.get_current_connections().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
total_connections
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -507,13 +527,24 @@ impl ProviderLineupManager {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_lineup(input: &ConfigInput, provider_connections: Option<&HashMap<&str, ProviderConfigConnection>>, connection_change_sender: ProviderConnectionChangeSender) -> ProviderLineup {
|
||||
fn create_lineup(cfg_input: &ConfigInput, provider_connections: Option<&HashMap<&str, ProviderConfigConnection>>, connection_change_sender: ProviderConnectionChangeSender) -> ProviderLineup {
|
||||
let get_connections = provider_connections.map(|c| |name: &str| c.get(name));
|
||||
|
||||
if input.aliases.as_ref().is_some_and(|a| !a.is_empty()) {
|
||||
ProviderLineup::Multi(MultiProviderLineup::new(input, get_connections, &connection_change_sender))
|
||||
let cfg_name = cfg_input.name.clone();
|
||||
let on_connection_change: ProviderConnectionChangeCallback = Arc::new(move |_name: &str, connections: usize| {
|
||||
let connection_change_sender = connection_change_sender.clone();
|
||||
let cfg_name = cfg_name.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = connection_change_sender.send((cfg_name, connections)).await;
|
||||
});
|
||||
});
|
||||
|
||||
let on_connection_change = Arc::new(on_connection_change);
|
||||
|
||||
if cfg_input.aliases.as_ref().is_some_and(|a| !a.is_empty()) {
|
||||
ProviderLineup::Multi(MultiProviderLineup::new(cfg_input, get_connections, &on_connection_change))
|
||||
} else {
|
||||
ProviderLineup::Single(SingleProviderLineup::new(input, get_connections, connection_change_sender))
|
||||
ProviderLineup::Single(SingleProviderLineup::new(cfg_input, get_connections, &on_connection_change))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,47 +742,21 @@ impl ProviderLineupManager {
|
||||
|
||||
pub async fn active_connections(&self) -> Option<HashMap<String, usize>> {
|
||||
let mut result = HashMap::<String, usize>::new();
|
||||
let mut add_provider = async |provider: Option<&ProviderConfig>, name: Option<String>, count: usize| {
|
||||
if let Some(provider_cfg) = provider {
|
||||
let count = provider_cfg.get_current_connections().await;
|
||||
if count > 0 {
|
||||
result.insert(provider_cfg.name.to_string(), count);
|
||||
}
|
||||
} else if count > 0 {
|
||||
result.insert(name.unwrap_or_default(), count);
|
||||
let mut add_provider = |name: String, count: usize| {
|
||||
if count > 0 {
|
||||
result.insert(name, count);
|
||||
}
|
||||
};
|
||||
let providers = self.providers.load();
|
||||
for lineup in providers.iter() {
|
||||
match lineup {
|
||||
ProviderLineup::Single(provider_lineup) => {
|
||||
add_provider(Some(&provider_lineup.provider), None, 0).await;
|
||||
let connections = provider_lineup.provider.get_current_connections().await;
|
||||
add_provider(provider_lineup.provider.name.clone(), connections);
|
||||
}
|
||||
ProviderLineup::Multi(provider_lineup) => {
|
||||
for provider_group in &provider_lineup.providers {
|
||||
match provider_group {
|
||||
ProviderPriorityGroup::SingleProviderGroup(provider) => {
|
||||
add_provider(Some(provider), None, 0).await;
|
||||
}
|
||||
ProviderPriorityGroup::MultiProviderGroup(_, providers) => {
|
||||
let mut connections = 0;
|
||||
let mut name = None;
|
||||
for provider in providers {
|
||||
if name.is_none() {
|
||||
name = Some(format!("[{}]", provider.name.clone()));
|
||||
}
|
||||
connections += provider.get_current_connections().await;
|
||||
}
|
||||
if connections > 0 {
|
||||
add_provider(None, name, connections).await;
|
||||
}
|
||||
|
||||
// for provider in providers {
|
||||
// add_provider(provider).await;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
let connections = provider_lineup.get_total_connections().await;
|
||||
add_provider(provider_lineup.name.clone(), connections);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1042,9 +1047,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_acquire_when_capacity_available() {
|
||||
let cfg = create_config_input(1, "provider5_1", 1, 2);
|
||||
let (change_tx, _) = tokio::sync::mpsc::channel::<(String, usize)>(1);
|
||||
let on_connection_change = move |_name, _connections| {};
|
||||
let change_tx = Arc::new(on_connection_change);
|
||||
let dummy_get_connection = |_s: &str| -> Option<&ProviderConfigConnection> { None };
|
||||
let lineup = SingleProviderLineup::new(&cfg, Some(dummy_get_connection), change_tx);
|
||||
let lineup = SingleProviderLineup::new(&cfg, Some(dummy_get_connection), &change_tx);
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
rt.block_on(async move {
|
||||
// First acquire attempt should succeed
|
||||
@@ -1063,10 +1069,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_release_connection() {
|
||||
let cfg = create_config_input(1, "provider7_1", 1, 2);
|
||||
let (change_tx, _) = tokio::sync::mpsc::channel::<(String, usize)>(1);
|
||||
let on_connection_change = move |_name, _connections| {};
|
||||
let change_tx = Arc::new(on_connection_change);
|
||||
let dummy_get_connection = |_s: &str| -> Option<&ProviderConfigConnection> { None };
|
||||
|
||||
let lineup = SingleProviderLineup::new(&cfg, Some(dummy_get_connection), change_tx);
|
||||
let lineup = SingleProviderLineup::new(&cfg, Some(dummy_get_connection), &change_tx);
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
rt.block_on(async move {
|
||||
// Acquire two connections
|
||||
@@ -1127,9 +1134,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_concurrent_acquire() {
|
||||
let cfg = create_config_input(1, "provider9_1", 1, 2);
|
||||
let (change_tx, _) = tokio::sync::mpsc::channel::<(String, usize)>(1);
|
||||
let on_connection_change = move |_name, _connections| {};
|
||||
let change_tx = Arc::new(on_connection_change);
|
||||
let dummy_get_connection = |_s: &str| -> Option<&ProviderConfigConnection> { None };
|
||||
let lineup = Arc::new(SingleProviderLineup::new(&cfg, Some(dummy_get_connection), change_tx));
|
||||
let lineup = Arc::new(SingleProviderLineup::new(&cfg, Some(dummy_get_connection), &change_tx));
|
||||
|
||||
let available_count = Arc::new(AtomicU16::new(2));
|
||||
let grace_period_count = Arc::new(AtomicU16::new(1));
|
||||
|
||||
@@ -12,6 +12,8 @@ use shared::write_if_some;
|
||||
pub type ProviderConnectionChangeSender = tokio::sync::mpsc::Sender<(String, usize)>;
|
||||
pub type ProviderConnectionChangeReceiver = tokio::sync::mpsc::Receiver<(String, usize)>;
|
||||
|
||||
pub type ProviderConnectionChangeCallback = Arc<dyn Fn(&str, usize) + Send + Sync>;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ProviderConfigAllocation {
|
||||
Exhausted,
|
||||
@@ -33,7 +35,6 @@ pub struct ProviderConfigConnection {
|
||||
/// `max_connections`: Maximum allowed concurrent connections.
|
||||
/// `priority`: Priority level for selecting providers.
|
||||
/// `current_connections`: A `RwLock` to safely track the number of active connections.
|
||||
#[derive(Debug)]
|
||||
pub struct ProviderConfig {
|
||||
pub id: u16,
|
||||
pub name: String,
|
||||
@@ -44,7 +45,8 @@ pub struct ProviderConfig {
|
||||
max_connections: usize,
|
||||
priority: i16,
|
||||
connection: RwLock<ProviderConfigConnection>,
|
||||
connection_change_tx: ProviderConnectionChangeSender,
|
||||
//connection_change_tx: ProviderConnectionChangeSender,
|
||||
on_connection_change: ProviderConnectionChangeCallback,
|
||||
}
|
||||
|
||||
impl fmt::Display for ProviderConfig {
|
||||
@@ -65,6 +67,12 @@ impl fmt::Display for ProviderConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ProviderConfig {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ProviderConfig {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
@@ -81,17 +89,23 @@ impl PartialEq for ProviderConfig {
|
||||
|
||||
macro_rules! modify_connections {
|
||||
($self:ident, $guard:ident, +1) => {{
|
||||
$guard.current_connections += 1;
|
||||
$self.notify_connection_change($guard.current_connections).await;
|
||||
let cnt = {
|
||||
$guard.current_connections += 1;
|
||||
$guard.current_connections
|
||||
};
|
||||
$self.notify_connection_change(cnt);
|
||||
}};
|
||||
($self:ident, $guard:ident, -1) => {{
|
||||
$guard.current_connections -= 1;
|
||||
$self.notify_connection_change($guard.current_connections).await;
|
||||
let cnt = {
|
||||
$guard.current_connections -= 1;
|
||||
$guard.current_connections
|
||||
};
|
||||
$self.notify_connection_change(cnt);
|
||||
}};
|
||||
}
|
||||
|
||||
impl ProviderConfig {
|
||||
pub fn new<'a, F>(cfg: &ConfigInput, get_connection: Option<F>, connection_change_tx: ProviderConnectionChangeSender) -> Self
|
||||
pub fn new<'a, F>(cfg: &ConfigInput, get_connection: Option<F>, on_connection_change: ProviderConnectionChangeCallback) -> Self
|
||||
where
|
||||
F: Fn(&str) -> Option<&'a ProviderConfigConnection>,
|
||||
{
|
||||
@@ -105,11 +119,11 @@ impl ProviderConfig {
|
||||
max_connections: cfg.max_connections as usize,
|
||||
priority: cfg.priority,
|
||||
connection: RwLock::new(get_connection.and_then(|f| f(cfg.name.as_str())).map_or_else(Default::default, Clone::clone)),
|
||||
connection_change_tx
|
||||
on_connection_change
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_alias<'a, F>(cfg: &ConfigInput, alias: &ConfigInputAlias, get_connection: Option<F>, connection_change_tx: ProviderConnectionChangeSender) -> Self
|
||||
pub fn new_alias<'a, F>(cfg: &ConfigInput, alias: &ConfigInputAlias, get_connection: Option<F>, on_connection_change: ProviderConnectionChangeCallback) -> Self
|
||||
where
|
||||
F: Fn(&str) -> Option<&'a ProviderConfigConnection>,
|
||||
{
|
||||
@@ -123,7 +137,7 @@ impl ProviderConfig {
|
||||
max_connections: alias.max_connections as usize,
|
||||
priority: alias.priority,
|
||||
connection: RwLock::new(get_connection.and_then(|f| f(alias.name.as_str())).map_or_else(Default::default, Clone::clone)),
|
||||
connection_change_tx,
|
||||
on_connection_change,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,8 +145,8 @@ impl ProviderConfig {
|
||||
InputUserInfo::new(self.input_type, self.username.as_deref(), self.password.as_deref(), &self.url)
|
||||
}
|
||||
|
||||
async fn notify_connection_change(&self, new_connections: usize) {
|
||||
let _ = self.connection_change_tx.send((self.name.clone(), new_connections)).await;
|
||||
fn notify_connection_change(&self, new_connections: usize) {
|
||||
(self.on_connection_change)(&self.name, new_connections);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -32,7 +32,12 @@ fn create_jwt(web_auth_config: &WebAuthConfig, username: &str, roles: Vec<String
|
||||
header.typ = Some("JWT".to_string());
|
||||
let now = Local::now();
|
||||
let iat = now.timestamp();
|
||||
let exp = (now + Duration::minutes(30)).timestamp();
|
||||
let duration = web_auth_config.token_ttl_mins;
|
||||
let exp = if duration > 0 {
|
||||
(now + Duration::minutes(i64::from(duration))).timestamp()
|
||||
} else {
|
||||
(now + Duration::days(365 * 100)).timestamp() // 100 years
|
||||
};
|
||||
let claims = Claims {
|
||||
username: username.to_string(),
|
||||
iss: web_auth_config.issuer.clone(),
|
||||
|
||||
@@ -11,6 +11,7 @@ pub struct WebAuthConfig {
|
||||
pub enabled: bool,
|
||||
pub issuer: String,
|
||||
pub secret: String,
|
||||
pub token_ttl_mins: u32,
|
||||
pub userfile: Option<String>,
|
||||
pub t_users: Option<Vec<UserCredential>>,
|
||||
}
|
||||
@@ -20,8 +21,9 @@ impl From<&WebAuthConfigDto> for WebAuthConfig {
|
||||
fn from(dto: &WebAuthConfigDto) -> Self {
|
||||
Self {
|
||||
enabled: dto.enabled,
|
||||
issuer: dto.issuer.to_string(),
|
||||
secret: dto.secret.to_string(),
|
||||
issuer: dto.issuer.clone(),
|
||||
secret: dto.secret.clone(),
|
||||
token_ttl_mins: dto.token_ttl_mins,
|
||||
userfile: dto.userfile.clone(),
|
||||
t_users: None,
|
||||
}
|
||||
@@ -32,8 +34,9 @@ impl From<&WebAuthConfig> for WebAuthConfigDto {
|
||||
fn from(instance: &WebAuthConfig) -> Self {
|
||||
Self {
|
||||
enabled: instance.enabled,
|
||||
issuer: instance.issuer.to_string(),
|
||||
secret: instance.secret.to_string(),
|
||||
issuer: instance.issuer.clone(),
|
||||
secret: instance.secret.clone(),
|
||||
token_ttl_mins: instance.token_ttl_mins,
|
||||
userfile: instance.userfile.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
echo "building binary for aarch64"
|
||||
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target aarch64-unknown-linux-musl
|
||||
cd ./webui || exit 1
|
||||
cd ./frontend || exit 1
|
||||
env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
echo "building binary for armv7"
|
||||
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target armv7-unknown-linux-musleabihf
|
||||
cd ./webui || exit 1
|
||||
cd ./frontend || exit 1
|
||||
env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target x86_64-apple-darwin
|
||||
cd ./webui || exit 1
|
||||
cd ./frontend || exit 1
|
||||
env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
cd ./webui && rm -rf dist && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
|
||||
cd ./frontend && rm -rf dist && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
|
||||
|
||||
@@ -7,7 +7,7 @@ BIN_DIR="${WORKING_DIR}/bin"
|
||||
RESOURCES_DIR="${WORKING_DIR}/resources"
|
||||
DOCKER_DIR="${WORKING_DIR}/docker"
|
||||
BACKEND_DIR="${WORKING_DIR}/backend"
|
||||
FRONTEND_DIR="${WORKING_DIR}/webui"
|
||||
FRONTEND_DIR="${WORKING_DIR}/frontend"
|
||||
FRONTEND_BUILD_DIR="${FRONTEND_DIR}/dist"
|
||||
declare -A ARCHITECTURES=(
|
||||
[LINUX]=x86_64-unknown-linux-musl
|
||||
|
||||
@@ -28,7 +28,7 @@ BIN_DIR="${WORKING_DIR}/bin"
|
||||
RESOURCES_DIR="${WORKING_DIR}/resources"
|
||||
DOCKER_DIR="${WORKING_DIR}/docker"
|
||||
BACKEND_DIR="${WORKING_DIR}/backend"
|
||||
FRONTEND_DIR="${WORKING_DIR}/webui"
|
||||
FRONTEND_DIR="${WORKING_DIR}/frontend"
|
||||
FRONTEND_BUILD_DIR="${FRONTEND_DIR}/dist"
|
||||
TARGET=aarch64-unknown-linux-musl
|
||||
VERSION=$(grep -Po '^version\s*=\s*"\K[0-9\.]+' "${BACKEND_DIR}/Cargo.toml" || true)
|
||||
|
||||
@@ -7,7 +7,7 @@ BIN_DIR="${WORKING_DIR}/bin"
|
||||
RESOURCES_DIR="${WORKING_DIR}/resources"
|
||||
DOCKER_DIR="${WORKING_DIR}/docker"
|
||||
BACKEND_DIR="${WORKING_DIR}/backend"
|
||||
FRONTEND_DIR="${WORKING_DIR}/webui"
|
||||
FRONTEND_DIR="${WORKING_DIR}/frontend"
|
||||
FRONTEND_BUILD_DIR="${FRONTEND_DIR}/dist"
|
||||
TARGET=x86_64-unknown-linux-musl
|
||||
VERSION=$(grep -Po '^version\s*=\s*"\K[0-9\.]+' "${BACKEND_DIR}/Cargo.toml" || true)
|
||||
|
||||
+1
-1
@@ -5,4 +5,4 @@ if [ "$(uname)" != "Linux" ]; then
|
||||
else
|
||||
cargo build -p tuliprox --release
|
||||
fi
|
||||
cd "./webui" && trunk build --release
|
||||
cd "./frontend" && trunk build --release
|
||||
@@ -2,4 +2,4 @@
|
||||
DEFAULT_TARGET="x86_64-unknown-linux-musl"
|
||||
TARGET="${1:-$DEFAULT_TARGET}"
|
||||
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target "$TARGET"
|
||||
cd "./webui" && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
|
||||
cd "./frontend" && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
|
||||
+1
-1
@@ -3,4 +3,4 @@ env RUSTFLAGS="--remap-path-prefix $HOME=~" cargo build -p tuliprox --release --
|
||||
# cross build, is only working with cargo clean
|
||||
# env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target x86_64-pc-windows-gnu
|
||||
|
||||
cd "./webui" && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
|
||||
cd "./frontend" && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ set -o pipefail
|
||||
WORKING_DIR=$(pwd)
|
||||
RESOURCES_DIR="$WORKING_DIR/resources"
|
||||
RELEASE_DIR="$WORKING_DIR/release"
|
||||
FRONTEND_DIR="${WORKING_DIR}/webui"
|
||||
FRONTEND_DIR="${WORKING_DIR}/frontend"
|
||||
FRONTEND_BUILD_DIR="${FRONTEND_DIR}/dist"
|
||||
|
||||
if ! command -v cargo-set-version &> /dev/null
|
||||
|
||||
+4
-4
@@ -58,7 +58,7 @@ RUN cargo install --locked trunk wasm-bindgen-cli
|
||||
# Now copy the actual source code and build the project
|
||||
COPY . .
|
||||
|
||||
WORKDIR /src/webui
|
||||
WORKDIR /src/frontend
|
||||
|
||||
# Run Trunk build
|
||||
RUN trunk build --release
|
||||
@@ -135,7 +135,7 @@ RUN rustup update && rustup target add $RUST_TARGET
|
||||
|
||||
# Create the final application layout to mimic production
|
||||
WORKDIR /app
|
||||
COPY --from=trunk-build /src/webui/dist ./web
|
||||
COPY --from=trunk-build /src/frontend/dist ./web
|
||||
COPY --from=resource-build /src/resources ./resources
|
||||
|
||||
# Create a separate directory for the source code
|
||||
@@ -170,7 +170,7 @@ COPY --from=rust-build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
# App
|
||||
WORKDIR /app
|
||||
COPY --from=rust-build /src/target/${RUST_TARGET}/release/tuliprox ./tuliprox
|
||||
COPY --from=trunk-build /src/webui/dist ./web
|
||||
COPY --from=trunk-build /src/frontend/dist ./web
|
||||
COPY --from=resource-build /src/resources ./resources
|
||||
|
||||
ENTRYPOINT ["/app/tuliprox"]
|
||||
@@ -195,7 +195,7 @@ RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=rust-build /src/target/${RUST_TARGET}/release/tuliprox ./tuliprox
|
||||
COPY --from=trunk-build /src/webui/dist ./web
|
||||
COPY --from=trunk-build /src/frontend/dist ./web
|
||||
COPY --from=resource-build /src/resources ./resources
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "--", "/app/tuliprox"]
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"jest/globals": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest",
|
||||
"eslint-config-prettier",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"createClass": "createReactClass",
|
||||
"pragma": "React",
|
||||
"fragment": "Fragment",
|
||||
"version": "detect",
|
||||
"flowVersion": "detect"
|
||||
}
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"import",
|
||||
"node",
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"consistent-this": [
|
||||
"error",
|
||||
"self"
|
||||
],
|
||||
"@typescript-eslint/no-this-alias": [
|
||||
"error",
|
||||
{
|
||||
"allowDestructuring": false,
|
||||
"allowedNames": [
|
||||
"self"
|
||||
]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"react/display-name": "off",
|
||||
"react/prop-types": "off"
|
||||
},
|
||||
"ignorePatterns": [ "*.test.tsc", "*.scss", "*.css" ]
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
{
|
||||
"name": "tuliprox",
|
||||
"description": "tuliprox",
|
||||
"author": "euzu",
|
||||
"version": "3.1.4",
|
||||
"private": true,
|
||||
"homepage": "",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"react-start": "react-scripts start",
|
||||
"react-build": "react-scripts build",
|
||||
"react-test": "react-scripts test --env=jsdom",
|
||||
"react-eject": "react-scripts eject",
|
||||
"build": "cross-env GENERATE_SOURCEMAP=false REACT_APP_STAGE=production PUBLIC_URL= yarn react-build",
|
||||
"start": "yarn react-start",
|
||||
"lint": "eslint --config eslintrc.json src/** --no-error-on-unmatched-pattern --quiet",
|
||||
"upgr": "yarn upgrade-interactive --latest",
|
||||
"analyze": "source-map-explorer 'build/static/js/*.js'"
|
||||
},
|
||||
"dependencies": {
|
||||
"file-saver": "2.0.5",
|
||||
"i18next": "25.0.1",
|
||||
"jwt-decode": "4.0.0",
|
||||
"mpegts.js": "1.8.0",
|
||||
"notistack": "3.0.2",
|
||||
"react": "19.1.0",
|
||||
"react-datepicker": "8.3.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-i18next": "15.4.1",
|
||||
"react-tag-input-component": "2.0.2",
|
||||
"rxjs": "7.8.2",
|
||||
"video.js": "8.22.0",
|
||||
"videojs-mpegtsjs": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "7.21.11",
|
||||
"@babel/runtime": "7.27.0",
|
||||
"@types/file-saver": "2.0.7",
|
||||
"@types/react": "19.1.2",
|
||||
"@types/react-dom": "19.1.2",
|
||||
"@types/video.js": "7.3.58",
|
||||
"@typescript-eslint/eslint-plugin": "8.30.1",
|
||||
"@typescript-eslint/parser": "8.30.1",
|
||||
"babel-eslint": "10.1.0",
|
||||
"concurrently": "9.1.2",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "9.25.0",
|
||||
"eslint-config-prettier": "10.0.1",
|
||||
"eslint-config-react-app": "7.0.1",
|
||||
"eslint-plugin-flowtype": "8.0.3",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-jest": "28.11.0",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-react": "7.37.4",
|
||||
"eslint-plugin-react-hooks": "5.2.0",
|
||||
"prettier": "3.5.3",
|
||||
"react-scripts": "5.0.1",
|
||||
"sass": "1.86.3",
|
||||
"source-map-explorer": "2.5.3",
|
||||
"typescript": "5.8.3",
|
||||
"wait-on": "8.0.3"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"proxy": "http://localhost:8901"
|
||||
}
|
||||
@@ -56,4 +56,4 @@
|
||||
font-weight: 400;
|
||||
src: url('fonts/rubik-latin-400-normal.woff2') format('woff2'), url('fonts/rubik-latin-400-normal.woff') format('woff');
|
||||
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
{
|
||||
"LABEL": {
|
||||
"LIVE": "Live",
|
||||
"VOD": "VOD",
|
||||
"SERIES": "Serien",
|
||||
"SAVE": "Speichern",
|
||||
"SUBMIT": "Übernehmen",
|
||||
"OK": "Ok",
|
||||
"CANCEL": "Abbrechen",
|
||||
"SHOW_SELECTED": "Ausgewählte anzeigen",
|
||||
"SELECT_ALL": "Alle auswählen",
|
||||
"DESELECT_ALL": "Alle abwählen",
|
||||
"SEARCH": "Suche",
|
||||
"CLEAR": "Löschen",
|
||||
"REGEXP": "RegExp",
|
||||
"LOGOUT": "Abmelden",
|
||||
"CONFIGURATION": "Konfiguration",
|
||||
"HOST": "Host",
|
||||
"PORT": "Port",
|
||||
"WEB_ROOT": "Web-Root",
|
||||
"THREADS": "Threads",
|
||||
"WORKING_DIR": "Arbeitsverzeichnis",
|
||||
"BACKUP_DIR": "Backup-Verzeichnis",
|
||||
"UPDATE_ON_BOOT": "Update beim Start",
|
||||
"WEB_UI": "Web-UI",
|
||||
"SCHEDULES": "Zeitpläne",
|
||||
"SANITIZE_SENSITIVE_INFO": "Sensiblen Inhalt bereinigen",
|
||||
"ACTIVE_CLIENTS": "Aktive Clients",
|
||||
"RESOURCE_REWRITE_DISABLE": "Ressourcenumschreibung deaktivieren",
|
||||
"RECONNECT": "Erneut verbinden",
|
||||
"BUFFER_ENABLED": "Puffer aktiviert",
|
||||
"BUFFER_SIZE": "Puffergröße",
|
||||
"ENABLED": "Aktiviert",
|
||||
"SIZE": "Größe",
|
||||
"CACHE_DIR": "Cache-Verzeichnis",
|
||||
"NOTIFY_ON": "Benachrichtigen bei",
|
||||
"BOT_TOKEN": "Bot-Token",
|
||||
"CHAT_IDS": "Chat-IDs",
|
||||
"URL": "URL",
|
||||
"USER": "Benutzer",
|
||||
"USERS": "Benutzer",
|
||||
"TOKEN": "Token",
|
||||
"WEB_SEARCH_URL": "Web-Such-URL",
|
||||
"EXTENSIONS": "Erweiterungen",
|
||||
"HEADERS": "Header",
|
||||
"DIRECTORY": "Verzeichnis",
|
||||
"ORGANIZE_INTO_DIRECTORIES": "In Verzeichnisse organisieren",
|
||||
"EPISODE_PATTERN": "Episoden-Muster",
|
||||
"API": "API",
|
||||
"MAIN": "Haupt",
|
||||
"REVERSE_PROXY": "ReverseProxy",
|
||||
"MESSAGING": "Nachrichten",
|
||||
"VIDEO": "Video",
|
||||
"EXAMPLES": "Beispiele",
|
||||
"UPDATE": "Aktualisieren",
|
||||
"START": "Starten",
|
||||
"CACHE": "Cache",
|
||||
"STREAM": "Stream",
|
||||
"LOG": "Protokoll",
|
||||
"CONFIG": "Konfiguration",
|
||||
"TELEGRAM": "Telegram",
|
||||
"REST": "Rest",
|
||||
"PUSHOVER": "Pushover",
|
||||
"PROXY": "Proxy",
|
||||
"REFRESH": "Aktualisieren",
|
||||
"STATUS": "Status",
|
||||
"MAX_CON": "MaxCon",
|
||||
"EXP_DATE": "Ablaufdatum",
|
||||
"USERNAME": "Benutzername",
|
||||
"PASSWORD": "Passwort",
|
||||
"SERVER": "Server",
|
||||
"TIMEZONE": "Zeitzone",
|
||||
"PATH": "Pfad",
|
||||
"MESSAGE": "Nachricht",
|
||||
"PROTOCOL": "Protokoll",
|
||||
"NAME": "Name"
|
||||
},
|
||||
"TITLE": {
|
||||
"USER_BOUQUET_EDITOR": "Benutzer-Gruppen-Editor"
|
||||
},
|
||||
"MESSAGES": {
|
||||
"INVALID_REGEXP": "Ungültiger regulärer Ausdruck",
|
||||
"INVALID_FILETYPE": "Ungültiger Dateityp",
|
||||
"DOWNLOAD": {
|
||||
"SUCCESS": "Erfolgreich heruntergeladen",
|
||||
"FAIL": "Fehler beim Herunterladen!",
|
||||
"PLAYLIST": {
|
||||
"SUCCESS": "Playlist erfolgreich heruntergeladen",
|
||||
"FAIL": "Fehler beim Herunterladen der Playlist!"
|
||||
},
|
||||
"SERVER_CONFIG": {
|
||||
"FAIL": "Fehler beim Herunterladen der Serverkonfiguration!"
|
||||
}
|
||||
},
|
||||
"SAVE": {
|
||||
"MAIN_CONFIG": {
|
||||
"SUCCESS": "Hauptkonfiguration gespeichert!",
|
||||
"FAIL": "Fehler beim Speichern der Hauptkonfiguration!"
|
||||
},
|
||||
"BOUQUET": {
|
||||
"SUCCESS": "Bouquet erfolgreich gespeichert",
|
||||
"FAIL": "Fehler beim Speichern des Bouquets!"
|
||||
},
|
||||
"USER": {
|
||||
"SUCCESS": "Benutzer erfolgreich gespeichert",
|
||||
"FAIL": "Fehler beim Speichern des Benutzers!"
|
||||
},
|
||||
"PLAYLIST_UPDATE": {
|
||||
"SUCCESS": "Playlist Aktualisierung erfolgreich gestartet!",
|
||||
"FAIL": "Playlist Aktualisierung fehlgeschlagen!"
|
||||
},
|
||||
"SOURCE_SELECTOR": {
|
||||
"MISSING_SELECTION": "Kein Eintrag ausgewählt"
|
||||
}
|
||||
}
|
||||
},
|
||||
"INFO": {
|
||||
"RESTART_TO_APPLY_CHANGES": "Sie müssen neu starten, um Änderungen anzuwenden.",
|
||||
"SCHEDULE_EXAMPLE": "Beispiel für Zeitplan",
|
||||
"THREADS": "Threads: Lassen Sie es auf 0, wenn keine Probleme bestehen. Erhöhen Sie es, wenn Sie mehrere Anbieter haben. Der parallele Zugriff auf Ihren Anbieter kann zu einer Sperrung führen.",
|
||||
"WEB_ROOT": "Web-Root: Enthält die Web-UI-Dateien. Normalerweise befinden sie sich im gleichen Verzeichnis unter ./web"
|
||||
}
|
||||
}
|
||||
@@ -195,7 +195,10 @@
|
||||
"ALIAS": "Alias",
|
||||
"INPUTS": "Inputs",
|
||||
"NEW_USER": "New User",
|
||||
"USERLIST": "Userlist"
|
||||
"USERLIST": "Userlist",
|
||||
"MAPPER": "Mapper",
|
||||
"SCRIPT": "Script",
|
||||
"COUNTER": "Counter"
|
||||
},
|
||||
"TABLE": {
|
||||
"EMPTY": "",
|
||||
@@ -1,206 +0,0 @@
|
||||
{
|
||||
"LABEL": {
|
||||
"LIVE": "Live",
|
||||
"VOD": "VOD",
|
||||
"SERIES": "Series",
|
||||
"SAVE": "Save",
|
||||
"SUBMIT": "Submit",
|
||||
"OK": "Ok",
|
||||
"CANCEL": "Cancel",
|
||||
"CLOSE": "Close",
|
||||
"SHOW_SELECTED": "Show selected",
|
||||
"SELECT_ALL": "Select All",
|
||||
"DESELECT_ALL": "Deselect all",
|
||||
"SEARCH": "Search",
|
||||
"CLEAR": "Clear",
|
||||
"REGEXP": "Regexp",
|
||||
"LOGOUT": "Logout",
|
||||
"CONFIGURATION": "Configuration",
|
||||
"HOST": "Host",
|
||||
"PORT": "Port",
|
||||
"WEB_ROOT": "Web-Root",
|
||||
"THREADS": "Threads",
|
||||
"WORKING_DIR": "Working dir",
|
||||
"BACKUP_DIR": "Backup dir",
|
||||
"UPDATE_ON_BOOT": "Update on boot",
|
||||
"WEB_UI": "Web UI",
|
||||
"USER_UI": "User UI",
|
||||
"WEB_UI_PATH": "Path prefix",
|
||||
"SCHEDULES": "Schedules",
|
||||
"SANITIZE_SENSITIVE_INFO": "Sanitize sensitive info",
|
||||
"ACTIVE_USER": "Active User",
|
||||
"RESOURCE_REWRITE_DISABLE": "Resource Rewrite disable",
|
||||
"DISABLE_REFERER_HEADER": "Referer header disable",
|
||||
"RECONNECT": "Reconnect",
|
||||
"THROTTLE": "Throttle",
|
||||
"GRACE_PERIOD": "Grace period ms",
|
||||
"GRACE_PERIOD_TIMEOUT": "Grace period timeout sec",
|
||||
"BUFFER_ENABLED": "Buffer Enabled",
|
||||
"BUFFER_SIZE": "Buffer Size",
|
||||
"ENABLED": "Enabled",
|
||||
"SIZE": "Size",
|
||||
"CACHE_DIR": "Cache dir",
|
||||
"NOTIFY_ON": "Notify on",
|
||||
"BOT_TOKEN": "Bot Token",
|
||||
"CHAT_IDS": "Chat Ids",
|
||||
"URL": "Url",
|
||||
"USER": "User",
|
||||
"USERS": "Users",
|
||||
"TOKEN": "Token",
|
||||
"WEB_SEARCH_URL": "Web Search Url",
|
||||
"EXTENSIONS": "Extensions",
|
||||
"HEADERS": "Headers",
|
||||
"DIRECTORY": "Directory",
|
||||
"ORGANIZE_INTO_DIRECTORIES": "Organize Into Directorie",
|
||||
"EPISODE_PATTERN": "Episode Pattern",
|
||||
"API": "Api",
|
||||
"MAIN": "Main",
|
||||
"REVERSE_PROXY": "ReverseProxy",
|
||||
"MESSAGING": "Messaging",
|
||||
"VIDEO": "Video",
|
||||
"EXAMPLES": "Examples",
|
||||
"UPDATE": "Update",
|
||||
"START": "Start",
|
||||
"CACHE": "Cache",
|
||||
"STREAM": "Stream",
|
||||
"LOG": "Log",
|
||||
"LOG_LEVEL": "Log level",
|
||||
"CONFIG": "Config",
|
||||
"TELEGRAM": "Telegram",
|
||||
"REST": "Rest",
|
||||
"PUSHOVER": "Pushover",
|
||||
"PROXY": "Proxy",
|
||||
"REFRESH": "Refresh",
|
||||
"STATUS": "Status",
|
||||
"MAX_CON": "MaxCon",
|
||||
"EXP_DATE": "ExpDate",
|
||||
"USERNAME": "Username",
|
||||
"PASSWORD": "Password",
|
||||
"SERVER": "Server",
|
||||
"TIMEZONE": "Timezone",
|
||||
"PATH": "Path",
|
||||
"MESSAGE": "Message",
|
||||
"PROTOCOL": "Protocol",
|
||||
"NAME": "Name",
|
||||
"API_PROXY": "Api-Proxy",
|
||||
"INFINITE": "Infinite",
|
||||
"ONE_YEAR": "One year",
|
||||
"SELF_HOSTED": "Self Hosted",
|
||||
"PROVIDER": "Provider",
|
||||
"CUSTOM": "Custom",
|
||||
"XTREAM_CODES": "xtream codes",
|
||||
"XC": "xc",
|
||||
"M3U": "m3u",
|
||||
"PLAYLIST_BROWSER": "Playlist Browser",
|
||||
"RATE_LIMIT": "Rate Limit",
|
||||
"PERIOD_MILLIS": "Period Millis",
|
||||
"BURST_SIZE": "Burst Size",
|
||||
"WEB_AUTH": "Auth",
|
||||
"ISSUER": "Issuer",
|
||||
"SECRET": "Secret",
|
||||
"USERFILE": "Userfile",
|
||||
"UI_ENABLED": "UI",
|
||||
"PLAYER_SERVER": "Player Server",
|
||||
"VERSION": "Version",
|
||||
"SERVER_TIME": "Server time",
|
||||
"BUILD_TIME": "Build time",
|
||||
"MEMORY": "Memory",
|
||||
"ACTIVE_USERS": "Active users",
|
||||
"ACTIVE_USER_CONNECTIONS": "Active user conn...",
|
||||
"ACTIVE_PROVIDER_CONNECTIONS": "Active provider conn...",
|
||||
"IP_CHECK": "IP Check",
|
||||
"URL_IPV4": "Url IPv4",
|
||||
"URL_IPV6": "Url IPv6",
|
||||
"PATTERN_IPV4": "Pattern IPv4",
|
||||
"PATTERN_IPV6": "Pattern IPv6",
|
||||
"IPv4": "IPv4",
|
||||
"IPv6": "IPv6",
|
||||
"IP": "IP",
|
||||
"TARGET": "Target",
|
||||
"TARGETS": "Targets",
|
||||
"NOTES": "Notes"
|
||||
},
|
||||
"TITLE": {
|
||||
"USER_BOUQUET_EDITOR": "User group editor"
|
||||
},
|
||||
"MESSAGES": {
|
||||
"NO_CONTENT": "No content",
|
||||
"INVALID_REGEXP": "Invalid regexp",
|
||||
"INVALID_FILETYPE": "Invalid filetype",
|
||||
"TARGET_NOT_EXISTS": "Target does not exist",
|
||||
"DOWNLOAD": {
|
||||
"SUCCESS": "Successfully downloaded",
|
||||
"FAIL": "Failed to download!",
|
||||
"PLAYLIST": {
|
||||
"SUCCESS": "Successfully downloaded playlist",
|
||||
"FAIL": "Failed to download playlist!"
|
||||
},
|
||||
"SERVER_CONFIG": {
|
||||
"FAIL": "Failed to download server config!"
|
||||
}
|
||||
},
|
||||
"SAVE": {
|
||||
"MAIN_CONFIG": {
|
||||
"SUCCESS": "Main config saved!",
|
||||
"FAIL": "Failed to save main config!"
|
||||
},
|
||||
"BOUQUET": {
|
||||
"SUCCESS": "Successfully saved bouquet",
|
||||
"FAIL": "Failed to save bouquet!"
|
||||
},
|
||||
"USER": {
|
||||
"SUCCESS": "Successfully saved user",
|
||||
"FAIL": "Failed to save user!",
|
||||
"USERNAME_REQUIRED": "Username required",
|
||||
"DUPLICATE_USERNAME": "Duplicate username!",
|
||||
"DUPLICATE_TOKEN": "Duplicate token!",
|
||||
"TARGET_NOT_FOUND": "Target not found!",
|
||||
"MAX_CONNECTIONS_INVALID": "MaxConnections invalid"
|
||||
}
|
||||
},
|
||||
"PLAYLIST_UPDATE": {
|
||||
"SUCCESS": "Successfully started playlist update!",
|
||||
"FAIL": "Playlist update failed!"
|
||||
},
|
||||
"SOURCE_SELECTOR": {
|
||||
"MISSING_SELECTION": "No entry selected"
|
||||
}
|
||||
},
|
||||
"INFO": {
|
||||
"RESTART_TO_APPLY_CHANGES": "You need to restart to apply changes.",
|
||||
"SCHEDULE_EXAMPLE": "Schedule example",
|
||||
"THREADS": "Threads: keep it 0 if you have no problems. Increase it if you have multiple providers. Accessing you provider parallel can cause a ban.",
|
||||
"WEB_ROOT": "Web-Root: Contains the web-ui files. They are normally placed in the same directory under ./web"
|
||||
},
|
||||
"HINT": {
|
||||
"CONFIG": {
|
||||
"API_PROXY": {
|
||||
"PATH": "URL path prefix"
|
||||
},
|
||||
"USER": {
|
||||
"TARGET_FORCE_REDIRECT": "Force Redirect is set for target"
|
||||
},
|
||||
"LOG": {
|
||||
"ACTIVE_USER": "Activates active user info log"
|
||||
},
|
||||
"PROXY": {
|
||||
"RESOURCE_REWRITE_DISABLE": "If activated, the URL rewrite of resources in reverse proxy mode is disabled",
|
||||
"DISABLE_REFERER_HEADER": "If activated, the referer header is not sent to the provider",
|
||||
"THROTTLE": "Supported units are KB/s,MB/s,KiB/s,MiB/s,kbps,mbps,Mibps"
|
||||
},
|
||||
"WEB_UI": {
|
||||
"PATH": "Configuration of a path prefix, allowing it to be accessed behind a reverse proxy under a custom subpath. like https://yourdomain.com/tuliprox"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SETUP":{
|
||||
"LABEL": {
|
||||
"NEXT": "Next",
|
||||
"BACK": "Back",
|
||||
"FINISH_SETUP": "Finish Setup"
|
||||
},
|
||||
"MSG": {
|
||||
"WELCOME": "Welcome to tuliprox setup. This wizard will guide you through the initial setup process."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
{
|
||||
"LABEL": {
|
||||
"LIVE": "En vivo",
|
||||
"VOD": "VOD",
|
||||
"SERIES": "Series",
|
||||
"SAVE": "Guardar",
|
||||
"SHOW_SELECTED": "Mostrar seleccionados",
|
||||
"SELECT_ALL": "Seleccionar todo",
|
||||
"DESELECT_ALL": "Deseleccionar todo",
|
||||
"SEARCH": "Buscar",
|
||||
"CLEAR": "Borrar",
|
||||
"REGEXP": "Expresión regular",
|
||||
"LOGOUT": "Cerrar sesión",
|
||||
"CONFIGURATION": "Configuración",
|
||||
"HOST": "Host",
|
||||
"PORT": "Puerto",
|
||||
"WEB_ROOT": "Raíz web",
|
||||
"THREADS": "Hilos",
|
||||
"WORKING_DIR": "Directorio de trabajo",
|
||||
"BACKUP_DIR": "Directorio de respaldo",
|
||||
"UPDATE_ON_BOOT": "Actualizar al arrancar",
|
||||
"WEB_UI": "Interfaz web",
|
||||
"SCHEDULES": "Horarios",
|
||||
"SANITIZE_SENSITIVE_INFO": "Sanitizar información sensible",
|
||||
"ACTIVE_CLIENTS": "Clientes activos",
|
||||
"RESOURCE_REWRITE_DISABLE": "Desactivar reescritura de recursos",
|
||||
"RECONNECT": "Reconectar",
|
||||
"BUFFER_ENABLED": "Búfer activado",
|
||||
"BUFFER_SIZE": "Tamaño del búfer",
|
||||
"ENABLED": "Habilitado",
|
||||
"SIZE": "Tamaño",
|
||||
"CACHE_DIR": "Directorio de caché",
|
||||
"NOTIFY_ON": "Notificar en",
|
||||
"BOT_TOKEN": "Token del bot",
|
||||
"CHAT_IDS": "IDs de chat",
|
||||
"URL": "URL",
|
||||
"USER": "Usuario",
|
||||
"TOKEN": "Token",
|
||||
"WEB_SEARCH_URL": "URL de búsqueda web",
|
||||
"EXTENSIONS": "Extensiones",
|
||||
"HEADERS": "Encabezados",
|
||||
"DIRECTORY": "Directorio",
|
||||
"ORGANIZE_INTO_DIRECTORIES": "Organizar en directorios",
|
||||
"EPISODE_PATTERN": "Patrón de episodio",
|
||||
"API": "API",
|
||||
"MAIN": "Principal",
|
||||
"REVERSE_PROXY": "Proxy inverso",
|
||||
"MESSAGING": "Mensajería",
|
||||
"VIDEO": "Video",
|
||||
"EXAMPLES": "Ejemplos",
|
||||
"UPDATE": "Actualizar",
|
||||
"START": "Iniciar",
|
||||
"CACHE": "Caché",
|
||||
"STREAM": "Flujo",
|
||||
"LOG": "Registro",
|
||||
"CONFIG": "Configuración",
|
||||
"TELEGRAM": "Telegram",
|
||||
"REST": "Rest",
|
||||
"PUSHOVER": "Pushover",
|
||||
"PROXY": "Proxy",
|
||||
"REFRESH": "Actualizar",
|
||||
"STATUS": "Estado",
|
||||
"MAX_CON": "MaxCon",
|
||||
"EXP_DATE": "Fecha de expiración",
|
||||
"USERNAME": "Nombre de usuario",
|
||||
"PASSWORD": "Contraseña",
|
||||
"SERVER": "Servidor",
|
||||
"TIMEZONE": "Zona horaria",
|
||||
"PATH": "Ruta",
|
||||
"MESSAGE": "Mensaje",
|
||||
"PROTOCOL": "Protocolo",
|
||||
"NAME": "Nombre"
|
||||
},
|
||||
"TITLE": {
|
||||
"USER_BOUQUET_EDITOR": "Editor de bouquet de usuario"
|
||||
},
|
||||
"MESSAGES": {
|
||||
"INVALID_REGEXP": "Expresión regular no válida",
|
||||
"INVALID_FILETYPE": "Tipo de archivo no válido",
|
||||
"DOWNLOAD": {
|
||||
"SUCCESS": "Descargado con éxito",
|
||||
"FAIL": "¡Error al descargar!",
|
||||
"PLAYLIST": {
|
||||
"SUCCESS": "Lista de reproducción descargada con éxito",
|
||||
"FAIL": "¡Error al descargar la lista de reproducción!"
|
||||
},
|
||||
"SERVER_CONFIG": {
|
||||
"FAIL": "¡Error al descargar la configuración del servidor!"
|
||||
}
|
||||
},
|
||||
"SAVE": {
|
||||
"MAIN_CONFIG": {
|
||||
"SUCCESS": "¡Configuración principal guardada!",
|
||||
"FAIL": "¡Error al guardar la configuración principal!"
|
||||
},
|
||||
"BOUQUET": {
|
||||
"SUCCESS": "Bouquet guardado con éxito",
|
||||
"FAIL": "¡Error al guardar el bouquet!"
|
||||
},
|
||||
"USER": {
|
||||
"SUCCESS": "Usuario guardado con éxito",
|
||||
"FAIL": "¡Error al guardar el usuario!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"INFO": {
|
||||
"RESTART_TO_APPLY_CHANGES": "Necesitas reiniciar para aplicar los cambios.",
|
||||
"SCHEDULE_EXAMPLE": "Ejemplo de horario",
|
||||
"THREADS": "Hilos: mantén en 0 si no tienes problemas. Aumenta si tienes varios proveedores. Acceder a tu proveedor en paralelo puede causar un baneo.",
|
||||
"WEB_ROOT": "Raíz web: contiene los archivos de la interfaz web. Normalmente se colocan en el mismo directorio bajo ./web"
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
{
|
||||
"LABEL": {
|
||||
"LIVE": "En direct",
|
||||
"VOD": "VOD",
|
||||
"SERIES": "Séries",
|
||||
"SAVE": "Enregistrer",
|
||||
"SHOW_SELECTED": "Afficher la sélection",
|
||||
"SELECT_ALL": "Tout sélectionner",
|
||||
"DESELECT_ALL": "Tout désélectionner",
|
||||
"SEARCH": "Rechercher",
|
||||
"CLEAR": "Effacer",
|
||||
"REGEXP": "Expression régulière",
|
||||
"LOGOUT": "Se déconnecter",
|
||||
"CONFIGURATION": "Configuration",
|
||||
"HOST": "Hôte",
|
||||
"PORT": "Port",
|
||||
"WEB_ROOT": "Racine Web",
|
||||
"THREADS": "Threads",
|
||||
"WORKING_DIR": "Répertoire de travail",
|
||||
"BACKUP_DIR": "Répertoire de sauvegarde",
|
||||
"UPDATE_ON_BOOT": "Mettre à jour au démarrage",
|
||||
"WEB_UI": "Interface Web",
|
||||
"SCHEDULES": "Programmes",
|
||||
"SANITIZE_SENSITIVE_INFO": "Assainir les informations sensibles",
|
||||
"ACTIVE_CLIENTS": "Clients actifs",
|
||||
"RESOURCE_REWRITE_DISABLE": "Désactiver la réécriture des ressources",
|
||||
"RECONNECT": "Se reconnecter",
|
||||
"BUFFER_ENABLED": "Mise en mémoire tampon activée",
|
||||
"BUFFER_SIZE": "Taille du tampon",
|
||||
"ENABLED": "Activé",
|
||||
"SIZE": "Taille",
|
||||
"CACHE_DIR": "Répertoire du cache",
|
||||
"NOTIFY_ON": "Notifier lorsqu'il y a",
|
||||
"BOT_TOKEN": "Jeton du bot",
|
||||
"CHAT_IDS": "Identifiants de chat",
|
||||
"URL": "URL",
|
||||
"USER": "Utilisateur",
|
||||
"TOKEN": "Jeton",
|
||||
"WEB_SEARCH_URL": "URL de recherche Web",
|
||||
"EXTENSIONS": "Extensions",
|
||||
"HEADERS": "En-têtes",
|
||||
"DIRECTORY": "Répertoire",
|
||||
"ORGANIZE_INTO_DIRECTORIES": "Organiser dans des répertoires",
|
||||
"EPISODE_PATTERN": "Modèle d'épisode",
|
||||
"API": "API",
|
||||
"MAIN": "Principal",
|
||||
"REVERSE_PROXY": "Proxy inverse",
|
||||
"MESSAGING": "Messagerie",
|
||||
"VIDEO": "Vidéo",
|
||||
"EXAMPLES": "Exemples",
|
||||
"UPDATE": "Mettre à jour",
|
||||
"START": "Démarrer",
|
||||
"CACHE": "Cache",
|
||||
"STREAM": "Diffusion",
|
||||
"LOG": "Journal",
|
||||
"CONFIG": "Configuration",
|
||||
"TELEGRAM": "Telegram",
|
||||
"REST": "Rest",
|
||||
"PUSHOVER": "Pushover",
|
||||
"PROXY": "Proxy",
|
||||
"REFRESH": "Rafraîchir",
|
||||
"STATUS": "Statut",
|
||||
"MAX_CON": "MaxCon",
|
||||
"EXP_DATE": "Date d'expiration",
|
||||
"USERNAME": "Nom d'utilisateur",
|
||||
"PASSWORD": "Mot de passe",
|
||||
"SERVER": "Serveur",
|
||||
"TIMEZONE": "Fuseau horaire",
|
||||
"PATH": "Chemin",
|
||||
"MESSAGE": "Message",
|
||||
"PROTOCOL": "Protocole",
|
||||
"NAME": "Nom"
|
||||
},
|
||||
"TITLE": {
|
||||
"USER_BOUQUET_EDITOR": "Éditeur de bouquet utilisateur"
|
||||
},
|
||||
"MESSAGES": {
|
||||
"INVALID_REGEXP": "Expression régulière invalide",
|
||||
"INVALID_FILETYPE": "Type de fichier invalide",
|
||||
"DOWNLOAD": {
|
||||
"SUCCESS": "Téléchargement réussi",
|
||||
"FAIL": "Échec du téléchargement!",
|
||||
"PLAYLIST": {
|
||||
"SUCCESS": "Liste de lecture téléchargée avec succès",
|
||||
"FAIL": "Échec du téléchargement de la liste de lecture!"
|
||||
},
|
||||
"SERVER_CONFIG": {
|
||||
"FAIL": "Échec du téléchargement de la configuration du serveur!"
|
||||
}
|
||||
},
|
||||
"SAVE": {
|
||||
"MAIN_CONFIG": {
|
||||
"SUCCESS": "Configuration principale enregistrée!",
|
||||
"FAIL": "Échec de l'enregistrement de la configuration principale!"
|
||||
},
|
||||
"BOUQUET": {
|
||||
"SUCCESS": "Bouquet enregistré avec succès",
|
||||
"FAIL": "Échec de l'enregistrement du bouquet!"
|
||||
},
|
||||
"USER": {
|
||||
"SUCCESS": "Utilisateur enregistré avec succès",
|
||||
"FAIL": "Échec de l'enregistrement de l'utilisateur!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"INFO": {
|
||||
"RESTART_TO_APPLY_CHANGES": "Vous devez redémarrer pour appliquer les modifications.",
|
||||
"SCHEDULE_EXAMPLE": "Exemple de programme",
|
||||
"THREADS": "Threads: laissez-le à 0 si vous n'avez aucun problème. Augmentez-le si vous avez plusieurs fournisseurs. L'accès parallèle à votre fournisseur peut entraîner un ban.",
|
||||
"WEB_ROOT": "Racine Web: Contient les fichiers de l'interface web. Ils sont normalement placés dans le même répertoire sous ./web"
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
{
|
||||
"LABEL": {
|
||||
"LIVE": "In diretta",
|
||||
"VOD": "VOD",
|
||||
"SERIES": "Serie",
|
||||
"SAVE": "Salva",
|
||||
"SHOW_SELECTED": "Mostra selezionati",
|
||||
"SELECT_ALL": "Seleziona tutto",
|
||||
"DESELECT_ALL": "Deseleziona tutto",
|
||||
"SEARCH": "Cerca",
|
||||
"CLEAR": "Cancella",
|
||||
"REGEXP": "Espressione regolare",
|
||||
"LOGOUT": "Esci",
|
||||
"CONFIGURATION": "Configurazione",
|
||||
"HOST": "Host",
|
||||
"PORT": "Porta",
|
||||
"WEB_ROOT": "Root web",
|
||||
"THREADS": "Thread",
|
||||
"WORKING_DIR": "Cartella di lavoro",
|
||||
"BACKUP_DIR": "Cartella di backup",
|
||||
"UPDATE_ON_BOOT": "Aggiorna all'avvio",
|
||||
"WEB_UI": "Interfaccia web",
|
||||
"SCHEDULES": "Orari",
|
||||
"SANITIZE_SENSITIVE_INFO": "Sanitizza informazioni sensibili",
|
||||
"ACTIVE_CLIENTS": "Client attivi",
|
||||
"RESOURCE_REWRITE_DISABLE": "Disabilita riscrittura risorse",
|
||||
"RECONNECT": "Riconnetti",
|
||||
"BUFFER_ENABLED": "Buffer attivato",
|
||||
"BUFFER_SIZE": "Dimensione buffer",
|
||||
"ENABLED": "Abilitato",
|
||||
"SIZE": "Dimensione",
|
||||
"CACHE_DIR": "Cartella cache",
|
||||
"NOTIFY_ON": "Notifica su",
|
||||
"BOT_TOKEN": "Token bot",
|
||||
"CHAT_IDS": "ID chat",
|
||||
"URL": "URL",
|
||||
"USER": "Utente",
|
||||
"TOKEN": "Token",
|
||||
"WEB_SEARCH_URL": "URL ricerca web",
|
||||
"EXTENSIONS": "Estensioni",
|
||||
"HEADERS": "Intestazioni",
|
||||
"DIRECTORY": "Cartella",
|
||||
"ORGANIZE_INTO_DIRECTORIES": "Organizza in cartelle",
|
||||
"EPISODE_PATTERN": "Pattern episodio",
|
||||
"API": "API",
|
||||
"MAIN": "Principale",
|
||||
"REVERSE_PROXY": "Proxy inverso",
|
||||
"MESSAGING": "Messaggistica",
|
||||
"VIDEO": "Video",
|
||||
"EXAMPLES": "Esempi",
|
||||
"UPDATE": "Aggiorna",
|
||||
"START": "Avvia",
|
||||
"CACHE": "Cache",
|
||||
"STREAM": "Flusso",
|
||||
"LOG": "Registro",
|
||||
"CONFIG": "Configurazione",
|
||||
"TELEGRAM": "Telegram",
|
||||
"REST": "Rest",
|
||||
"PUSHOVER": "Pushover",
|
||||
"PROXY": "Proxy",
|
||||
"REFRESH": "Ricarica",
|
||||
"STATUS": "Stato",
|
||||
"MAX_CON": "MaxCon",
|
||||
"EXP_DATE": "Data di scadenza",
|
||||
"USERNAME": "Nome utente",
|
||||
"PASSWORD": "Password",
|
||||
"SERVER": "Server",
|
||||
"TIMEZONE": "Fuso orario",
|
||||
"PATH": "Percorso",
|
||||
"MESSAGE": "Messaggio",
|
||||
"PROTOCOL": "Protocollo",
|
||||
"NAME": "Nome"
|
||||
},
|
||||
"TITLE": {
|
||||
"USER_BOUQUET_EDITOR": "Editor bouquet utente"
|
||||
},
|
||||
"MESSAGES": {
|
||||
"INVALID_REGEXP": "Espressione regolare non valida",
|
||||
"INVALID_FILETYPE": "Tipo di file non valido",
|
||||
"DOWNLOAD": {
|
||||
"SUCCESS": "Scaricato con successo",
|
||||
"FAIL": "Download fallito!",
|
||||
"PLAYLIST": {
|
||||
"SUCCESS": "Playlist scaricata con successo",
|
||||
"FAIL": "Download playlist fallito!"
|
||||
},
|
||||
"SERVER_CONFIG": {
|
||||
"FAIL": "Download configurazione server fallito!"
|
||||
}
|
||||
},
|
||||
"SAVE": {
|
||||
"MAIN_CONFIG": {
|
||||
"SUCCESS": "Configurazione principale salvata!",
|
||||
"FAIL": "Salvataggio configurazione principale fallito!"
|
||||
},
|
||||
"BOUQUET": {
|
||||
"SUCCESS": "Bouquet salvato con successo",
|
||||
"FAIL": "Salvataggio bouquet fallito!"
|
||||
},
|
||||
"USER": {
|
||||
"SUCCESS": "Utente salvato con successo",
|
||||
"FAIL": "Salvataggio utente fallito!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"INFO": {
|
||||
"RESTART_TO_APPLY_CHANGES": "È necessario riavviare per applicare le modifiche.",
|
||||
"SCHEDULE_EXAMPLE": "Esempio di programma",
|
||||
"THREADS": "Thread: lascia 0 se non hai problemi. Aumenta se hai più provider. L'accesso parallelo ai tuoi provider potrebbe causare un ban.",
|
||||
"WEB_ROOT": "Root Web: Contiene i file dell'interfaccia web. Di solito vengono posizionati nella stessa cartella sotto ./web"
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
{
|
||||
"LABEL": {
|
||||
"LIVE": "Прямой эфир",
|
||||
"VOD": "VOD",
|
||||
"SERIES": "Сериалы",
|
||||
"SAVE": "Сохранить",
|
||||
"SHOW_SELECTED": "Показать выбранное",
|
||||
"SELECT_ALL": "Выбрать все",
|
||||
"DESELECT_ALL": "Отменить выбор всех",
|
||||
"SEARCH": "Поиск",
|
||||
"CLEAR": "Очистить",
|
||||
"REGEXP": "Регулярное выражение",
|
||||
"LOGOUT": "Выйти",
|
||||
"CONFIGURATION": "Конфигурация",
|
||||
"HOST": "Хост",
|
||||
"PORT": "Порт",
|
||||
"WEB_ROOT": "Корень веба",
|
||||
"THREADS": "Потоки",
|
||||
"WORKING_DIR": "Рабочий каталог",
|
||||
"BACKUP_DIR": "Каталог резервных копий",
|
||||
"UPDATE_ON_BOOT": "Обновить при загрузке",
|
||||
"WEB_UI": "Веб-интерфейс",
|
||||
"SCHEDULES": "Расписания",
|
||||
"SANITIZE_SENSITIVE_INFO": "Очистить чувствительную информацию",
|
||||
"ACTIVE_CLIENTS": "Активные клиенты",
|
||||
"RESOURCE_REWRITE_DISABLE": "Отключить переписывание ресурсов",
|
||||
"RECONNECT": "Переподключить",
|
||||
"BUFFER_ENABLED": "Буфер включен",
|
||||
"BUFFER_SIZE": "Размер буфера",
|
||||
"ENABLED": "Включено",
|
||||
"SIZE": "Размер",
|
||||
"CACHE_DIR": "Каталог кэша",
|
||||
"NOTIFY_ON": "Уведомить о",
|
||||
"BOT_TOKEN": "Токен бота",
|
||||
"CHAT_IDS": "ID чатов",
|
||||
"URL": "URL",
|
||||
"USER": "Пользователь",
|
||||
"TOKEN": "Токен",
|
||||
"WEB_SEARCH_URL": "URL веб-поиска",
|
||||
"EXTENSIONS": "Расширения",
|
||||
"HEADERS": "Заголовки",
|
||||
"DIRECTORY": "Каталог",
|
||||
"ORGANIZE_INTO_DIRECTORIES": "Организовать в каталоги",
|
||||
"EPISODE_PATTERN": "Шаблон эпизодов",
|
||||
"API": "API",
|
||||
"MAIN": "Основное",
|
||||
"REVERSE_PROXY": "Обратный прокси",
|
||||
"MESSAGING": "Сообщения",
|
||||
"VIDEO": "Видео",
|
||||
"EXAMPLES": "Примеры",
|
||||
"UPDATE": "Обновить",
|
||||
"START": "Старт",
|
||||
"CACHE": "Кэш",
|
||||
"STREAM": "Поток",
|
||||
"LOG": "Лог",
|
||||
"CONFIG": "Конфигурация",
|
||||
"TELEGRAM": "Телеграм",
|
||||
"REST": "Rest",
|
||||
"PUSHOVER": "Pushover",
|
||||
"PROXY": "Прокси",
|
||||
"REFRESH": "Обновить",
|
||||
"STATUS": "Статус",
|
||||
"MAX_CON": "Макс. соединений",
|
||||
"EXP_DATE": "Дата истечения",
|
||||
"USERNAME": "Имя пользователя",
|
||||
"PASSWORD": "Пароль",
|
||||
"SERVER": "Сервер",
|
||||
"TIMEZONE": "Часовой пояс",
|
||||
"PATH": "Путь",
|
||||
"MESSAGE": "Сообщение",
|
||||
"PROTOCOL": "Протокол",
|
||||
"NAME": "Имя"
|
||||
},
|
||||
"TITLE": {
|
||||
"USER_BOUQUET_EDITOR": "Редактор букета пользователя"
|
||||
},
|
||||
"MESSAGES": {
|
||||
"INVALID_REGEXP": "Неверное регулярное выражение",
|
||||
"INVALID_FILETYPE": "Неверный тип файла",
|
||||
"DOWNLOAD": {
|
||||
"SUCCESS": "Загрузка успешна",
|
||||
"FAIL": "Ошибка загрузки!",
|
||||
"PLAYLIST": {
|
||||
"SUCCESS": "Плейлист успешно загружен",
|
||||
"FAIL": "Ошибка загрузки плейлиста!"
|
||||
},
|
||||
"SERVER_CONFIG": {
|
||||
"FAIL": "Не удалось загрузить конфигурацию сервера!"
|
||||
}
|
||||
},
|
||||
"SAVE": {
|
||||
"MAIN_CONFIG": {
|
||||
"SUCCESS": "Основная конфигурация сохранена!",
|
||||
"FAIL": "Не удалось сохранить основную конфигурацию!"
|
||||
},
|
||||
"BOUQUET": {
|
||||
"SUCCESS": "Букет успешно сохранен",
|
||||
"FAIL": "Не удалось сохранить букет!"
|
||||
},
|
||||
"USER": {
|
||||
"SUCCESS": "Пользователь успешно сохранен",
|
||||
"FAIL": "Не удалось сохранить пользователя!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"INFO": {
|
||||
"RESTART_TO_APPLY_CHANGES": "Необходимо перезагрузить систему, чтобы применить изменения.",
|
||||
"SCHEDULE_EXAMPLE": "Пример расписания",
|
||||
"THREADS": "Потоки: оставьте 0, если нет проблем. Увеличьте, если у вас несколько провайдеров. Одновременный доступ к нескольким провайдерам может привести к блокировке.",
|
||||
"WEB_ROOT": "Корень веба: содержит файлы веб-интерфейса. Обычно они размещаются в той же директории ./web"
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
{
|
||||
"LABEL": {
|
||||
"LIVE": "Canlı",
|
||||
"VOD": "Video",
|
||||
"SERIES": "Diziler",
|
||||
"SAVE": "Kaydet",
|
||||
"SUBMIT": "Onayla",
|
||||
"OK": "Tamam",
|
||||
"CANCEL": "İptal",
|
||||
"SHOW_SELECTED": "Seçilenleri Göster",
|
||||
"SELECT_ALL": "Tümünü Seç",
|
||||
"DESELECT_ALL": "Tümünü Seçimden Çıkar",
|
||||
"SEARCH": "Ara",
|
||||
"CLEAR": "Sil",
|
||||
"REGEXP": "RegExp",
|
||||
"LOGOUT": "Çıkış Yap",
|
||||
"CONFIGURATION": "Yapılandırma",
|
||||
"HOST": "Sunucu",
|
||||
"PORT": "Port",
|
||||
"WEB_ROOT": "Web Klasörü",
|
||||
"THREADS": "İşlemciler",
|
||||
"WORKING_DIR": "Çalışma Klasörü",
|
||||
"BACKUP_DIR": "Yedekleme Klasörü",
|
||||
"UPDATE_ON_BOOT": "Başlangıçta Güncelle",
|
||||
"WEB_UI": "Web UI",
|
||||
"SCHEDULES": "Programlar",
|
||||
"SANITIZE_SENSITIVE_INFO": "Hassas Bilgileri Temizle",
|
||||
"ACTIVE_CLIENTS": "Aktif Kullanıcılar",
|
||||
"RESOURCE_REWRITE_DISABLE": "Url Yönlendirme Devre Dışı",
|
||||
"RECONNECT": "Yeniden Bağlan",
|
||||
"BUFFER_ENABLED": "Arabellek Aktif",
|
||||
"BUFFER_SIZE": "Arabellek Boyutu",
|
||||
"ENABLED": "Aktif",
|
||||
"SIZE": "Boyut",
|
||||
"CACHE_DIR": "Önbellek Dizin",
|
||||
"NOTIFY_ON": "Bildirimler",
|
||||
"BOT_TOKEN": "Bot Token'i",
|
||||
"CHAT_IDS": "Chat ID'leri",
|
||||
"URL": "URL",
|
||||
"USER": "Kullanıcı",
|
||||
"TOKEN": "Token",
|
||||
"WEB_SEARCH_URL": "Web Arama URL'si",
|
||||
"EXTENSIONS": "Eklentiler",
|
||||
"HEADERS": "Başlıklar",
|
||||
"DIRECTORY": "Klasör",
|
||||
"ORGANIZE_INTO_DIRECTORIES": "Klasörlere ayır",
|
||||
"EPISODE_PATTERN": "Bölüm Pattern'i",
|
||||
"API": "API",
|
||||
"MAIN": "Ana",
|
||||
"REVERSE_PROXY": "Ters Proxy",
|
||||
"MESSAGING": "Mesajlaşma",
|
||||
"VIDEO": "Video",
|
||||
"EXAMPLES": "Örnekler",
|
||||
"UPDATE": "Güncelle",
|
||||
"START": "Başlat",
|
||||
"CACHE": "Önbellek",
|
||||
"STREAM": "Yayın",
|
||||
"LOG": "Kayıt",
|
||||
"CONFIG": "Yapılandırma",
|
||||
"TELEGRAM": "Telegram",
|
||||
"REST": "Rest",
|
||||
"PUSHOVER": "Pushover",
|
||||
"PROXY": "Proxy",
|
||||
"REFRESH": "Yenile",
|
||||
"STATUS": "Durum",
|
||||
"MAX_CON": "Max. Bağlantı",
|
||||
"EXP_DATE": "Son Kullanma Tarihi",
|
||||
"USERNAME": "Kullanıcı Adı",
|
||||
"PASSWORD": "Şifre",
|
||||
"SERVER": "Sunucu",
|
||||
"TIMEZONE": "Zaman Dilimi",
|
||||
"PATH": "Yol",
|
||||
"MESSAGE": "Mesaj",
|
||||
"PROTOCOL": "Protokol",
|
||||
"NAME": "Ad"
|
||||
},
|
||||
"TITLE": {
|
||||
"USER_BOUQUET_EDITOR": "Kullanıcı Buket Editörü"
|
||||
},
|
||||
"MESSAGES": {
|
||||
"INVALID_REGEXP": "Geçersiz regexp",
|
||||
"INVALID_FILETYPE": "Geçersiz dosya türü",
|
||||
"DOWNLOAD": {
|
||||
"SUCCESS": "Başarıyla indirildi",
|
||||
"FAIL": "İndirme başarısız!",
|
||||
"PLAYLIST": {
|
||||
"SUCCESS": "Liste başarıyla indirildi",
|
||||
"FAIL": "Liste indirilemedi!"
|
||||
},
|
||||
"SERVER_CONFIG": {
|
||||
"FAIL": "Sunucu yapılandırması indirilemedi!"
|
||||
}
|
||||
},
|
||||
"SAVE": {
|
||||
"MAIN_CONFIG": {
|
||||
"SUCCESS": "Ana yapılandırma kaydedildi!",
|
||||
"FAIL": "Ana yapılandırma kaydedilemedi!"
|
||||
},
|
||||
"BOUQUET": {
|
||||
"SUCCESS": "Buket başarıyla kaydedildi",
|
||||
"FAIL": "Buket kaydedilemedi!"
|
||||
},
|
||||
"USER": {
|
||||
"SUCCESS": "Kullanıcı başarıyla kaydedildi",
|
||||
"FAIL": "Kullanıcı kaydedilemedi!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"INFO": {
|
||||
"RESTART_TO_APPLY_CHANGES": "Değişiklikleri uygulamak için yeniden başlatmanız gerekmektedir.",
|
||||
"SCHEDULE_EXAMPLE": "Program örneği",
|
||||
"THREADS": "İşlemciler: Eğer bir sorununuz yoksa 0 olarak bırakın. Birden fazla sağlayıcı kullanıyorsanız artırabilirsiniz. Sağlayıcınıza paralel erişim, yasaklanmanıza neden olabilir.",
|
||||
"WEB_ROOT": "Web klassörü: Web-UI dosyalarını içerir. Genellikle ./web altında aynı dizine yerleştirilir."
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
{
|
||||
"tab_title": "tuliprox",
|
||||
"app_title": "tuliprox",
|
||||
"app_logo": "assets/tuliprox-logo.svg",
|
||||
"tabTitle": "tuliprox",
|
||||
"appLogo": "/assets/tuliprox-logo.svg",
|
||||
"api": {
|
||||
"apiUrl": "/api/v1/",
|
||||
"authUrl": "/auth/"
|
||||
}
|
||||
},
|
||||
"discord": "https://discord.gg/gkzCmWw9Tf",
|
||||
"documentation": "https://github.com/euzu/tuliprox/blob/master/README.md",
|
||||
"wsUrl" : "/ws",
|
||||
"protocolVersion": 1
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="/">
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/assets/favicon.ico" />
|
||||
<link rel="css" href="%PUBLIC_URL%/assets/fonts.css" />
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!-- link rel="manifest" href="%PUBLIC_URL%/assets/manifest.json" / -->
|
||||
<title>tuliprox</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"short_name": "tuliprox",
|
||||
"name": "tuliprox",
|
||||
"icons": [
|
||||
{
|
||||
"src": "assets/favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -271,6 +271,30 @@
|
||||
--menu-icon-color-main_config: #FF2DF1;
|
||||
--menu-icon-color-status: #F9E400;
|
||||
|
||||
--code-identifier-color: #569cd6;
|
||||
--code-var-access-color: #9cdcfe;
|
||||
--code-field-color: #bb8953;
|
||||
--code-property-color: #9a652f;
|
||||
--code-literal-color: #65a181;
|
||||
--code-num-literal-color: #6796ba;
|
||||
--code-built-in-function-color: #fac46c;
|
||||
--code-reserved-color: #e98863;
|
||||
--code-regex-color: #bb8953;
|
||||
--code-enum-color: #65a181;
|
||||
--code-range-color: #6796ba;
|
||||
--code-op-color: #569cd6;
|
||||
--code-comparison-background-color: #202429;
|
||||
--code-comparison-color: #ffffff;
|
||||
--code-bracket-1-color: #569CD6;
|
||||
--code-bracket-2-color: #C586C0;
|
||||
--code-bracket-3-color: #B5CEA8;
|
||||
--code-editor-background-color: #1e1e1e;
|
||||
--code-editor-color: #d4d4d4;
|
||||
--code-any-match-color: #fa5f2d;
|
||||
--code-null-literal-color: #fa5f2d;
|
||||
|
||||
|
||||
|
||||
--toolbar-bg-color: #161b25;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
@forward "components/dialog";
|
||||
@forward "components/custom_dialog";
|
||||
@forward "components/filter";
|
||||
@forward "components/mapper_script";
|
||||
@forward "components/mapper_counter";
|
||||
@forward "components/userlist/user_state";
|
||||
@forward "components/userlist/proxy_type";
|
||||
@forward "components/userlist/max_connections";
|
||||
@@ -57,3 +59,4 @@
|
||||
@forward "components/loading_indicator";
|
||||
@forward "components/toastr";
|
||||
@forward "components/drop_down_icon_button";
|
||||
@forward "components/accordion";
|
||||
@@ -0,0 +1,7 @@
|
||||
.tp__accordion {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
//flex-shrink: 0;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
@@ -35,10 +35,10 @@
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
height: fit-content;
|
||||
overflow: hidden;
|
||||
// transition: max-height 0.5s ease;
|
||||
// max-height: 5000px;
|
||||
}
|
||||
+1
@@ -73,6 +73,7 @@
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding-top: var(--padding-small);
|
||||
gap: var(--gap-default);
|
||||
|
||||
&-left {
|
||||
+13
-14
@@ -5,8 +5,8 @@
|
||||
|
||||
.tp__filter__code {
|
||||
font-family: monospace;
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
background: var(--code-editor-background-color);
|
||||
color: var(--code-editor-color);
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
white-space: pre;
|
||||
@@ -14,38 +14,40 @@
|
||||
.binary_op-wrapper{
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.comparison {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
background-color: #202429;
|
||||
color: var(--code-comparison-color);
|
||||
background-color: var(--code-comparison-background-color);
|
||||
padding: var(--padding-micro);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.unary_op {
|
||||
font-weight: bold;
|
||||
color: #569cd6;
|
||||
color: var(--code-op-color);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.binary_op {
|
||||
font-weight: bold;
|
||||
color: #9cdcfe;
|
||||
color: var(--code-op-color);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.field {
|
||||
font-weight: bold;
|
||||
color: #fdb76c;
|
||||
color: var(--code-field-color);
|
||||
}
|
||||
|
||||
.regex {
|
||||
color: #bb8953;
|
||||
color: var(--code-regex-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.enum {
|
||||
color: #78ce89;
|
||||
color: var(--code-enum-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,12 +56,9 @@
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
//.bracket-1 { color: #c551ac; }
|
||||
//.bracket-2 { color: #70db39; }
|
||||
//.bracket-3 { color: #dbdb02; }
|
||||
.bracket-1 { color: #569CD6; }
|
||||
.bracket-2 { color: #C586C0; }
|
||||
.bracket-3 { color: #B5CEA8; }
|
||||
.bracket-1 { color: var(--code-bracket-1-color); }
|
||||
.bracket-2 { color: var(--code-bracket-2-color); }
|
||||
.bracket-3 { color: var(--code-bracket-3-color); }
|
||||
}
|
||||
.tp__filter__inline {
|
||||
.tp__filter__code {
|
||||
@@ -0,0 +1,20 @@
|
||||
.tp__mapper-counter {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
box-sizing: border-box;
|
||||
label {
|
||||
color: var(--modest-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tp__mapper-counter__inline {
|
||||
flex-flow: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
.tp__mapper-script {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
overflow: auto;
|
||||
|
||||
.tp__mapper-script__code {
|
||||
font-family: monospace;
|
||||
background: #1e1e1e;
|
||||
color: var(--modest-text-color);
|
||||
padding: var(--padding-default);
|
||||
border-radius: var(--border-radius);
|
||||
white-space: pre;
|
||||
|
||||
.identifier {
|
||||
color: var(--code-identifier-color);
|
||||
}
|
||||
|
||||
.var-access {
|
||||
color: var(--code-var-access-color);
|
||||
}
|
||||
|
||||
.field {
|
||||
color: var(--code-field-color);
|
||||
}
|
||||
|
||||
.property {
|
||||
color: var(--code-property-color);
|
||||
}
|
||||
|
||||
.literal {
|
||||
color: var(--code-literal-color);
|
||||
}
|
||||
|
||||
.num-literal {
|
||||
color: var(--code-num-literal-color);
|
||||
}
|
||||
|
||||
|
||||
.built-in-function {
|
||||
color: var(--code-built-in-function-color);
|
||||
}
|
||||
|
||||
.reserved {
|
||||
color: var(--code-reserved-color);
|
||||
}
|
||||
|
||||
.any-match {
|
||||
color: var(--code-any-match-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.range {
|
||||
color: var(--code-range-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.null-literal {
|
||||
color: var(--code-null-literal-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.bracket {
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.bracket-1 { color: #569CD6; }
|
||||
.bracket-2 { color: #C586C0; }
|
||||
.bracket-3 { color: #B5CEA8; }
|
||||
}
|
||||
.tp__mapper-script__inline {
|
||||
.tp__mapper-script__code {
|
||||
padding: 0;
|
||||
margin-block: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
@use '../../../../scss/size';
|
||||
|
||||
.tp__playlist-mappings {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
&__list {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: var(--gap-default);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__mapping {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: var(--gap-default);
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
&-section {
|
||||
display: flex;
|
||||
flex-flow: row ;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
gap: var(--gap-small);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&-mapper {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
gap: var(--gap-default);
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: var(--gap-default);
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
color: var(--modest-text-color);
|
||||
|
||||
&:after {
|
||||
content: ':'
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -1,7 +1,7 @@
|
||||
.tp__playlist-update-view {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
width: 100%;
|
||||
width: 80vw;
|
||||
gap: var(--gap-default);
|
||||
padding: var(--padding-default) 0;
|
||||
box-sizing: border-box;
|
||||
@@ -17,6 +17,7 @@
|
||||
&__body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
|
||||
gap: var(--gap-default);
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
+6
@@ -1,6 +1,12 @@
|
||||
@use "../../../size";
|
||||
|
||||
.tp__playlist-source-selector {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: var(--gap-default);
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
|
||||
&__source-picker .tp__collapse-panel__body {
|
||||
display: flex;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user