End of Day
This commit is contained in:
parent
43047ec499
commit
4dfd98da93
@ -157,7 +157,7 @@ final class CApp {
|
||||
|
||||
if (!str_starts_with($path, '/assets') && $path !== '/favicon.ico') {
|
||||
$lang = isset($_COOKIE['scmedia_lang']) ? (string)$_COOKIE['scmedia_lang'] : '';
|
||||
$token = (string)($_COOKIE['access_token'] ?? '');
|
||||
$token = (string)($_COOKIE['scmedia_access_token'] ?? '');
|
||||
if ($lang === '' && $token !== '' && isset($this->container->all()['auth'])) {
|
||||
/** @var \ScMedia\Services\AuthService $auth */
|
||||
$auth = $this->container->all()['auth'];
|
||||
|
||||
@ -65,10 +65,14 @@ final class AuthController extends BaseController {
|
||||
}
|
||||
$tokens = $res['tokens'] ?? [];
|
||||
if (!empty($tokens['access_token'])) {
|
||||
setcookie('access_token', $tokens['access_token'], [
|
||||
setcookie('scmedia_access_token', $tokens['access_token'], [
|
||||
'expires' => (int)($tokens['access_expires_at'] ?? 0),
|
||||
'path' => '/',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
setcookie('access_token', '', [
|
||||
'expires' => time() - 3600,
|
||||
'path' => '/',
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
}
|
||||
@ -96,10 +100,14 @@ final class AuthController extends BaseController {
|
||||
}
|
||||
$tokens = $res['tokens'] ?? [];
|
||||
if (!empty($tokens['access_token'])) {
|
||||
setcookie('access_token', $tokens['access_token'], [
|
||||
setcookie('scmedia_access_token', $tokens['access_token'], [
|
||||
'expires' => (int)($tokens['access_expires_at'] ?? 0),
|
||||
'path' => '/',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
setcookie('access_token', '', [
|
||||
'expires' => time() - 3600,
|
||||
'path' => '/',
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
}
|
||||
@ -120,10 +128,14 @@ final class AuthController extends BaseController {
|
||||
}
|
||||
$tokens = $res['tokens'] ?? [];
|
||||
if (!empty($tokens['access_token'])) {
|
||||
setcookie('access_token', $tokens['access_token'], [
|
||||
setcookie('scmedia_access_token', $tokens['access_token'], [
|
||||
'expires' => (int)($tokens['access_expires_at'] ?? 0),
|
||||
'path' => '/',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
setcookie('access_token', '', [
|
||||
'expires' => time() - 3600,
|
||||
'path' => '/',
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
}
|
||||
@ -138,16 +150,20 @@ final class AuthController extends BaseController {
|
||||
if ($refresh !== '') {
|
||||
$auth->logout($refresh);
|
||||
} else {
|
||||
$token = (string)($_COOKIE['access_token'] ?? '');
|
||||
$token = (string)($_COOKIE['scmedia_access_token'] ?? '');
|
||||
$auth->logoutByAccessToken($token);
|
||||
}
|
||||
setcookie('scmedia_access_token', '', [
|
||||
'expires' => time() - 3600,
|
||||
'path' => '/',
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
setcookie('access_token', '', [
|
||||
'expires' => time() - 3600,
|
||||
'path' => '/',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
setcookie('sse_key', '', [
|
||||
setcookie('scmedia_sse_key', '', [
|
||||
'expires' => time() - 3600,
|
||||
'path' => '/',
|
||||
'httponly' => true,
|
||||
@ -225,7 +241,7 @@ final class AuthController extends BaseController {
|
||||
$user = $auth->requireAuth();
|
||||
$key = $auth->issueSseKey((int)$user['id']);
|
||||
$ttl = (int)($key['expires_in'] ?? 60);
|
||||
setcookie('sse_key', $key['key'], [
|
||||
setcookie('scmedia_sse_key', $key['key'], [
|
||||
'expires' => time() + max(10, $ttl),
|
||||
'path' => '/',
|
||||
'httponly' => true,
|
||||
|
||||
@ -107,7 +107,7 @@ final class EventsController extends BaseController {
|
||||
|
||||
// Read and validate SSE key.
|
||||
private function readUserId(): ?int {
|
||||
$key = isset($_COOKIE['sse_key']) ? (string)$_COOKIE['sse_key'] : '';
|
||||
$key = isset($_COOKIE['scmedia_sse_key']) ? (string)$_COOKIE['scmedia_sse_key'] : '';
|
||||
$key = trim($key, "\"'");
|
||||
$key = urldecode($key);
|
||||
$userId = $this->auth->validateSseKey($key);
|
||||
|
||||
@ -988,7 +988,7 @@ final class AuthService {
|
||||
if (preg_match('/^Bearer\s+(.+)$/i', $header, $m)) {
|
||||
return trim($m[1]);
|
||||
}
|
||||
$cookie = $_COOKIE['access_token'] ?? '';
|
||||
$cookie = $_COOKIE['scmedia_access_token'] ?? '';
|
||||
if (is_string($cookie) && $cookie !== '') {
|
||||
return $cookie;
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@ final class SettingsService {
|
||||
'layout' => $map['layout'] ?? [],
|
||||
'media_rules' => $map['media_rules'] ?? [],
|
||||
'rules' => $map['rules'] ?? [],
|
||||
'templates' => $map['templates'] ?? [],
|
||||
'sources' => $map['sources'] ?? [],
|
||||
'metadata' => $map['metadata'] ?? [],
|
||||
'exports' => $map['exports'] ?? [],
|
||||
@ -67,7 +68,7 @@ final class SettingsService {
|
||||
throw new Exception("Settings revision mismatch");
|
||||
}
|
||||
|
||||
$allowed = ['general', 'scanner_defaults', 'paths', 'tools', 'logs', 'layout', 'media_rules', 'rules', 'sources', 'metadata', 'exports', 'ui', 'background', 'safety', 'tasks', 'pending_tasks'];
|
||||
$allowed = ['general', 'scanner_defaults', 'paths', 'tools', 'logs', 'layout', 'media_rules', 'rules', 'templates', 'sources', 'metadata', 'exports', 'ui', 'background', 'safety', 'tasks', 'pending_tasks'];
|
||||
$toSave = [];
|
||||
foreach ($allowed as $k) {
|
||||
if (array_key_exists($k, $payload)) {
|
||||
|
||||
@ -27,17 +27,16 @@ $toolbarRightHtml = '';
|
||||
|
||||
<main class="layout">
|
||||
<nav class="tabs">
|
||||
<button class="tab active" data-tab="scan" data-i18n="settings.tabs.scan_profiles"><?php echo htmlspecialchars($t('settings.tabs.scan_profiles', 'Scan Profiles'), ENT_QUOTES); ?></button>
|
||||
<button class="tab" data-tab="layout" data-i18n="settings.tabs.library_layout"><?php echo htmlspecialchars($t('settings.tabs.library_layout', 'Library'), ENT_QUOTES); ?></button>
|
||||
<button class="tab active" data-tab="server" data-i18n="settings.tabs.server"><?php echo htmlspecialchars($t('settings.tabs.server', 'Server'), ENT_QUOTES); ?></button>
|
||||
<button class="tab" data-tab="plugins" data-i18n="settings.tabs.plugins"><?php echo htmlspecialchars($t('settings.tabs.plugins', 'Plugins'), ENT_QUOTES); ?></button>
|
||||
<button class="tab" data-tab="tasks" data-i18n="settings.tabs.tasks"><?php echo htmlspecialchars($t('settings.tabs.tasks', 'Tasks'), ENT_QUOTES); ?></button>
|
||||
<button class="tab" data-tab="sources" data-i18n="settings.tabs.sources"><?php echo htmlspecialchars($t('settings.tabs.sources', 'Sources'), ENT_QUOTES); ?></button>
|
||||
<button class="tab" data-tab="rules" data-i18n="settings.tabs.rules"><?php echo htmlspecialchars($t('settings.tabs.rules', 'Rules'), ENT_QUOTES); ?></button>
|
||||
<button class="tab" data-tab="server" data-i18n="settings.tabs.server"><?php echo htmlspecialchars($t('settings.tabs.server', 'Server'), ENT_QUOTES); ?></button>
|
||||
<button class="tab" data-tab="templates" data-i18n="settings.tabs.templates"><?php echo htmlspecialchars($t('settings.tabs.templates', 'Templates'), ENT_QUOTES); ?></button>
|
||||
<button class="tab" data-tab="tasks" data-i18n="settings.tabs.tasks"><?php echo htmlspecialchars($t('settings.tabs.tasks', 'Tasks'), ENT_QUOTES); ?></button>
|
||||
<button class="tab" data-tab="tools" data-i18n="settings.tabs.tools"><?php echo htmlspecialchars($t('settings.tabs.tools', 'Programs'), ENT_QUOTES); ?></button>
|
||||
<button class="tab" data-tab="logs" data-i18n="settings.tabs.logs"><?php echo htmlspecialchars($t('settings.tabs.logs', 'Logs'), ENT_QUOTES); ?></button>
|
||||
<button class="tab" data-tab="admin" data-i18n="admin.title"><?php echo htmlspecialchars($t('admin.title', 'Admin'), ENT_QUOTES); ?></button>
|
||||
<button class="tab" data-tab="debug" id="tabDebug"<?php echo empty($debugToolsEnabled) ? ' style="display:none;"' : ''; ?> data-i18n="settings.tabs.debug"><?php echo htmlspecialchars($t('settings.tabs.debug', 'Debug'), ENT_QUOTES); ?></button>
|
||||
<button class="tab" data-tab="about" data-i18n="settings.tabs.about"><?php echo htmlspecialchars($t('settings.tabs.about', 'About'), ENT_QUOTES); ?></button>
|
||||
<?php
|
||||
$statusHidden = false;
|
||||
$statusClass = $statusHidden ? 'status hidden' : 'status';
|
||||
@ -52,86 +51,11 @@ $toolbarRightHtml = '';
|
||||
</nav>
|
||||
|
||||
<section class="panel">
|
||||
<!-- ===================== SCAN TAB ===================== -->
|
||||
<div class="tabpane active" id="pane-scan">
|
||||
<!-- ===================== SOURCES TAB ===================== -->
|
||||
<div class="tabpane" id="pane-sources">
|
||||
<div class="pane-header">
|
||||
<h2 data-i18n="settings.scan_profiles.title"><?php echo htmlspecialchars($t('settings.scan_profiles.title', 'Scan Profiles'), ENT_QUOTES); ?></h2>
|
||||
<button id="btnAddProfile" class="btn primary" data-i18n="settings.scan_profiles.add"><?php echo htmlspecialchars($t('settings.scan_profiles.add', 'Add profile'), ENT_QUOTES); ?></button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<template id="tplProfileRow">
|
||||
<tr>
|
||||
<td><button class="drag-handle" type="button" data-action="drag" data-i18n-aria="settings.scan_profiles.move">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||
<path fill="currentColor" d="M7 5h10v2H7V5zm0 6h10v2H7v-2zm0 6h10v2H7v-2z"/>
|
||||
</svg>
|
||||
</button></td>
|
||||
<td><input type="checkbox" data-field="enabled"></td>
|
||||
<td data-field="profile_type"></td>
|
||||
<td data-field="name"></td>
|
||||
<td><code data-field="root_path"></code></td>
|
||||
<td data-field="max_depth"></td>
|
||||
<td data-field="exclude"></td>
|
||||
<td data-field="ext"></td>
|
||||
<td data-field="last_scan"></td>
|
||||
<td data-field="last_result"></td>
|
||||
<td>
|
||||
<button class="btn" data-action="edit" data-i18n="common.edit"><?php echo htmlspecialchars($t('common.edit', 'Edit'), ENT_QUOTES); ?></button>
|
||||
<button class="btn" data-action="del" data-i18n="common.delete"><?php echo htmlspecialchars($t('common.delete', 'Delete'), ENT_QUOTES); ?></button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<?php
|
||||
$id = 'profilesTable';
|
||||
$tableClass = 'data-table';
|
||||
$headers = [
|
||||
['label' => '', 'i18n' => '', 'key' => '', 'filter' => null],
|
||||
['label' => $t('settings.scan_profiles.th_on', 'On'), 'i18n' => 'settings.scan_profiles.th_on', 'key' => 'enabled', 'filter' => ['type' => 'boolean']],
|
||||
['label' => $t('settings.scan_profiles.th_type', 'Type'), 'i18n' => 'settings.scan_profiles.th_type', 'key' => 'profile_type', 'filter' => ['type' => 'text', 'ops' => ['like','eq']]],
|
||||
['label' => $t('settings.scan_profiles.th_name', 'Name'), 'i18n' => 'settings.scan_profiles.th_name', 'sort' => 'name', 'key' => 'name', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('settings.scan_profiles.th_root', 'Root path'), 'i18n' => 'settings.scan_profiles.th_root', 'key' => 'root_path', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('settings.scan_profiles.th_depth', 'Depth'), 'i18n' => 'settings.scan_profiles.th_depth', 'key' => 'max_depth', 'filter' => ['type' => 'number', 'ops' => ['eq','between','gt','lt']]],
|
||||
['label' => $t('settings.scan_profiles.th_excludes', 'Excludes'), 'i18n' => 'settings.scan_profiles.th_excludes', 'key' => 'exclude', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('settings.scan_profiles.th_ext', 'Ext'), 'i18n' => 'settings.scan_profiles.th_ext', 'key' => 'ext', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('settings.scan_profiles.th_last_scan', 'Last scan'), 'i18n' => 'settings.scan_profiles.th_last_scan', 'key' => 'last_scan', 'filter' => ['type' => 'date', 'ops' => ['eq','between']]],
|
||||
['label' => $t('settings.scan_profiles.th_result', 'Result'), 'i18n' => 'settings.scan_profiles.th_result', 'key' => 'last_result', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => '', 'i18n' => '', 'key' => '', 'filter' => null],
|
||||
];
|
||||
$attrs = ['data-table' => 'profiles'];
|
||||
require __DIR__ . '/../partials/table.php';
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3 data-i18n="settings.scanner_defaults.title"><?php echo htmlspecialchars($t('settings.scanner_defaults.title', 'Global scanner defaults'), ENT_QUOTES); ?></h3>
|
||||
<div class="grid">
|
||||
<label>
|
||||
<div class="lbl" data-i18n="settings.scanner_defaults.video_ext"><?php echo htmlspecialchars($t('settings.scanner_defaults.video_ext', 'Video extensions (comma)'), ENT_QUOTES); ?></div>
|
||||
<input id="videoExt" type="text" placeholder="mkv,mp4,avi" data-i18n-placeholder="settings.scanner_defaults.video_ext_ph">
|
||||
</label>
|
||||
<label>
|
||||
<div class="lbl" data-i18n="settings.scanner_defaults.max_depth"><?php echo htmlspecialchars($t('settings.scanner_defaults.max_depth', 'Max depth default'), ENT_QUOTES); ?></div>
|
||||
<input id="maxDepthDefault" type="number" min="1" max="10">
|
||||
</label>
|
||||
<label>
|
||||
<div class="lbl" data-i18n="settings.scanner_defaults.max_files"><?php echo htmlspecialchars($t('settings.scanner_defaults.max_files', 'Max files per item'), ENT_QUOTES); ?></div>
|
||||
<input id="maxFilesPerItem" type="number" min="100" max="200000">
|
||||
</label>
|
||||
<label>
|
||||
<div class="lbl" data-i18n="settings.scanner_defaults.max_items"><?php echo htmlspecialchars($t('settings.scanner_defaults.max_items', 'Max items per scan (0 = no limit)'), ENT_QUOTES); ?></div>
|
||||
<input id="maxItemsPerScan" type="number" min="0" max="1000000">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===================== LAYOUT TAB ===================== -->
|
||||
<div class="tabpane" id="pane-layout">
|
||||
<div class="pane-header">
|
||||
<h2 data-i18n="settings.library_layout.title"><?php echo htmlspecialchars($t('settings.library_layout.title', 'Library Layout'), ENT_QUOTES); ?></h2>
|
||||
<button id="btnAddRoot" class="btn primary" data-i18n="settings.library_layout.add_root"><?php echo htmlspecialchars($t('settings.library_layout.add_root', 'Add root'), ENT_QUOTES); ?></button>
|
||||
<h2 data-i18n="settings.sources.title"><?php echo htmlspecialchars($t('settings.sources.title', 'Sources'), ENT_QUOTES); ?></h2>
|
||||
<button id="btnAddRoot" class="btn primary" data-i18n="settings.sources.add_root"><?php echo htmlspecialchars($t('settings.sources.add_root', 'Add source'), ENT_QUOTES); ?></button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
@ -139,9 +63,9 @@ $toolbarRightHtml = '';
|
||||
$id = 'rootsTable';
|
||||
$tableClass = 'data-table';
|
||||
$headers = [
|
||||
['label' => $t('settings.library_layout.th_type', 'Type'), 'i18n' => 'settings.library_layout.th_type', 'key' => 'type', 'filter' => ['type' => 'text', 'ops' => ['like','eq']]],
|
||||
['label' => $t('settings.library_layout.th_path', 'Path'), 'i18n' => 'settings.library_layout.th_path', 'sort' => 'path', 'key' => 'path', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('settings.library_layout.th_status', 'Status'), 'i18n' => 'settings.library_layout.th_status', 'key' => 'status', 'filter' => ['type' => 'text', 'ops' => ['like','eq']]],
|
||||
['label' => $t('settings.sources.th_type', 'Type'), 'i18n' => 'settings.sources.th_type', 'sort' => 'type', 'key' => 'type', 'filter' => ['type' => 'text', 'ops' => ['like','eq']]],
|
||||
['label' => $t('settings.sources.th_path', 'Path'), 'i18n' => 'settings.sources.th_path', 'sort' => 'path', 'key' => 'path', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('settings.sources.th_status', 'Status'), 'i18n' => 'settings.sources.th_status', 'sort' => 'status', 'key' => 'status', 'filter' => ['type' => 'text', 'ops' => ['like','eq']]],
|
||||
['label' => '', 'i18n' => '', 'key' => '', 'filter' => null],
|
||||
];
|
||||
$attrs = ['data-table' => 'roots'];
|
||||
@ -192,7 +116,8 @@ $toolbarRightHtml = '';
|
||||
$headers = [
|
||||
['label' => $t('settings.tasks.th_name', 'Task'), 'i18n' => 'settings.tasks.th_name', 'sort' => 'name', 'key' => 'name', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('settings.tasks.th_sources', 'Sources'), 'i18n' => 'settings.tasks.th_sources', 'key' => 'sources', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('settings.tasks.th_actions', 'Actions'), 'i18n' => 'settings.tasks.th_actions', 'key' => 'actions', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('settings.tasks.th_rules', 'Rules'), 'i18n' => 'settings.tasks.th_rules', 'key' => 'rules', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('settings.tasks.th_template', 'Template'), 'i18n' => 'settings.tasks.th_template', 'key' => 'template', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('settings.tasks.th_status', 'Status'), 'i18n' => 'settings.tasks.th_status', 'key' => 'status', 'filter' => ['type' => 'text', 'ops' => ['like','eq']]],
|
||||
['label' => '', 'i18n' => '', 'key' => '', 'filter' => null],
|
||||
];
|
||||
@ -207,26 +132,7 @@ $toolbarRightHtml = '';
|
||||
<div class="tabpane" id="pane-rules">
|
||||
<div class="pane-header">
|
||||
<h2 data-i18n="settings.rules.title"><?php echo htmlspecialchars($t('settings.rules.title', 'Rules'), ENT_QUOTES); ?></h2>
|
||||
<div class="row">
|
||||
<div class="dropdown">
|
||||
<button class="btn primary" id="btnAddRule" data-i18n="settings.rules.add_rule"><?php echo htmlspecialchars($t('settings.rules.add_rule', 'Add rule'), ENT_QUOTES); ?></button>
|
||||
<div class="dropdown-menu" id="ruleTypeMenu">
|
||||
<button type="button" data-rule-type="name_map" data-i18n="rules.type.name_map"><?php echo htmlspecialchars($t('rules.type.name_map', 'Name mapping'), ENT_QUOTES); ?></button>
|
||||
<button type="button" data-rule-type="delete_track" data-i18n="rules.type.delete_track"><?php echo htmlspecialchars($t('rules.type.delete_track', 'Delete tracks'), ENT_QUOTES); ?></button>
|
||||
<button type="button" data-rule-type="priorities" data-i18n="rules.type.priorities"><?php echo htmlspecialchars($t('rules.type.priorities', 'Priorities'), ENT_QUOTES); ?></button>
|
||||
<button type="button" data-rule-type="lang_fix" data-i18n="rules.type.lang_fix"><?php echo htmlspecialchars($t('rules.type.lang_fix', 'Language fix'), ENT_QUOTES); ?></button>
|
||||
<button type="button" data-rule-type="source_filter" data-i18n="rules.type.source_filter"><?php echo htmlspecialchars($t('rules.type.source_filter', 'Source filter'), ENT_QUOTES); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
<label class="rule-sort">
|
||||
<span class="lbl" data-i18n="rules.sort_by"><?php echo htmlspecialchars($t('rules.sort_by', 'Sort by'), ENT_QUOTES); ?></span>
|
||||
<select id="rulesSortField">
|
||||
<option value="name" data-i18n="rules.sort.name"><?php echo htmlspecialchars($t('rules.sort.name', 'Name'), ENT_QUOTES); ?></option>
|
||||
<option value="type" data-i18n="rules.sort.type"><?php echo htmlspecialchars($t('rules.sort.type', 'Type'), ENT_QUOTES); ?></option>
|
||||
</select>
|
||||
<button class="btn" id="rulesSortDir" type="button">↑</button>
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn primary" id="btnAddRule" data-i18n="settings.rules.add_rule"><?php echo htmlspecialchars($t('settings.rules.add_rule', 'Add rule'), ENT_QUOTES); ?></button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
@ -235,8 +141,8 @@ $toolbarRightHtml = '';
|
||||
$tableClass = 'data-table';
|
||||
$headers = [
|
||||
['label' => $t('rules.th.name', 'Name'), 'i18n' => 'rules.th.name', 'sort' => 'name', 'key' => 'name', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('rules.th.type', 'Type'), 'i18n' => 'rules.th.type', 'sort' => 'type', 'key' => 'type', 'filter' => ['type' => 'text', 'ops' => ['like','eq']]],
|
||||
['label' => $t('rules.th.summary', 'Summary'), 'i18n' => 'rules.th.summary', 'key' => 'summary', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('rules.th.tags', 'Tags'), 'i18n' => 'rules.th.tags', 'key' => 'tags', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('rules.th.conditions', 'Conditions'), 'i18n' => 'rules.th.conditions', 'key' => 'conditions', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('rules.th.status', 'Status'), 'i18n' => 'rules.th.status', 'key' => 'status', 'filter' => ['type' => 'text', 'ops' => ['like','eq']]],
|
||||
['label' => '', 'i18n' => '', 'key' => '', 'filter' => null],
|
||||
];
|
||||
@ -246,8 +152,31 @@ $toolbarRightHtml = '';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===================== TEMPLATES TAB ===================== -->
|
||||
<div class="tabpane" id="pane-templates">
|
||||
<div class="pane-header">
|
||||
<h2 data-i18n="settings.templates.title"><?php echo htmlspecialchars($t('settings.templates.title', 'Templates'), ENT_QUOTES); ?></h2>
|
||||
<button id="btnAddTemplate" class="btn primary" data-i18n="settings.templates.add"><?php echo htmlspecialchars($t('settings.templates.add', 'Add template'), ENT_QUOTES); ?></button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<?php
|
||||
$id = 'templatesTable';
|
||||
$tableClass = 'data-table';
|
||||
$headers = [
|
||||
['label' => $t('settings.templates.th_name', 'Template'), 'i18n' => 'settings.templates.th_name', 'sort' => 'name', 'key' => 'name', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('settings.templates.th_action', 'Action'), 'i18n' => 'settings.templates.th_action', 'key' => 'action', 'filter' => ['type' => 'text', 'ops' => ['like','eq','empty']]],
|
||||
['label' => $t('settings.templates.th_status', 'Status'), 'i18n' => 'settings.templates.th_status', 'key' => 'status', 'filter' => ['type' => 'text', 'ops' => ['like','eq']]],
|
||||
['label' => '', 'i18n' => '', 'key' => '', 'filter' => null],
|
||||
];
|
||||
$attrs = ['data-table' => 'templates'];
|
||||
require __DIR__ . '/../partials/table.php';
|
||||
?>
|
||||
<div class="hint" id="templatesHint"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===================== SERVER TAB ===================== -->
|
||||
<div class="tabpane" id="pane-server">
|
||||
<div class="tabpane active" id="pane-server">
|
||||
<div class="pane-header">
|
||||
<h2 data-i18n="settings.server.title"><?php echo htmlspecialchars($t('settings.server.title', 'Server'), ENT_QUOTES); ?></h2>
|
||||
</div>
|
||||
@ -391,6 +320,41 @@ $toolbarRightHtml = '';
|
||||
<div class="card">
|
||||
<h3 data-i18n="settings.server.danger"><?php echo htmlspecialchars($t('settings.server.danger', 'Danger zone'), ENT_QUOTES); ?></h3>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3 data-i18n="settings.about.title"><?php echo htmlspecialchars($t('settings.about.title', 'About'), ENT_QUOTES); ?></h3>
|
||||
<div class="about-card" data-version>
|
||||
<div class="about-row">
|
||||
<img class="about-logo" src="/assets/icons/scmedia.png" alt="scMedia">
|
||||
<div class="about-body">
|
||||
<div class="about-title">
|
||||
<a class="about-link" href="https://safe-cap.com/software/scMedia">scMedia</a>
|
||||
<span class="version">v0.0.0</span>
|
||||
</div>
|
||||
<div class="about-meta">
|
||||
<span class="about-company">SAFE-CAP</span>
|
||||
<a class="about-link muted" href="https://safe-cap.com/">safe-cap.com</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<?php
|
||||
$id = 'aboutPluginsTable';
|
||||
$tableClass = 'table';
|
||||
$headers = [
|
||||
['label' => $t('settings.about.table_name', 'Name'), 'i18n' => 'settings.about.table_name'],
|
||||
['label' => $t('settings.about.table_type', 'Type'), 'i18n' => 'settings.about.table_type'],
|
||||
['label' => $t('settings.about.table_author', 'Author'), 'i18n' => 'settings.about.table_author'],
|
||||
['label' => $t('settings.about.table_version', 'Version'), 'i18n' => 'settings.about.table_version'],
|
||||
['label' => $t('settings.about.table_update', 'Update'), 'i18n' => 'settings.about.table_update'],
|
||||
];
|
||||
$footer = false;
|
||||
require __DIR__ . '/../partials/table.php';
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===================== TOOLS TAB ===================== -->
|
||||
@ -657,102 +621,9 @@ $toolbarRightHtml = '';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===================== ABOUT TAB ===================== -->
|
||||
<div class="tabpane" id="pane-about">
|
||||
<div class="pane-header">
|
||||
<h2 data-i18n="settings.about.title"><?php echo htmlspecialchars($t('settings.about.title', 'About'), ENT_QUOTES); ?></h2>
|
||||
</div>
|
||||
<div class="card about-card" data-version>
|
||||
<div class="about-row">
|
||||
<img class="about-logo" src="/assets/icons/scmedia.png" alt="scMedia">
|
||||
<div class="about-body">
|
||||
<div class="about-title">
|
||||
<a class="about-link" href="https://safe-cap.com/software/scMedia">scMedia</a>
|
||||
<span class="version">v0.0.0</span>
|
||||
</div>
|
||||
<div class="about-meta">
|
||||
<span class="about-company">SAFE-CAP</span>
|
||||
<a class="about-link muted" href="https://safe-cap.com/">safe-cap.com</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<?php
|
||||
$id = 'aboutPluginsTable';
|
||||
$tableClass = 'table';
|
||||
$headers = [
|
||||
['label' => $t('settings.about.table_name', 'Name'), 'i18n' => 'settings.about.table_name'],
|
||||
['label' => $t('settings.about.table_type', 'Type'), 'i18n' => 'settings.about.table_type'],
|
||||
['label' => $t('settings.about.table_author', 'Author'), 'i18n' => 'settings.about.table_author'],
|
||||
['label' => $t('settings.about.table_version', 'Version'), 'i18n' => 'settings.about.table_version'],
|
||||
['label' => $t('settings.about.table_update', 'Update'), 'i18n' => 'settings.about.table_update'],
|
||||
];
|
||||
$footer = false;
|
||||
require __DIR__ . '/../partials/table.php';
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div id="modal" class="modal" style="display:none;">
|
||||
<div class="modal-card">
|
||||
<div class="modal-head">
|
||||
<div class="modal-title" id="modalTitle"><?php echo htmlspecialchars($t('settings.scan_profiles.modal_edit', 'Edit profile'), ENT_QUOTES); ?></div>
|
||||
<button id="modalClose" class="btn" data-i18n="common.close"><?php echo htmlspecialchars($t('common.close', 'Close'), ENT_QUOTES); ?></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="grid">
|
||||
<label>
|
||||
<div class="lbl" data-i18n="settings.scan_profiles.modal_name"><?php echo htmlspecialchars($t('settings.scan_profiles.modal_name', 'Name'), ENT_QUOTES); ?></div>
|
||||
<input id="pName" type="text">
|
||||
</label>
|
||||
<label>
|
||||
<div class="lbl" data-i18n="settings.scan_profiles.modal_root"><?php echo htmlspecialchars($t('settings.scan_profiles.modal_root', 'Root path'), ENT_QUOTES); ?></div>
|
||||
<input id="pRoot" type="text">
|
||||
</label>
|
||||
<label>
|
||||
<div class="lbl" data-i18n="settings.scan_profiles.modal_depth"><?php echo htmlspecialchars($t('settings.scan_profiles.modal_depth', 'Max depth'), ENT_QUOTES); ?></div>
|
||||
<input id="pDepth" type="number" min="1" max="10">
|
||||
</label>
|
||||
<label>
|
||||
<div class="lbl" data-i18n="settings.scan_profiles.modal_type"><?php echo htmlspecialchars($t('settings.scan_profiles.modal_type', 'Profile type'), ENT_QUOTES); ?></div>
|
||||
<select id="pType">
|
||||
<option value="scan" data-i18n="settings.scan_profiles.type_scan"><?php echo htmlspecialchars($t('settings.scan_profiles.type_scan', 'Scan'), ENT_QUOTES); ?></option>
|
||||
<option value="analyze" data-i18n="settings.scan_profiles.type_analyze"><?php echo htmlspecialchars($t('settings.scan_profiles.type_analyze', 'Analyze'), ENT_QUOTES); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<div class="lbl" data-i18n="settings.scan_profiles.modal_enabled"><?php echo htmlspecialchars($t('settings.scan_profiles.modal_enabled', 'Enabled'), ENT_QUOTES); ?></div>
|
||||
<select id="pEnabled">
|
||||
<option value="1" data-i18n="common.yes"><?php echo htmlspecialchars($t('common.yes', 'Yes'), ENT_QUOTES); ?></option>
|
||||
<option value="0" data-i18n="common.no"><?php echo htmlspecialchars($t('common.no', 'No'), ENT_QUOTES); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<div class="lbl" data-i18n="settings.scan_profiles.modal_excludes"><?php echo htmlspecialchars($t('settings.scan_profiles.modal_excludes', 'Exclude patterns'), ENT_QUOTES); ?></div>
|
||||
<input id="pExcludes" type="text">
|
||||
</label>
|
||||
<label>
|
||||
<div class="lbl" data-i18n="settings.scan_profiles.modal_ext_mode"><?php echo htmlspecialchars($t('settings.scan_profiles.modal_ext_mode', 'Include ext mode'), ENT_QUOTES); ?></div>
|
||||
<select id="pExtMode">
|
||||
<option value="default" data-i18n="settings.scan_profiles.ext_default"><?php echo htmlspecialchars($t('settings.scan_profiles.ext_default', 'default'), ENT_QUOTES); ?></option>
|
||||
<option value="custom" data-i18n="settings.scan_profiles.ext_custom"><?php echo htmlspecialchars($t('settings.scan_profiles.ext_custom', 'custom'), ENT_QUOTES); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
<label id="pExtCustomWrap" style="display:none;">
|
||||
<div class="lbl" data-i18n="settings.scan_profiles.modal_ext_custom"><?php echo htmlspecialchars($t('settings.scan_profiles.modal_ext_custom', 'Custom extensions'), ENT_QUOTES); ?></div>
|
||||
<input id="pExtCustom" type="text">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-foot">
|
||||
<button id="modalSave" class="btn primary" data-i18n="common.save"><?php echo htmlspecialchars($t('common.save', 'Save'), ENT_QUOTES); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ruleModal" class="modal" style="display:none;">
|
||||
<div class="modal-card">
|
||||
<div class="modal-head">
|
||||
|
||||
@ -204,6 +204,8 @@ input, select {
|
||||
z-index: 20;
|
||||
display: none;
|
||||
min-width: 200px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
margin-top: 6px;
|
||||
padding: 6px;
|
||||
border: 1px solid var(--border);
|
||||
@ -448,6 +450,9 @@ input, select {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
.modal-head, .modal-foot {
|
||||
@ -470,6 +475,21 @@ input, select {
|
||||
|
||||
.modal-body {
|
||||
padding: 12px;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.root-row {
|
||||
display: grid;
|
||||
grid-template-columns: 140px minmax(240px, 1fr) 140px;
|
||||
gap: 10px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.root-row label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
|
||||
@ -78,11 +78,16 @@
|
||||
"settings.tasks.modal_title": "Aufgabe",
|
||||
"settings.tasks.field_name": "Name",
|
||||
"settings.tasks.field_sources": "Quellen",
|
||||
"settings.tasks.field_actions": "Aktionen",
|
||||
"settings.tasks.field_rules": "Regeln",
|
||||
"settings.tasks.field_template": "Vorlage",
|
||||
"settings.tasks.field_enabled": "Aktiv",
|
||||
"settings.tasks.source.library": "Bibliothek",
|
||||
"settings.tasks.hint": "Quellen und Regeln bestimmen die Daten, die Vorlage definiert die Aktion.",
|
||||
"settings.tasks.source.movie": "Filme",
|
||||
"settings.tasks.source.series": "Serien",
|
||||
"settings.tasks.source.transmission": "Transmission",
|
||||
"settings.tasks.source.staging": "Staging",
|
||||
"settings.tasks.rules_empty": "Noch keine Regeln",
|
||||
"settings.tasks.template_none": "Keine Vorlage",
|
||||
"settings.tasks.action.analyze": "Analyse",
|
||||
"settings.tasks.action.identify": "Identifizieren",
|
||||
"settings.tasks.action.normalize": "Normalisieren",
|
||||
@ -146,8 +151,10 @@
|
||||
"settings.title": "Einstellungen",
|
||||
"settings.back": "Zurück",
|
||||
"settings.tabs.scan_profiles": "Scan-Profile",
|
||||
"settings.tabs.library_layout": "Bibliothek",
|
||||
"settings.tabs.library": "Bibliothek",
|
||||
"settings.tabs.sources": "Quellen",
|
||||
"settings.tabs.plugins": "Plugins",
|
||||
"settings.tabs.templates": "Vorlagen",
|
||||
"settings.tabs.tasks": "Aufgaben",
|
||||
"settings.tabs.rules": "Regeln",
|
||||
"settings.tabs.tools": "Programme",
|
||||
@ -162,15 +169,34 @@
|
||||
"settings.scan_profiles.confirm_delete": "Profil löschen?",
|
||||
"settings.scan_profiles.ext_default": "Standard",
|
||||
"settings.scanner_defaults.title": "Globale Scanner-Defaults",
|
||||
"settings.library_layout.title": "Library",
|
||||
"settings.library_layout.preview": "Vorschau",
|
||||
"settings.library_layout.roots": "Ordner",
|
||||
"settings.library_layout.add_root": "Hinzufuegen",
|
||||
"settings.library_layout.th_type": "Typ",
|
||||
"settings.library_layout.th_path": "Pfad",
|
||||
"settings.library_layout.th_status": "Status",
|
||||
"settings.library_layout.modal_title": "Ordner",
|
||||
"settings.library_layout.confirm_delete": "Ordner löschen?",
|
||||
"settings.library.title": "Library",
|
||||
"settings.library.preview": "Vorschau",
|
||||
"settings.library.roots": "Ordner",
|
||||
"settings.library.add_root": "Hinzufuegen",
|
||||
"settings.library.th_type": "Typ",
|
||||
"settings.library.th_path": "Pfad",
|
||||
"settings.library.th_status": "Status",
|
||||
"settings.library.modal_title": "Ordner",
|
||||
"settings.library.confirm_delete": "Ordner löschen?",
|
||||
"settings.sources.title": "Quellen",
|
||||
"settings.sources.add_root": "Quelle hinzufügen",
|
||||
"settings.sources.th_type": "Typ",
|
||||
"settings.sources.th_path": "Pfad",
|
||||
"settings.sources.th_status": "Status",
|
||||
"settings.sources.modal_title": "Quelle",
|
||||
"settings.sources.confirm_delete": "Quelle löschen?",
|
||||
"settings.templates.title": "Vorlagen",
|
||||
"settings.templates.add": "Vorlage hinzufügen",
|
||||
"settings.templates.th_name": "Vorlage",
|
||||
"settings.templates.th_action": "Aktion",
|
||||
"settings.templates.th_status": "Status",
|
||||
"settings.templates.modal_title": "Vorlage",
|
||||
"settings.templates.field_name": "Name",
|
||||
"settings.templates.field_action": "Aktion",
|
||||
"settings.templates.field_enabled": "Aktiv",
|
||||
"settings.templates.hint": "Beschreibe die Aktion (Mapping, Konvertieren, Normalisieren, Export).",
|
||||
"settings.templates.confirm_delete": "Vorlage löschen?",
|
||||
"settings.templates.unnamed": "Ohne Namen",
|
||||
"root.type.movie": "Filme",
|
||||
"root.type.series": "Serien",
|
||||
"root.type.staging": "Staging",
|
||||
@ -332,21 +358,21 @@
|
||||
"settings.scanner_defaults.max_files": "Max. Dateien pro Element",
|
||||
"settings.scanner_defaults.max_items": "Max. Elemente pro Scan (0 = ohne Limit)",
|
||||
|
||||
"settings.library_layout.movies_root": "Filme-Ordner",
|
||||
"settings.library_layout.movies_root_ph": "/mnt/media/library/movies",
|
||||
"settings.library_layout.series_root": "Serien-Ordner",
|
||||
"settings.library_layout.series_root_ph": "/mnt/media/library/series",
|
||||
"settings.library_layout.staging_root": "Staging (optional)",
|
||||
"settings.library_layout.staging_root_ph": "/mnt/media/.staging",
|
||||
"settings.library.movies_root": "Filme-Ordner",
|
||||
"settings.library.movies_root_ph": "/mnt/media/library/movies",
|
||||
"settings.library.series_root": "Serien-Ordner",
|
||||
"settings.library.series_root_ph": "/mnt/media/library/series",
|
||||
"settings.library.staging_root": "Staging (optional)",
|
||||
"settings.library.staging_root_ph": "/mnt/media/.staging",
|
||||
|
||||
"settings.library_layout.movies_strategy_title": "Filme-Strategie",
|
||||
"settings.library_layout.series_strategy_title": "Serien-Strategie",
|
||||
"settings.library_layout.strategy": "Strategie",
|
||||
"settings.library_layout.season_naming": "Staffelbezeichnung",
|
||||
"settings.library_layout.normalization_title": "Normalisierung",
|
||||
"settings.library_layout.collision_title": "Kollisionsregel",
|
||||
"settings.library_layout.preview_title": "Vorschau",
|
||||
"settings.library_layout.preview_hint": "Klicke auf „Vorschau erzeugen“. ",
|
||||
"settings.library.movies_strategy_title": "Filme-Strategie",
|
||||
"settings.library.series_strategy_title": "Serien-Strategie",
|
||||
"settings.library.strategy": "Strategie",
|
||||
"settings.library.season_naming": "Staffelbezeichnung",
|
||||
"settings.library.normalization_title": "Normalisierung",
|
||||
"settings.library.collision_title": "Kollisionsregel",
|
||||
"settings.library.preview_title": "Vorschau",
|
||||
"settings.library.preview_hint": "Klicke auf „Vorschau erzeugen“. ",
|
||||
|
||||
"settings.strategy.flat": "Flach",
|
||||
"settings.strategy.first_letter": "Nach erstem Buchstaben",
|
||||
@ -511,7 +537,8 @@
|
||||
"settings.tasks.add": "Aufgabe hinzufugen",
|
||||
"settings.tasks.th_name": "Aufgabe",
|
||||
"settings.tasks.th_sources": "Quellen",
|
||||
"settings.tasks.th_actions": "Aktionen",
|
||||
"settings.tasks.th_rules": "Regeln",
|
||||
"settings.tasks.th_template": "Vorlage",
|
||||
"settings.tasks.th_status": "Status",
|
||||
|
||||
"auth.page_title": "scMedia / Login",
|
||||
|
||||
@ -78,11 +78,16 @@
|
||||
"settings.tasks.modal_title": "Task",
|
||||
"settings.tasks.field_name": "Name",
|
||||
"settings.tasks.field_sources": "Sources",
|
||||
"settings.tasks.field_actions": "Actions",
|
||||
"settings.tasks.field_rules": "Rules",
|
||||
"settings.tasks.field_template": "Template",
|
||||
"settings.tasks.field_enabled": "Enabled",
|
||||
"settings.tasks.source.library": "Library",
|
||||
"settings.tasks.hint": "Sources + rules choose what to process. Template defines the action pipeline.",
|
||||
"settings.tasks.source.movie": "Movies",
|
||||
"settings.tasks.source.series": "Series",
|
||||
"settings.tasks.source.transmission": "Transmission",
|
||||
"settings.tasks.source.staging": "Staging",
|
||||
"settings.tasks.rules_empty": "No rules yet",
|
||||
"settings.tasks.template_none": "No template",
|
||||
"settings.tasks.action.analyze": "Analyze",
|
||||
"settings.tasks.action.identify": "Identify",
|
||||
"settings.tasks.action.normalize": "Normalize",
|
||||
@ -146,8 +151,10 @@
|
||||
"settings.title": "Settings",
|
||||
"settings.back": "Back",
|
||||
"settings.tabs.scan_profiles": "Scan Profiles",
|
||||
"settings.tabs.library_layout": "Library",
|
||||
"settings.tabs.library": "Library",
|
||||
"settings.tabs.sources": "Sources",
|
||||
"settings.tabs.plugins": "Plugins",
|
||||
"settings.tabs.templates": "Templates",
|
||||
"settings.tabs.tasks": "Tasks",
|
||||
"settings.tabs.rules": "Rules",
|
||||
"settings.tabs.tools": "Programs",
|
||||
@ -162,15 +169,34 @@
|
||||
"settings.scan_profiles.confirm_delete": "Delete profile?",
|
||||
"settings.scan_profiles.ext_default": "default",
|
||||
"settings.scanner_defaults.title": "Global scanner defaults",
|
||||
"settings.library_layout.title": "Library",
|
||||
"settings.library_layout.preview": "Preview",
|
||||
"settings.library_layout.roots": "Folders",
|
||||
"settings.library_layout.add_root": "Add",
|
||||
"settings.library_layout.th_type": "Type",
|
||||
"settings.library_layout.th_path": "Path",
|
||||
"settings.library_layout.th_status": "Status",
|
||||
"settings.library_layout.modal_title": "Root",
|
||||
"settings.library_layout.confirm_delete": "Delete root?",
|
||||
"settings.library.title": "Library",
|
||||
"settings.library.preview": "Preview",
|
||||
"settings.library.roots": "Folders",
|
||||
"settings.library.add_root": "Add",
|
||||
"settings.library.th_type": "Type",
|
||||
"settings.library.th_path": "Path",
|
||||
"settings.library.th_status": "Status",
|
||||
"settings.library.modal_title": "Root",
|
||||
"settings.library.confirm_delete": "Delete root?",
|
||||
"settings.sources.title": "Sources",
|
||||
"settings.sources.add_root": "Add source",
|
||||
"settings.sources.th_type": "Type",
|
||||
"settings.sources.th_path": "Path",
|
||||
"settings.sources.th_status": "Status",
|
||||
"settings.sources.modal_title": "Source",
|
||||
"settings.sources.confirm_delete": "Delete source?",
|
||||
"settings.templates.title": "Templates",
|
||||
"settings.templates.add": "Add template",
|
||||
"settings.templates.th_name": "Template",
|
||||
"settings.templates.th_action": "Action",
|
||||
"settings.templates.th_status": "Status",
|
||||
"settings.templates.modal_title": "Template",
|
||||
"settings.templates.field_name": "Name",
|
||||
"settings.templates.field_action": "Action",
|
||||
"settings.templates.field_enabled": "Enabled",
|
||||
"settings.templates.hint": "Describe the action pipeline (mapping, convert, normalize, export).",
|
||||
"settings.templates.confirm_delete": "Delete template?",
|
||||
"settings.templates.unnamed": "Untitled",
|
||||
"root.type.movie": "Movie",
|
||||
"root.type.series": "Series",
|
||||
"root.type.staging": "Staging",
|
||||
@ -332,21 +358,21 @@
|
||||
"settings.scanner_defaults.max_files": "Max files per item",
|
||||
"settings.scanner_defaults.max_items": "Max items per scan (0 = no limit)",
|
||||
|
||||
"settings.library_layout.movies_root": "Movies root",
|
||||
"settings.library_layout.movies_root_ph": "/mnt/media/library/movies",
|
||||
"settings.library_layout.series_root": "Series root",
|
||||
"settings.library_layout.series_root_ph": "/mnt/media/library/series",
|
||||
"settings.library_layout.staging_root": "Staging root (optional)",
|
||||
"settings.library_layout.staging_root_ph": "/mnt/media/.staging",
|
||||
"settings.library.movies_root": "Movies root",
|
||||
"settings.library.movies_root_ph": "/mnt/media/library/movies",
|
||||
"settings.library.series_root": "Series root",
|
||||
"settings.library.series_root_ph": "/mnt/media/library/series",
|
||||
"settings.library.staging_root": "Staging root (optional)",
|
||||
"settings.library.staging_root_ph": "/mnt/media/.staging",
|
||||
|
||||
"settings.library_layout.movies_strategy_title": "Movies strategy",
|
||||
"settings.library_layout.series_strategy_title": "Series strategy",
|
||||
"settings.library_layout.strategy": "Strategy",
|
||||
"settings.library_layout.season_naming": "Season naming",
|
||||
"settings.library_layout.normalization_title": "Normalization",
|
||||
"settings.library_layout.collision_title": "Collision policy",
|
||||
"settings.library_layout.preview_title": "Preview",
|
||||
"settings.library_layout.preview_hint": "Click \"Generate preview\".",
|
||||
"settings.library.movies_strategy_title": "Movies strategy",
|
||||
"settings.library.series_strategy_title": "Series strategy",
|
||||
"settings.library.strategy": "Strategy",
|
||||
"settings.library.season_naming": "Season naming",
|
||||
"settings.library.normalization_title": "Normalization",
|
||||
"settings.library.collision_title": "Collision policy",
|
||||
"settings.library.preview_title": "Preview",
|
||||
"settings.library.preview_hint": "Click \"Generate preview\".",
|
||||
|
||||
"settings.strategy.flat": "Flat",
|
||||
"settings.strategy.first_letter": "By first letter",
|
||||
@ -511,7 +537,8 @@
|
||||
"settings.tasks.add": "Add task",
|
||||
"settings.tasks.th_name": "Task",
|
||||
"settings.tasks.th_sources": "Sources",
|
||||
"settings.tasks.th_actions": "Actions",
|
||||
"settings.tasks.th_rules": "Rules",
|
||||
"settings.tasks.th_template": "Template",
|
||||
"settings.tasks.th_status": "Status",
|
||||
|
||||
"auth.page_title": "scMedia / Login",
|
||||
|
||||
@ -78,11 +78,16 @@
|
||||
"settings.tasks.modal_title": "Задача",
|
||||
"settings.tasks.field_name": "Название",
|
||||
"settings.tasks.field_sources": "Источники",
|
||||
"settings.tasks.field_actions": "Действия",
|
||||
"settings.tasks.field_rules": "Правила",
|
||||
"settings.tasks.field_template": "Шаблон",
|
||||
"settings.tasks.field_enabled": "Включена",
|
||||
"settings.tasks.source.library": "Библиотека",
|
||||
"settings.tasks.hint": "Источники и правила выбирают данные, шаблон задает цепочку действий.",
|
||||
"settings.tasks.source.movie": "Фильмы",
|
||||
"settings.tasks.source.series": "Сериалы",
|
||||
"settings.tasks.source.transmission": "Transmission",
|
||||
"settings.tasks.source.staging": "Staging",
|
||||
"settings.tasks.rules_empty": "Правил пока нет",
|
||||
"settings.tasks.template_none": "Без шаблона",
|
||||
"settings.tasks.action.analyze": "Анализ",
|
||||
"settings.tasks.action.identify": "Поиск",
|
||||
"settings.tasks.action.normalize": "Нормализация",
|
||||
@ -146,8 +151,10 @@
|
||||
"settings.title": "Настройки",
|
||||
"settings.back": "Назад",
|
||||
"settings.tabs.scan_profiles": "Профили сканирования",
|
||||
"settings.tabs.library_layout": "Библиотека",
|
||||
"settings.tabs.library": "Библиотека",
|
||||
"settings.tabs.sources": "Источники",
|
||||
"settings.tabs.plugins": "Плагины",
|
||||
"settings.tabs.templates": "Шаблоны",
|
||||
"settings.tabs.tasks": "Задачи",
|
||||
"settings.tabs.rules": "Правила",
|
||||
"settings.tabs.tools": "Программы",
|
||||
@ -162,15 +169,34 @@
|
||||
"settings.scan_profiles.confirm_delete": "Удалить профиль?",
|
||||
"settings.scan_profiles.ext_default": "по умолчанию",
|
||||
"settings.scanner_defaults.title": "Глобальные настройки сканера",
|
||||
"settings.library_layout.title": "Библиотека",
|
||||
"settings.library_layout.preview": "Превью",
|
||||
"settings.library_layout.roots": "Папки",
|
||||
"settings.library_layout.add_root": "Добавить",
|
||||
"settings.library_layout.th_type": "Тип",
|
||||
"settings.library_layout.th_path": "Путь",
|
||||
"settings.library_layout.th_status": "Статус",
|
||||
"settings.library_layout.modal_title": "Папка",
|
||||
"settings.library_layout.confirm_delete": "Удалить папку?",
|
||||
"settings.library.title": "Библиотека",
|
||||
"settings.library.preview": "Превью",
|
||||
"settings.library.roots": "Папки",
|
||||
"settings.library.add_root": "Добавить",
|
||||
"settings.library.th_type": "Тип",
|
||||
"settings.library.th_path": "Путь",
|
||||
"settings.library.th_status": "Статус",
|
||||
"settings.library.modal_title": "Папка",
|
||||
"settings.library.confirm_delete": "Удалить папку?",
|
||||
"settings.sources.title": "Источники",
|
||||
"settings.sources.add_root": "Добавить источник",
|
||||
"settings.sources.th_type": "Тип",
|
||||
"settings.sources.th_path": "Путь",
|
||||
"settings.sources.th_status": "Статус",
|
||||
"settings.sources.modal_title": "Источник",
|
||||
"settings.sources.confirm_delete": "Удалить источник?",
|
||||
"settings.templates.title": "Шаблоны",
|
||||
"settings.templates.add": "Добавить шаблон",
|
||||
"settings.templates.th_name": "Шаблон",
|
||||
"settings.templates.th_action": "Действие",
|
||||
"settings.templates.th_status": "Статус",
|
||||
"settings.templates.modal_title": "Шаблон",
|
||||
"settings.templates.field_name": "Название",
|
||||
"settings.templates.field_action": "Действие",
|
||||
"settings.templates.field_enabled": "Включен",
|
||||
"settings.templates.hint": "Опиши цепочку действий (маппинг, конвертация, нормализация, экспорт).",
|
||||
"settings.templates.confirm_delete": "Удалить шаблон?",
|
||||
"settings.templates.unnamed": "Без названия",
|
||||
"root.type.movie": "Фильмы",
|
||||
"root.type.series": "Сериалы",
|
||||
"root.type.staging": "Стейджинг",
|
||||
@ -334,21 +360,21 @@
|
||||
"settings.scanner_defaults.max_files": "Макс. файлов на элемент",
|
||||
"settings.scanner_defaults.max_items": "Макс. элементов за скан (0 = без лимита)",
|
||||
|
||||
"settings.library_layout.movies_root": "Папка фильмов",
|
||||
"settings.library_layout.movies_root_ph": "/mnt/media/library/movies",
|
||||
"settings.library_layout.series_root": "Папка сериалов",
|
||||
"settings.library_layout.series_root_ph": "/mnt/media/library/series",
|
||||
"settings.library_layout.staging_root": "Staging (опционально)",
|
||||
"settings.library_layout.staging_root_ph": "/mnt/media/.staging",
|
||||
"settings.library.movies_root": "Папка фильмов",
|
||||
"settings.library.movies_root_ph": "/mnt/media/library/movies",
|
||||
"settings.library.series_root": "Папка сериалов",
|
||||
"settings.library.series_root_ph": "/mnt/media/library/series",
|
||||
"settings.library.staging_root": "Staging (опционально)",
|
||||
"settings.library.staging_root_ph": "/mnt/media/.staging",
|
||||
|
||||
"settings.library_layout.movies_strategy_title": "Стратегия фильмов",
|
||||
"settings.library_layout.series_strategy_title": "Стратегия сериалов",
|
||||
"settings.library_layout.strategy": "Стратегия",
|
||||
"settings.library_layout.season_naming": "Название сезона",
|
||||
"settings.library_layout.normalization_title": "Нормализация",
|
||||
"settings.library_layout.collision_title": "Политика коллизий",
|
||||
"settings.library_layout.preview_title": "Превью",
|
||||
"settings.library_layout.preview_hint": "Нажмите «Сгенерировать превью».",
|
||||
"settings.library.movies_strategy_title": "Стратегия фильмов",
|
||||
"settings.library.series_strategy_title": "Стратегия сериалов",
|
||||
"settings.library.strategy": "Стратегия",
|
||||
"settings.library.season_naming": "Название сезона",
|
||||
"settings.library.normalization_title": "Нормализация",
|
||||
"settings.library.collision_title": "Политика коллизий",
|
||||
"settings.library.preview_title": "Превью",
|
||||
"settings.library.preview_hint": "Нажмите «Сгенерировать превью».",
|
||||
|
||||
"settings.strategy.flat": "Плоско",
|
||||
"settings.strategy.first_letter": "По первой букве",
|
||||
@ -513,7 +539,8 @@
|
||||
"settings.tasks.add": "Добавить задачу",
|
||||
"settings.tasks.th_name": "Задача",
|
||||
"settings.tasks.th_sources": "Источники",
|
||||
"settings.tasks.th_actions": "Действия",
|
||||
"settings.tasks.th_rules": "Правила",
|
||||
"settings.tasks.th_template": "Шаблон",
|
||||
"settings.tasks.th_status": "Статус",
|
||||
|
||||
"auth.page_title": "scMedia / Вход",
|
||||
|
||||
@ -90,9 +90,6 @@
|
||||
if (ui.theme && window.UI?.setTheme) {
|
||||
window.UI.setTheme(ui.theme);
|
||||
}
|
||||
if (ui.language && ui.language !== (window.APP_LANG || 'en')) {
|
||||
localStorage.setItem('scmedia_lang', ui.language);
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadAvatar() {
|
||||
|
||||
@ -1369,7 +1369,7 @@ function initEventsPipe() {
|
||||
initSourcesPolling();
|
||||
return;
|
||||
}
|
||||
document.cookie = `sse_key=${encodeURIComponent(key)}; path=/; max-age=${Math.max(10, ttl)}`;
|
||||
document.cookie = `scmedia_sse_key=${encodeURIComponent(key)}; path=/; max-age=${Math.max(10, ttl)}`;
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
const es = new EventSource('/api/events');
|
||||
state.eventsSource = es;
|
||||
@ -1643,9 +1643,6 @@ function init() {
|
||||
if (prefs?.theme && window.UI?.setTheme) {
|
||||
window.UI.setTheme(prefs.theme);
|
||||
}
|
||||
if (prefs?.language && prefs.language !== state.lang) {
|
||||
localStorage.setItem('scmedia_lang', prefs.language);
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
UI.initThemeToggle();
|
||||
|
||||
@ -2,14 +2,24 @@
|
||||
/* English comments: auth token helpers and login UI */
|
||||
|
||||
(function () {
|
||||
const LS_ACCESS = 'scmedia_access_token';
|
||||
const LS_REFRESH = 'scmedia_refresh_token';
|
||||
const LS_CLIENT = 'scmedia_client_type';
|
||||
const ACCESS_COOKIE = 'scmedia_access_token';
|
||||
let refreshPromise = null;
|
||||
let refreshTimer = null;
|
||||
|
||||
function getAccessToken() {
|
||||
return localStorage.getItem(LS_ACCESS) || sessionStorage.getItem(LS_ACCESS) || '';
|
||||
const parts = document.cookie.split(';').map((p) => p.trim());
|
||||
for (const part of parts) {
|
||||
if (!part) continue;
|
||||
const idx = part.indexOf('=');
|
||||
if (idx === -1) continue;
|
||||
const key = part.slice(0, idx);
|
||||
if (key === ACCESS_COOKIE) {
|
||||
return decodeURIComponent(part.slice(idx + 1));
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function getRefreshToken() {
|
||||
@ -22,22 +32,22 @@
|
||||
|
||||
function setTokens(tokens, clientType = 'web', remember = true) {
|
||||
const storage = remember ? localStorage : sessionStorage;
|
||||
if (tokens?.access_token) storage.setItem(LS_ACCESS, tokens.access_token);
|
||||
if (tokens?.refresh_token) storage.setItem(LS_REFRESH, tokens.refresh_token);
|
||||
storage.setItem(LS_CLIENT, clientType);
|
||||
const other = remember ? sessionStorage : localStorage;
|
||||
other.removeItem(LS_ACCESS);
|
||||
other.removeItem(LS_REFRESH);
|
||||
other.removeItem(LS_CLIENT);
|
||||
localStorage.removeItem('scmedia_access_token');
|
||||
sessionStorage.removeItem('scmedia_access_token');
|
||||
}
|
||||
|
||||
function clearTokens() {
|
||||
localStorage.removeItem(LS_ACCESS);
|
||||
localStorage.removeItem(LS_REFRESH);
|
||||
localStorage.removeItem(LS_CLIENT);
|
||||
sessionStorage.removeItem(LS_ACCESS);
|
||||
sessionStorage.removeItem(LS_REFRESH);
|
||||
sessionStorage.removeItem(LS_CLIENT);
|
||||
localStorage.removeItem('scmedia_access_token');
|
||||
sessionStorage.removeItem('scmedia_access_token');
|
||||
}
|
||||
|
||||
function parseJwt(token) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -202,7 +202,7 @@
|
||||
localStorage.removeItem(SSE_LAST_TYPE);
|
||||
localStorage.removeItem(SSE_LEASE_KEY);
|
||||
sessionStorage.removeItem(SSE_KEY_EXP);
|
||||
document.cookie = 'sse_key=; path=/; max-age=0';
|
||||
document.cookie = 'scmedia_sse_key=; path=/; max-age=0';
|
||||
}
|
||||
|
||||
window.Sse = { start, stop, on };
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
/* English comments: shared DOM + UI helpers */
|
||||
|
||||
(function () {
|
||||
const LS_THEME = 'scmedia_theme';
|
||||
const THEME_COOKIE = 'scmedia_theme';
|
||||
let themeToggleReady = false;
|
||||
let queueState = { active: [], recent: [] };
|
||||
let sseOpening = false;
|
||||
@ -21,16 +21,33 @@
|
||||
return Array.from(document.querySelectorAll(selector));
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
const parts = document.cookie.split(';').map((p) => p.trim());
|
||||
for (const part of parts) {
|
||||
if (!part) continue;
|
||||
const idx = part.indexOf('=');
|
||||
if (idx === -1) continue;
|
||||
const key = part.slice(0, idx);
|
||||
if (key === name) return decodeURIComponent(part.slice(idx + 1));
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function setCookie(name, value, days = 365) {
|
||||
const expires = new Date(Date.now() + days * 86400000).toUTCString();
|
||||
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; expires=${expires}; SameSite=Lax`;
|
||||
}
|
||||
|
||||
function setTheme(theme) {
|
||||
const mode = theme === 'light' ? 'light' : 'dark';
|
||||
document.documentElement.setAttribute('data-theme', mode);
|
||||
localStorage.setItem(LS_THEME, mode);
|
||||
setCookie(THEME_COOKIE, mode);
|
||||
}
|
||||
|
||||
|
||||
function initThemeToggle() {
|
||||
const btn = qs('themeToggle');
|
||||
const saved = localStorage.getItem(LS_THEME) || 'dark';
|
||||
const saved = getCookie(THEME_COOKIE) || 'dark';
|
||||
setTheme(saved);
|
||||
if (!btn || themeToggleReady) return;
|
||||
btn.addEventListener('click', () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user