Initial import of docroot-changer
This commit is contained in:
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
|
||||
user="${USERNAME:-${LOGNAME:-${USER:-}}}"
|
||||
lang="${LANGUAGE:-}"
|
||||
|
||||
normalize_lang() {
|
||||
val="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
||||
case "$val" in
|
||||
pl*|polish*) echo "pl" ;;
|
||||
en*|english*) echo "en" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_enabled_for_user() {
|
||||
u="$1"
|
||||
[ -n "$u" ] || return 1
|
||||
cfg="/usr/local/directadmin/data/users/$u/user.conf"
|
||||
[ -r "$cfg" ] || return 1
|
||||
val="$(grep -E '^docroot_changer=' "$cfg" 2>/dev/null | tail -n1 | cut -d= -f2-)"
|
||||
case "$(printf '%s' "$val" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" in
|
||||
yes|on|true|1) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
lang="$(normalize_lang "$lang")"
|
||||
if [ -z "$lang" ] && [ -n "$user" ] && [ -r "/usr/local/directadmin/data/users/$user/user.conf" ]; then
|
||||
cfg_lang="$(grep -i -m1 -E '^(lang|language|locale)=' "/usr/local/directadmin/data/users/$user/user.conf" | cut -d= -f2-)"
|
||||
lang="$(normalize_lang "$cfg_lang")"
|
||||
fi
|
||||
|
||||
label="Zmiana katalogu domeny"
|
||||
|
||||
if ! is_enabled_for_user "$user"; then
|
||||
printf '[]\n'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf '[{"name":"%s","icon":"","entries":[{"name":"%s","icon":"","href":"/CMD_PLUGINS/docroot-changer","newTab":false,"updates":0}]}]\n' "$label" "$label"
|
||||
@@ -0,0 +1 @@
|
||||
<a href="/CMD_PLUGINS/docroot-changer">Zmiana katalogu domeny</a>
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none">
|
||||
<path d="M3.5 6.5A2.5 2.5 0 0 1 6 4h4.1c.8 0 1.4.3 1.9.9l1 1.1c.2.2.4.3.7.3H18A2.5 2.5 0 0 1 20.5 8.8v1H3.5v-3.3Z" fill="#F8B63C"/>
|
||||
<path d="M2.8 10.2h18.4a1.5 1.5 0 0 1 1.5 1.8l-1.3 6.6a2.5 2.5 0 0 1-2.5 2H5.1a2.5 2.5 0 0 1-2.5-2L1.3 12a1.5 1.5 0 0 1 1.5-1.8Z" fill="#FFD36A"/>
|
||||
<path d="M6.5 8.1h11.7A2.3 2.3 0 0 1 20.5 10v.2H3.5V10c0-1 .8-1.9 2-1.9Z" fill="#E8A531"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 479 B |
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 4167 4167" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1.01421,0,0,1.01421,-112.877,259.062)">
|
||||
<g id="Layer1">
|
||||
<g>
|
||||
<g transform="matrix(5.81705,0,0,5.81705,-791.069,-955.669)">
|
||||
<path d="M852.334,296.058L852.334,590.728C852.334,615.583 832.155,635.762 807.3,635.762L209.256,635.762C184.401,635.762 164.223,615.583 164.223,590.728L164.223,230.464C164.223,205.609 184.401,185.43 209.256,185.43L380.147,185.43L431.235,251.025L807.3,251.025C832.155,251.025 852.334,271.204 852.334,296.058Z" style="fill:rgb(90,93,157);"/>
|
||||
</g>
|
||||
<g transform="matrix(5.81705,0,0,5.81705,-791.069,-223.484)">
|
||||
<path d="M852.334,230.464L852.334,590.728C852.334,615.583 832.155,635.762 807.3,635.762L209.256,635.762C184.401,635.762 164.223,615.583 164.223,590.728L164.223,230.464C164.223,205.609 184.401,185.43 209.256,185.43L807.3,185.43C832.155,185.43 852.334,205.609 852.334,230.464Z" style="fill:rgb(97,189,246);"/>
|
||||
</g>
|
||||
<g transform="matrix(3.23929,0,0,6.02396,1410.65,-2184.76)">
|
||||
<path d="M265.446,830.527L265.446,768.549L553.27,768.549L553.27,710.997L717.924,799.538L553.27,888.078L553.27,830.527L265.446,830.527Z" style="fill:rgb(90,93,157);"/>
|
||||
</g>
|
||||
<g transform="matrix(10.3353,0,0,10.3353,-1432.04,-2942.16)">
|
||||
<g transform="matrix(189.707,0,0,189.707,329.704,555.54)">
|
||||
</g>
|
||||
<text x="251.557px" y="555.54px" style="font-family:'Inter-Black', 'Inter';font-weight:900;font-size:189.707px;fill:white;">/</text>
|
||||
</g>
|
||||
<g transform="matrix(1.14154,0,0,1.14154,-655.393,-214.085)">
|
||||
<path d="M2752.93,2182.83C2919.69,2252.68 2997.15,2448.31 2925.8,2619.42C2854.45,2790.53 2661.14,2872.74 2494.38,2802.89C2327.62,2733.05 2250.16,2537.42 2321.51,2366.31C2392.86,2195.2 2586.17,2112.98 2752.93,2182.83Z" style="fill:rgb(90,93,157);"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SCRIPT="$PLUGIN_DIR/scripts/install.sh"
|
||||
|
||||
if [ ! -x "$SCRIPT" ]; then
|
||||
chmod 755 "$SCRIPT" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
exec "$SCRIPT"
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
return [
|
||||
'Zmiana Docroot' => 'Docroot Changer',
|
||||
'Zmiana katalogu domeny' => 'Change domain directory',
|
||||
'Docroot Changer' => 'Docroot Changer',
|
||||
'Domena' => 'Domain',
|
||||
'Aktualny Docroot' => 'Current Docroot',
|
||||
'Tryb' => 'Mode',
|
||||
'Domyślny' => 'Default',
|
||||
'Niestandardowy' => 'Custom',
|
||||
'Akcje' => 'Actions',
|
||||
'Edytuj' => 'Edit',
|
||||
'Zmień katalog domeny' => 'Change domain directory',
|
||||
'Ustaw domyślną' => 'Set default',
|
||||
'Brak domen do wyświetlenia.' => 'No domains to display.',
|
||||
'Wykryty webserwer:' => 'Detected web server:',
|
||||
'Sprawdź ładowanie elementów statycznych, np. grafik lub stylów CSS po zmianie DocRoot Domeny' => 'Check loading of static assets (for example images or CSS) after changing the domain DocRoot',
|
||||
'Dozwolona baza ścieżek:' => 'Allowed path base:',
|
||||
'Zapisz' => 'Save',
|
||||
'Anuluj' => 'Cancel',
|
||||
'Potwierdzenie' => 'Confirmation',
|
||||
'Przywróć domyślną' => 'Restore default',
|
||||
'Przywróć poprzedni Docroot' => 'Restore previous Docroot',
|
||||
'Obecne ustawienie' => 'Current setting',
|
||||
'Standardowy DocRoot' => 'Standard DocRoot',
|
||||
'Niestandardowy DocRoot' => 'Custom DocRoot',
|
||||
'Przeglądaj' => 'Browse',
|
||||
'Wybierz katalog' => 'Select directory',
|
||||
'Wybierz' => 'Select',
|
||||
'W górę' => 'Up',
|
||||
'Nowy katalog' => 'New directory',
|
||||
'Utwórz' => 'Create',
|
||||
'Brak katalogów' => 'No directories',
|
||||
'Nie udało się wczytać katalogów.' => 'Failed to load directories.',
|
||||
'Nie udało się utworzyć katalogu.' => 'Failed to create directory.',
|
||||
'Nieprawidłowa nazwa katalogu.' => 'Invalid directory name.',
|
||||
'Katalog już istnieje.' => 'Directory already exists.',
|
||||
'Nie można utworzyć katalogu poza dozwoloną ścieżką.' => 'Cannot create directory outside the allowed path.',
|
||||
'Nie można utworzyć katalogu bezpośrednio w ' => 'Cannot create a directory directly in ',
|
||||
'Tworzenie katalogów jest wyłączone.' => 'Directory creation is disabled.',
|
||||
'Podaj nazwę katalogu.' => 'Provide a directory name.',
|
||||
'Ścieżka musi zaczynać się od:' => 'Path must start with:',
|
||||
'Ścieżka nie zaczyna się od:' => 'Path does not start with:',
|
||||
'ścieżka zawiera nieprawidłowe znaki - proszę wprowadzić poprawną ścieżkę lub skorzystać z opcji __ICON__ w celu wyboru poprawnie ścieżki.' => 'Path contains invalid characters - please enter a valid path or use the __ICON__ option to choose a valid path.',
|
||||
'Ścieżka zawiera niedozwolony fragment "..".' => 'Path contains forbidden ".." segment.',
|
||||
'Docroot musi wskazywać istniejący katalog.' => 'Docroot must point to an existing directory.',
|
||||
'Docroot musi być katalogiem.' => 'Docroot must be a directory.',
|
||||
'Brak dostępu do katalogu docroot.' => 'No access to the docroot directory.',
|
||||
'Nieprawidłowy token CSRF.' => 'Invalid CSRF token.',
|
||||
'Metoda niedozwolona.' => 'Method Not Allowed.',
|
||||
'Brak dostępu do pluginu. Włącz opcję docroot_changer w pakiecie lub dla użytkownika.' => 'No access to the plugin. Enable docroot_changer in package or for the user.',
|
||||
'Brak dostępu' => 'Access denied',
|
||||
'Nieprawidłowa domena.' => 'Invalid domain.',
|
||||
'Nie znaleziono domeny.' => 'Domain not found.',
|
||||
'Wskazany katalog nie należy do użytkownika docelowego.' => 'The selected directory does not belong to the target user.',
|
||||
'Nie udało się ustawić właściciela katalogu na użytkownika DirectAdmin.' => 'Failed to set directory owner to the DirectAdmin user.',
|
||||
'Nie udało się zapisać poprzedniego docroot.' => 'Failed to save previous docroot.',
|
||||
'Nie znaleziono poprzedniego docroot dla domeny.' => 'No previous docroot found for this domain.',
|
||||
'Zapisano docroot dla domeny.' => 'Docroot saved for domain.',
|
||||
'Przywrócono domyślny docroot.' => 'Default docroot restored.',
|
||||
'Przywrócono poprzedni docroot.' => 'Previous docroot restored.',
|
||||
'Błąd API DirectAdmin:' => 'DirectAdmin API error:',
|
||||
'Brak odpowiedzi z API DirectAdmin.' => 'No response from DirectAdmin API.',
|
||||
'Brak aktywnej sesji DirectAdmin do wywołania API.' => 'No active DirectAdmin session for API call.',
|
||||
'Nieobsługiwany webserwer, używam trybu apache.' => 'Unsupported web server, using apache mode.',
|
||||
'Kliknij nazwę domeny, aby zmienić Docroot.' => 'Click a domain name to change Docroot.',
|
||||
'Docroot dla domeny:' => 'Docroot for domain:',
|
||||
'Wpisz ścieżkę ręcznie lub użyj pickera katalogów.' => 'Enter a path manually or use the directory picker.',
|
||||
'Aby zmienić DocRoot dla domeny __DOMAIN__ Wpisz ścieżkę do katalogu docelowego, lub użyj przycisku __ICON__ aby wskazać go w drzewie katalogów.' => 'To change DocRoot for domain __DOMAIN__, enter the target directory path, or use the __ICON__ button to select it in the directory tree.',
|
||||
'Czy na pewno chcesz zmienić katalog domeny __DOMAIN__ na niestandardowy?' => 'Are you sure you want to set a custom domain directory for __DOMAIN__?',
|
||||
'Czy na pewno chcesz przywrócić domyślny katalog domeny __DOMAIN__' => 'Are you sure you want to restore the default domain directory for __DOMAIN__?',
|
||||
'Obecny katalog domeny:' => 'Current domain directory:',
|
||||
'Nowy katalog domeny:' => 'New domain directory:',
|
||||
'Domyślny katalog domeny:' => 'Default domain directory:',
|
||||
'Wskazany katalog ma właściciela __OWNER__:__GROUP__. Czy kontynuować? W przypadku kontynuacji wskazany katalog __PATH__ będzie miał zmienione uprawnienia na __USER__:__USER__. Zmiana dotyczy wyłącznie tego katalogu (bez podkatalogów).' => 'The selected directory owner is __OWNER__:__GROUP__. Continue? If you continue, __PATH__ will be changed to __USER__:__USER__. This change applies only to this directory (without subdirectories).',
|
||||
'Zmiana katalogu domeny __DOMAIN__ wykonana pomyślnie będzie ona widoczna w ciągu kilku minut' => 'Domain directory change for __DOMAIN__ completed successfully; it will be visible within a few minutes',
|
||||
'Zamknij' => 'Close',
|
||||
'Kod błędu:' => 'Error code:',
|
||||
'Nie udało się wysłać żądania.' => 'Failed to send request.',
|
||||
'Nie udało się przetworzyć odpowiedzi.' => 'Failed to process response.',
|
||||
'Przywrócić domyślny docroot dla tej domeny?' => 'Restore default docroot for this domain?',
|
||||
'Przywrócić poprzedni docroot dla tej domeny?' => 'Restore previous docroot for this domain?',
|
||||
'Niezgodna konfiguracja Docroot między Apache i Nginx (pokazano pierwszą wykrytą).' => 'Docroot mismatch between Apache and Nginx (showing first detected).',
|
||||
'Tak' => 'Yes',
|
||||
'Nie' => 'No',
|
||||
'Ścieżka musi znajdować się w katalogu:' => 'Path must be inside:',
|
||||
'Ścieżka musi znajdować się w katalogu użytkownika:' => 'Path must be inside user directory:',
|
||||
'Nie udało się usunąć pliku konfiguracji webserwera: ' => 'Failed to remove web server configuration file: ',
|
||||
'Nie udało się odczytać pliku konfiguracji webserwera: ' => 'Failed to read web server configuration file: ',
|
||||
'Nie udało się zapisać pliku konfiguracji webserwera: ' => 'Failed to write web server configuration file: ',
|
||||
'Nie udało się zainicjować przebudowy konfiguracji przez dataskq.' => 'Failed to queue configuration rebuild via dataskq.',
|
||||
'Nieprawidłowa nazwa użytkownika dla zadania rewrite httpd.' => 'Invalid username for httpd rewrite task.',
|
||||
'Nie udało się dodać zadania rewrite httpd do task.queue.' => 'Failed to append httpd rewrite task to task.queue.',
|
||||
'Nie udało się zainicjować przebudowy konfiguracji httpd przez task.queue/dataskq.' => 'Failed to trigger httpd configuration rebuild via task.queue/dataskq.',
|
||||
'Nie udało się zastosować zmiany docroot.' => 'Failed to apply docroot change.',
|
||||
'wprowadzona ścieżka jest dowiązaniem symbolicznym do: __TARGET__, wprowadź ścieżkę do realnego katalogu' => 'The entered path is a symbolic link to: __TARGET__, enter a path to the real directory',
|
||||
'Wskazany katalog jest pusty' => 'The selected directory is empty',
|
||||
'pusty' => 'empty',
|
||||
];
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
$en = include __DIR__ . '/en.php';
|
||||
$pl = [];
|
||||
foreach ($en as $key => $_value) {
|
||||
$pl[$key] = $key;
|
||||
}
|
||||
return $pl;
|
||||
@@ -0,0 +1,7 @@
|
||||
[PHP]
|
||||
short_open_tag=On
|
||||
variables_order=GPCS
|
||||
error_reporting=E_ALL & ~E_NOTICE
|
||||
display_errors=Off
|
||||
log_errors=On
|
||||
output_buffering=4096
|
||||
@@ -0,0 +1,17 @@
|
||||
# Ustawienia pluginu docroot-changer
|
||||
# CUSTOM_PACKAGE_ITEM_NAME - nazwa custom package item w DirectAdmin.
|
||||
CUSTOM_PACKAGE_ITEM_NAME=docroot_changer
|
||||
# CUSTOM_PACKAGE_ITEM_DEFAULT - domyślna wartość custom package item.
|
||||
CUSTOM_PACKAGE_ITEM_DEFAULT=no
|
||||
# ENFORCE_DOMAINS_BASE - ścieżka bazowa dozwolona dla docroot.
|
||||
ENFORCE_DOMAINS_BASE=/home/DA_USER/domains
|
||||
# ALLOW_PICKER_CREATE_DIR - pozwala tworzyć katalogi z poziomu pickera (yes/no).
|
||||
ALLOW_PICKER_CREATE_DIR=yes
|
||||
# ALLOW_DOCROOT_OUTSIDE_DOMAINS_DIR - pozwala ustawić DocRoot poza /home/DA_USER/domains.
|
||||
# Gdy yes, dozwolony zakres to tylko /home/DA_USER (nigdy powyżej katalogu użytkownika).
|
||||
ALLOW_DOCROOT_OUTSIDE_DOMAINS_DIR=no
|
||||
# DISABLE_CONFIRMATIONS - wyłącza popupy potwierdzeń (true/false).
|
||||
DISABLE_CONFIRMATIONS=false
|
||||
# FIX_OWNERSHIP - sprawdza owner katalogu i w razie potrzeby pozwala poprawić owner na DA_USER:DA_USER.
|
||||
# Ustaw false, aby pominąć sprawdzanie ownera, ostrzeżenia i zmianę ownera.
|
||||
FIX_OWNERSHIP=true
|
||||
@@ -0,0 +1,6 @@
|
||||
name=Docroot-Changer
|
||||
id=docroot-changer
|
||||
type=user
|
||||
author=HITME.PL
|
||||
version=2.1.2
|
||||
user_run_as=root
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
chown -R diradmin:diradmin "$PLUGIN_DIR" || true
|
||||
else
|
||||
echo "docroot-changer: warning: not running as root, cannot chown to diradmin" >&2
|
||||
fi
|
||||
|
||||
chmod 755 "$PLUGIN_DIR" "$PLUGIN_DIR/hooks" "$PLUGIN_DIR/user" "$PLUGIN_DIR/scripts" "$PLUGIN_DIR/images" || true
|
||||
chmod 755 "$PLUGIN_DIR/user/index.html" "$PLUGIN_DIR/install.sh" "$PLUGIN_DIR/uninstall.sh" "$PLUGIN_DIR/scripts/install.sh" "$PLUGIN_DIR/scripts/uninstall.sh" "$PLUGIN_DIR/hooks/menu_user.sh" || true
|
||||
chmod 644 "$PLUGIN_DIR/plugin.conf" "$PLUGIN_DIR/plugin-settings.conf" "$PLUGIN_DIR/hooks/user_txt.html" "$PLUGIN_DIR/user/evolution.css" "$PLUGIN_DIR/images/user_icon.svg" "$PLUGIN_DIR/images/folder.svg" || true
|
||||
|
||||
DATA_DIR="$PLUGIN_DIR/data"
|
||||
mkdir -p "$DATA_DIR/csrf"
|
||||
chmod 700 "$DATA_DIR" "$DATA_DIR/csrf" || true
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
chown -R diradmin:diradmin "$DATA_DIR" || true
|
||||
fi
|
||||
|
||||
CPI_FILE="/usr/local/directadmin/data/admin/custom_package_items.conf"
|
||||
ITEM_LINE='docroot_changer=checkbox=no&desc=Allow+Docroot+Changer+plugin'
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
mkdir -p "$(dirname "$CPI_FILE")"
|
||||
touch "$CPI_FILE"
|
||||
if grep -q '^docroot_changer=' "$CPI_FILE"; then
|
||||
sed -i.bak '/^docroot_changer=/d' "$CPI_FILE"
|
||||
fi
|
||||
printf '%s\n' "$ITEM_LINE" >> "$CPI_FILE"
|
||||
chmod 644 "$CPI_FILE" || true
|
||||
chown diradmin:diradmin "$CPI_FILE" || true
|
||||
else
|
||||
echo "docroot-changer: warning: not running as root, cannot modify custom_package_items.conf" >&2
|
||||
fi
|
||||
|
||||
echo "docroot-changer: install complete"
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
if [ "$(basename "$PLUGIN_DIR")" != "docroot-changer" ]; then
|
||||
echo "docroot-changer: safety check failed (basename is not docroot-changer): $PLUGIN_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$PLUGIN_DIR/plugin.conf" ]; then
|
||||
echo "docroot-changer: safety check failed (plugin.conf missing): $PLUGIN_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CPI_FILE="/usr/local/directadmin/data/admin/custom_package_items.conf"
|
||||
if [ "$(id -u)" -eq 0 ] && [ -f "$CPI_FILE" ]; then
|
||||
sed -i.bak '/^docroot_changer=/d' "$CPI_FILE" || true
|
||||
fi
|
||||
|
||||
(sleep 3; rm -rf "$PLUGIN_DIR") >/dev/null 2>&1 &
|
||||
|
||||
echo "docroot-changer: uninstall complete"
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SCRIPT="$PLUGIN_DIR/scripts/uninstall.sh"
|
||||
|
||||
if [ ! -x "$SCRIPT" ]; then
|
||||
chmod 755 "$SCRIPT" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
exec "$SCRIPT"
|
||||
@@ -0,0 +1,347 @@
|
||||
:root {
|
||||
--br-bg: #f5f8ff;
|
||||
--br-card: #ffffff;
|
||||
--br-text: #111827;
|
||||
--br-muted: #64748b;
|
||||
--br-border: #e2e8f0;
|
||||
--br-accent: #2563eb;
|
||||
--br-accent-dark: #1d4ed8;
|
||||
--br-danger: #dc2626;
|
||||
--br-warn: #f59e0b;
|
||||
--br-shadow: 0 10px 28px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--br-bg);
|
||||
color: var(--br-text);
|
||||
font-family: "IBM Plex Sans", "Segoe UI", Arial, sans-serif;
|
||||
}
|
||||
.br-container {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
margin: 16px auto;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
.br-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.br-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
.br-card {
|
||||
background: var(--br-card);
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: var(--br-shadow);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.br-muted { color: var(--br-muted); }
|
||||
.br-alert {
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
margin: 8px 0;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.br-alert-error { background: #fff1f2; border-color: #fecdd3; color: #9f1239; }
|
||||
.br-alert-warn { background: #fffbeb; border-color: #fde68a; color: #92400e; }
|
||||
.br-alert-success { background: #ecfdf3; border-color: #bbf7d0; color: #166534; }
|
||||
.br-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.35);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
.br-modal {
|
||||
width: min(560px, 92vw);
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
border: 2px solid var(--br-border);
|
||||
box-shadow: var(--br-shadow);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 90vh;
|
||||
}
|
||||
.br-modal-log {
|
||||
width: 90vw;
|
||||
background: #ffffff;
|
||||
}
|
||||
.br-modal-warning {
|
||||
background: #f3c8c8;
|
||||
}
|
||||
.br-modal-warning .br-btn-ghost {
|
||||
background: #ffffff;
|
||||
}
|
||||
.br-modal-warning .br-btn-ghost:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.br-modal-danger {
|
||||
border-color: var(--br-danger);
|
||||
}
|
||||
.br-modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.br-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.br-modal-title {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
.br-modal-body {
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.br-modal-scroll {
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.br-log-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
.br-log-header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.br-log-meta {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin: 10px 0 12px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
background: #f8fafc;
|
||||
font-size: 13px;
|
||||
}
|
||||
.br-log-actions {
|
||||
margin: 8px 0 10px;
|
||||
}
|
||||
.br-picker-path {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.br-picker-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
max-height: 45vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.br-picker-create {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.br-picker-create input {
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.br-picker-list li {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--br-border);
|
||||
}
|
||||
.br-picker-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.br-picker-list li:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.br-picker-list li.active {
|
||||
background: #d1fae5;
|
||||
}
|
||||
.br-btn-small {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.br-actions-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.br-actions-row form {
|
||||
margin: 0;
|
||||
}
|
||||
.br-icon-btn {
|
||||
border: 1px solid var(--br-border);
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
.br-icon-btn:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.br-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
table-layout: fixed;
|
||||
}
|
||||
.br-table th {
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--br-muted);
|
||||
background: #f1f5f9;
|
||||
padding: 10px;
|
||||
}
|
||||
.br-table td {
|
||||
padding: 10px;
|
||||
border-top: 1px solid var(--br-border);
|
||||
vertical-align: top;
|
||||
}
|
||||
.br-table tr:nth-child(even) td { background: #f8fafc; }
|
||||
.br-col-select { width: 50px; text-align: center; }
|
||||
.br-col-status { width: 120px; }
|
||||
.br-col-progress { width: 90px; }
|
||||
.br-col-mode { width: 180px; }
|
||||
.br-col-user { width: 140px; }
|
||||
.br-col-backup { width: 220px; overflow-wrap: anywhere; }
|
||||
.br-col-path { width: 260px; overflow-wrap: anywhere; }
|
||||
.br-col-target { width: 220px; overflow-wrap: anywhere; }
|
||||
.br-col-time { width: 150px; }
|
||||
.br-col-actions { width: 120px; }
|
||||
.br-table input[type="checkbox"] {
|
||||
accent-color: var(--br-accent);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.br-actions { display: flex; gap: 8px; align-items: center; margin: 10px 0; }
|
||||
.br-btn {
|
||||
background: var(--br-accent);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
.br-btn:hover { background: var(--br-accent-dark); }
|
||||
.br-btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--br-text);
|
||||
border: 1px solid var(--br-border);
|
||||
}
|
||||
.br-btn-ghost:hover { background: #e2e8f0; }
|
||||
.br-btn-danger {
|
||||
background: var(--br-danger);
|
||||
}
|
||||
.br-btn-danger:hover { background: #b91c1c; }
|
||||
.br-link-danger { color: var(--br-danger); text-decoration: none; }
|
||||
.br-link-danger:hover { text-decoration: underline; }
|
||||
.br-inline-form { display: inline; }
|
||||
.br-link-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
.br-link-button:hover { text-decoration: underline; }
|
||||
.br-link-button.br-link-danger { color: var(--br-danger); }
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
max-width: 680px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
pre {
|
||||
background: #0b1020;
|
||||
color: #e2e8f0;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
overflow: auto;
|
||||
}
|
||||
a { color: var(--br-accent-dark); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.br-toast {
|
||||
position: fixed;
|
||||
bottom: 18px;
|
||||
right: 18px;
|
||||
background: #16a34a;
|
||||
color: #fff;
|
||||
padding: 12px 16px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 12px 28px rgba(16, 24, 40, 0.25);
|
||||
z-index: 9999;
|
||||
font-weight: 600;
|
||||
}
|
||||
.br-toast-stack {
|
||||
position: fixed;
|
||||
top: 18px;
|
||||
right: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
z-index: 9999;
|
||||
}
|
||||
.br-toast-stack .br-toast {
|
||||
position: static;
|
||||
top: auto;
|
||||
bottom: auto;
|
||||
}
|
||||
.br-toast-error {
|
||||
background: #dc2626;
|
||||
top: 18px;
|
||||
bottom: auto;
|
||||
}
|
||||
.br-toast-success {
|
||||
background: #16a34a;
|
||||
}
|
||||
.br-status-line {
|
||||
margin: 6px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.br-progress {
|
||||
height: 8px;
|
||||
background: #e5e7eb;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
margin: 6px 0 12px;
|
||||
}
|
||||
.br-progress-bar {
|
||||
height: 100%;
|
||||
background: var(--br-accent);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
:root {
|
||||
--br-bg: #f5f8ff;
|
||||
--br-card: #ffffff;
|
||||
--br-text: #111827;
|
||||
--br-muted: #64748b;
|
||||
--br-border: #e2e8f0;
|
||||
--br-accent: #2563eb;
|
||||
--br-accent-dark: #1d4ed8;
|
||||
--br-danger: #dc2626;
|
||||
--br-warn: #f59e0b;
|
||||
--br-shadow: 0 10px 28px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--br-bg);
|
||||
color: var(--br-text);
|
||||
font-family: "IBM Plex Sans", "Segoe UI", Arial, sans-serif;
|
||||
}
|
||||
.br-container {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
margin: 16px auto;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
.br-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.br-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
.br-card {
|
||||
background: var(--br-card);
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: var(--br-shadow);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.br-muted { color: var(--br-muted); }
|
||||
.br-alert {
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
margin: 8px 0;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.br-alert-error { background: #fff1f2; border-color: #fecdd3; color: #9f1239; }
|
||||
.br-alert-warn { background: #fffbeb; border-color: #fde68a; color: #92400e; }
|
||||
.br-alert-success { background: #ecfdf3; border-color: #bbf7d0; color: #166534; }
|
||||
.br-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.35);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
.br-modal {
|
||||
width: min(560px, 92vw);
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
border: 2px solid var(--br-border);
|
||||
box-shadow: var(--br-shadow);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 90vh;
|
||||
}
|
||||
.br-modal-log {
|
||||
width: 90vw;
|
||||
background: #ffffff;
|
||||
}
|
||||
.br-modal-warning {
|
||||
background: #f3c8c8;
|
||||
}
|
||||
.br-modal-warning .br-btn-ghost {
|
||||
background: #ffffff;
|
||||
}
|
||||
.br-modal-warning .br-btn-ghost:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.br-modal-danger {
|
||||
border-color: var(--br-danger);
|
||||
}
|
||||
.br-modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.br-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.br-modal-title {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
.br-modal-body {
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.br-modal-scroll {
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.br-log-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
.br-log-header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.br-log-meta {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin: 10px 0 12px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
background: #f8fafc;
|
||||
font-size: 13px;
|
||||
}
|
||||
.br-log-actions {
|
||||
margin: 8px 0 10px;
|
||||
}
|
||||
.br-picker-path {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.br-picker-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
max-height: 45vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.br-picker-create {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.br-picker-create input {
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.br-picker-list li {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--br-border);
|
||||
}
|
||||
.br-picker-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.br-picker-list li:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.br-picker-list li.active {
|
||||
background: #d1fae5;
|
||||
}
|
||||
.br-btn-small {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.br-actions-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.br-actions-row form {
|
||||
margin: 0;
|
||||
}
|
||||
.br-icon-btn {
|
||||
border: 1px solid var(--br-border);
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
.br-icon-btn:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.br-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
table-layout: fixed;
|
||||
}
|
||||
.br-table th {
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--br-muted);
|
||||
background: #f1f5f9;
|
||||
padding: 10px;
|
||||
}
|
||||
.br-table td {
|
||||
padding: 10px;
|
||||
border-top: 1px solid var(--br-border);
|
||||
vertical-align: top;
|
||||
}
|
||||
.br-table tr:nth-child(even) td { background: #f8fafc; }
|
||||
.br-col-select { width: 50px; text-align: center; }
|
||||
.br-col-status { width: 120px; }
|
||||
.br-col-progress { width: 90px; }
|
||||
.br-col-mode { width: 180px; }
|
||||
.br-col-user { width: 140px; }
|
||||
.br-col-backup { width: 220px; overflow-wrap: anywhere; }
|
||||
.br-col-path { width: 260px; overflow-wrap: anywhere; }
|
||||
.br-col-target { width: 220px; overflow-wrap: anywhere; }
|
||||
.br-col-time { width: 150px; }
|
||||
.br-col-actions { width: 120px; }
|
||||
.br-table input[type="checkbox"] {
|
||||
accent-color: var(--br-accent);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.br-actions { display: flex; gap: 8px; align-items: center; margin: 10px 0; }
|
||||
.br-btn {
|
||||
background: var(--br-accent);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
.br-btn:hover { background: var(--br-accent-dark); }
|
||||
.br-btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--br-text);
|
||||
border: 1px solid var(--br-border);
|
||||
}
|
||||
.br-btn-ghost:hover { background: #e2e8f0; }
|
||||
.br-btn-danger {
|
||||
background: var(--br-danger);
|
||||
}
|
||||
.br-btn-danger:hover { background: #b91c1c; }
|
||||
.br-link-danger { color: var(--br-danger); text-decoration: none; }
|
||||
.br-link-danger:hover { text-decoration: underline; }
|
||||
.br-inline-form { display: inline; }
|
||||
.br-link-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
.br-link-button:hover { text-decoration: underline; }
|
||||
.br-link-button.br-link-danger { color: var(--br-danger); }
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
max-width: 680px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
pre {
|
||||
background: #0b1020;
|
||||
color: #e2e8f0;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
overflow: auto;
|
||||
}
|
||||
a { color: var(--br-accent-dark); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.br-toast {
|
||||
position: fixed;
|
||||
bottom: 18px;
|
||||
right: 18px;
|
||||
background: #16a34a;
|
||||
color: #fff;
|
||||
padding: 12px 16px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 12px 28px rgba(16, 24, 40, 0.25);
|
||||
z-index: 9999;
|
||||
font-weight: 600;
|
||||
}
|
||||
.br-toast-stack {
|
||||
position: fixed;
|
||||
top: 18px;
|
||||
right: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
z-index: 9999;
|
||||
}
|
||||
.br-toast-stack .br-toast {
|
||||
position: static;
|
||||
top: auto;
|
||||
bottom: auto;
|
||||
}
|
||||
.br-toast-error {
|
||||
background: #dc2626;
|
||||
top: 18px;
|
||||
bottom: auto;
|
||||
}
|
||||
.br-toast-success {
|
||||
background: #16a34a;
|
||||
}
|
||||
.br-status-line {
|
||||
margin: 6px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.br-progress {
|
||||
height: 8px;
|
||||
background: #e5e7eb;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
margin: 6px 0 12px;
|
||||
}
|
||||
.br-progress-bar {
|
||||
height: 100%;
|
||||
background: var(--br-accent);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
Executable
+2069
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user