feat: improve admin trial settings controls
This commit is contained in:
@@ -31,6 +31,10 @@
|
|||||||
"TRIAL_TRAFFIC_STRATEGY",
|
"TRIAL_TRAFFIC_STRATEGY",
|
||||||
"TRIAL_SQUAD_UUIDS",
|
"TRIAL_SQUAD_UUIDS",
|
||||||
];
|
];
|
||||||
|
const TRIAL_SWITCH_KEYS = ["TRIAL_ENABLED"];
|
||||||
|
const TRIAL_GENERAL_KEYS = ["TRIAL_DURATION_DAYS", "TRIAL_TRAFFIC_LIMIT_GB"];
|
||||||
|
const TRIAL_RESET_KEYS = ["TRIAL_TRAFFIC_STRATEGY"];
|
||||||
|
const TRIAL_SQUAD_KEYS = ["TRIAL_SQUAD_UUIDS"];
|
||||||
const LEGACY_PERIODS = [
|
const LEGACY_PERIODS = [
|
||||||
["1", "MONTH_1_ENABLED", "RUB_PRICE_1_MONTH", "STARS_PRICE_1_MONTH"],
|
["1", "MONTH_1_ENABLED", "RUB_PRICE_1_MONTH", "STARS_PRICE_1_MONTH"],
|
||||||
["3", "MONTH_3_ENABLED", "RUB_PRICE_3_MONTHS", "STARS_PRICE_3_MONTHS"],
|
["3", "MONTH_3_ENABLED", "RUB_PRICE_3_MONTHS", "STARS_PRICE_3_MONTHS"],
|
||||||
@@ -76,6 +80,7 @@
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
let selectedTrialSquad = "";
|
let selectedTrialSquad = "";
|
||||||
|
let trialSquadSelectKey = 0;
|
||||||
let tariffSettingsOpen = [];
|
let tariffSettingsOpen = [];
|
||||||
|
|
||||||
function tariffName(tariff) {
|
function tariffName(tariff) {
|
||||||
@@ -122,6 +127,18 @@
|
|||||||
settingsStore.markDirty(key, value);
|
settingsStore.markDirty(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSettingDirty(key) {
|
||||||
|
return Boolean(settingsDirty[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dirtyCount(keys) {
|
||||||
|
return (keys || []).filter((key) => isSettingDirty(key)).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetSetting(key) {
|
||||||
|
settingsStore.clearDirty(key);
|
||||||
|
}
|
||||||
|
|
||||||
function csvList(key) {
|
function csvList(key) {
|
||||||
return String(valueForKey(key) || "")
|
return String(valueForKey(key) || "")
|
||||||
.split(",")
|
.split(",")
|
||||||
@@ -139,10 +156,19 @@
|
|||||||
function addTrialSquad(uuid) {
|
function addTrialSquad(uuid) {
|
||||||
const next = String(uuid || "").trim();
|
const next = String(uuid || "").trim();
|
||||||
if (!next) return;
|
if (!next) return;
|
||||||
setCsvList("TRIAL_SQUAD_UUIDS", [...csvList("TRIAL_SQUAD_UUIDS"), next]);
|
const current = csvList("TRIAL_SQUAD_UUIDS");
|
||||||
|
if (!current.includes(next)) {
|
||||||
|
setCsvList("TRIAL_SQUAD_UUIDS", [...current, next]);
|
||||||
|
}
|
||||||
selectedTrialSquad = "";
|
selectedTrialSquad = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleTrialSquadSelect(uuid) {
|
||||||
|
addTrialSquad(uuid);
|
||||||
|
selectedTrialSquad = "";
|
||||||
|
trialSquadSelectKey += 1;
|
||||||
|
}
|
||||||
|
|
||||||
function removeTrialSquad(uuid) {
|
function removeTrialSquad(uuid) {
|
||||||
setCsvList(
|
setCsvList(
|
||||||
"TRIAL_SQUAD_UUIDS",
|
"TRIAL_SQUAD_UUIDS",
|
||||||
@@ -225,96 +251,323 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="admin-card-body">
|
<div class="admin-card-body admin-trial-settings-body">
|
||||||
<div class="admin-form admin-tariff-settings-form">
|
<div class="admin-settings-field-groups admin-trial-settings-groups">
|
||||||
<div class="admin-form-row admin-form-row-3">
|
<section class="admin-settings-field-group" class:is-dirty={dirtyCount(TRIAL_SWITCH_KEYS)}>
|
||||||
<label class="admin-field-label admin-field-label-compact">
|
<header class="admin-settings-field-group-head">
|
||||||
<span>{at("tariffs_trial_enabled", {}, "Trial enabled")}</span>
|
<div class="admin-settings-field-group-head-copy">
|
||||||
<div class="admin-setting-switch">
|
<strong>{at("tariffs_trial_group_switch", {}, "Доступ")}</strong>
|
||||||
<Switch.Root
|
<small>
|
||||||
checked={boolValue("TRIAL_ENABLED")}
|
{at(
|
||||||
onCheckedChange={(checked) => setSetting("TRIAL_ENABLED", checked)}
|
"tariffs_trial_group_switch_hint",
|
||||||
class="admin-switch-root"
|
{},
|
||||||
>
|
"Включает или выключает выдачу пробного периода пользователям."
|
||||||
<Switch.Thumb class="admin-switch-thumb" />
|
)}
|
||||||
</Switch.Root>
|
</small>
|
||||||
<small
|
|
||||||
>{boolValue("TRIAL_ENABLED")
|
|
||||||
? at("enabled", {}, "Enabled")
|
|
||||||
: at("disabled", {}, "Disabled")}</small
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</label>
|
{#if dirtyCount(TRIAL_SWITCH_KEYS)}
|
||||||
<label class="admin-field-label admin-field-label-compact">
|
<AdminBadge variant="warning">
|
||||||
<span>{at("tariffs_trial_days", {}, "Duration, days")}</span>
|
{at(
|
||||||
<input
|
"settings_dirty_count",
|
||||||
class="input"
|
{ count: dirtyCount(TRIAL_SWITCH_KEYS) },
|
||||||
type="number"
|
`Изменений: ${dirtyCount(TRIAL_SWITCH_KEYS)}`
|
||||||
min="0"
|
)}
|
||||||
step="1"
|
</AdminBadge>
|
||||||
value={valueForKey("TRIAL_DURATION_DAYS")}
|
{/if}
|
||||||
oninput={(event) => setSetting("TRIAL_DURATION_DAYS", event.currentTarget.value)}
|
</header>
|
||||||
/>
|
<div class="admin-settings-field-group-body">
|
||||||
</label>
|
<div
|
||||||
<label class="admin-field-label admin-field-label-compact">
|
class="admin-setting admin-trial-setting-row"
|
||||||
<span>{at("tariffs_trial_traffic", {}, "Traffic limit, GB")}</span>
|
class:is-dirty={isSettingDirty("TRIAL_ENABLED")}
|
||||||
<input
|
>
|
||||||
class="input"
|
<div class="admin-setting-meta">
|
||||||
type="number"
|
<strong>
|
||||||
min="0"
|
{at("tariffs_trial_enabled", {}, "Триал включён")}
|
||||||
step="0.1"
|
{#if isSettingDirty("TRIAL_ENABLED")}
|
||||||
value={valueForKey("TRIAL_TRAFFIC_LIMIT_GB")}
|
<AdminBadge variant="warning"
|
||||||
oninput={(event) => setSetting("TRIAL_TRAFFIC_LIMIT_GB", event.currentTarget.value)}
|
>{at("settings_badge_dirty", {}, "Изменено")}</AdminBadge
|
||||||
/>
|
>
|
||||||
</label>
|
{/if}
|
||||||
</div>
|
</strong>
|
||||||
<div class="admin-form-row admin-form-row-2">
|
<code>TRIAL_ENABLED</code>
|
||||||
<label class="admin-field-label admin-field-label-compact">
|
</div>
|
||||||
<span>{at("tariffs_trial_strategy", {}, "Traffic reset strategy")}</span>
|
<div class="admin-setting-control">
|
||||||
<AdminSelect
|
<div class="admin-setting-switch">
|
||||||
class="admin-setting-select"
|
<Switch.Root
|
||||||
value={String(valueForKey("TRIAL_TRAFFIC_STRATEGY") || "NO_RESET")}
|
checked={boolValue("TRIAL_ENABLED")}
|
||||||
items={TRAFFIC_STRATEGY_OPTIONS}
|
onCheckedChange={(checked) => setSetting("TRIAL_ENABLED", checked)}
|
||||||
ariaLabel={at("tariffs_trial_strategy", {}, "Traffic reset strategy")}
|
class="admin-switch-root"
|
||||||
onValueChange={(value) => setSetting("TRIAL_TRAFFIC_STRATEGY", value)}
|
>
|
||||||
/>
|
<Switch.Thumb class="admin-switch-thumb" />
|
||||||
</label>
|
</Switch.Root>
|
||||||
<label class="admin-field-label admin-field-label-compact">
|
<span
|
||||||
<span>{at("tariffs_trial_squads", {}, "Trial Internal Squads")}</span>
|
>{boolValue("TRIAL_ENABLED")
|
||||||
<small>
|
? at("enabled", {}, "Включено")
|
||||||
{at(
|
: at("disabled", {}, "Выключено")}</span
|
||||||
"tariffs_trial_squads_hint",
|
>
|
||||||
{},
|
</div>
|
||||||
"These squads are applied when trial is activated. Empty value falls back to USER_SQUAD_UUIDS."
|
{#if isSettingDirty("TRIAL_ENABLED")}
|
||||||
)}
|
<AdminButton
|
||||||
</small>
|
size="sm"
|
||||||
<AdminSelect
|
variant="ghost"
|
||||||
bind:value={selectedTrialSquad}
|
onclick={() => resetSetting("TRIAL_ENABLED")}
|
||||||
items={panelSquadOptions}
|
>
|
||||||
disabled={panelSquadsLoading || !panelSquadOptions.length}
|
<X size={12} />
|
||||||
placeholder={panelSquadsLoading
|
{at("reset", {}, "Сбросить")}
|
||||||
? at("loading", {}, "Loading...")
|
</AdminButton>
|
||||||
: at("tariffs_trial_add_squad", {}, "Add squad from panel")}
|
{/if}
|
||||||
ariaLabel={at("tariffs_trial_add_squad", {}, "Add squad from panel")}
|
</div>
|
||||||
onValueChange={addTrialSquad}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
class="input"
|
|
||||||
type="text"
|
|
||||||
placeholder={valueForKey("USER_SQUAD_UUIDS") || "uuid-a,uuid-b"}
|
|
||||||
value={valueForKey("TRIAL_SQUAD_UUIDS")}
|
|
||||||
oninput={(event) => setSetting("TRIAL_SQUAD_UUIDS", event.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
<div class="admin-chip-list">
|
|
||||||
{#each csvList("TRIAL_SQUAD_UUIDS") as uuid}
|
|
||||||
<button type="button" class="admin-chip" onclick={() => removeTrialSquad(uuid)}>
|
|
||||||
{trialSquadLabel(uuid)}
|
|
||||||
<X size={12} />
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
|
|
||||||
|
<section class="admin-settings-field-group" class:is-dirty={dirtyCount(TRIAL_GENERAL_KEYS)}>
|
||||||
|
<header class="admin-settings-field-group-head">
|
||||||
|
<div class="admin-settings-field-group-head-copy">
|
||||||
|
<strong>{at("tariffs_trial_group_general", {}, "Общие настройки")}</strong>
|
||||||
|
<small>
|
||||||
|
{at(
|
||||||
|
"tariffs_trial_group_general_hint",
|
||||||
|
{},
|
||||||
|
"Длительность пробного доступа и объём трафика, который получает пользователь."
|
||||||
|
)}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
{#if dirtyCount(TRIAL_GENERAL_KEYS)}
|
||||||
|
<AdminBadge variant="warning">
|
||||||
|
{at(
|
||||||
|
"settings_dirty_count",
|
||||||
|
{ count: dirtyCount(TRIAL_GENERAL_KEYS) },
|
||||||
|
`Изменений: ${dirtyCount(TRIAL_GENERAL_KEYS)}`
|
||||||
|
)}
|
||||||
|
</AdminBadge>
|
||||||
|
{/if}
|
||||||
|
</header>
|
||||||
|
<div class="admin-settings-field-group-body">
|
||||||
|
<div
|
||||||
|
class="admin-setting admin-trial-setting-row"
|
||||||
|
class:is-dirty={isSettingDirty("TRIAL_DURATION_DAYS")}
|
||||||
|
>
|
||||||
|
<div class="admin-setting-meta">
|
||||||
|
<strong>
|
||||||
|
{at("tariffs_trial_days", {}, "Длительность, дней")}
|
||||||
|
{#if isSettingDirty("TRIAL_DURATION_DAYS")}
|
||||||
|
<AdminBadge variant="warning"
|
||||||
|
>{at("settings_badge_dirty", {}, "Изменено")}</AdminBadge
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</strong>
|
||||||
|
<code>TRIAL_DURATION_DAYS</code>
|
||||||
|
</div>
|
||||||
|
<div class="admin-setting-control">
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
value={valueForKey("TRIAL_DURATION_DAYS")}
|
||||||
|
oninput={(event) => setSetting("TRIAL_DURATION_DAYS", event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
{#if isSettingDirty("TRIAL_DURATION_DAYS")}
|
||||||
|
<AdminButton
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onclick={() => resetSetting("TRIAL_DURATION_DAYS")}
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
{at("reset", {}, "Сбросить")}
|
||||||
|
</AdminButton>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="admin-setting admin-trial-setting-row"
|
||||||
|
class:is-dirty={isSettingDirty("TRIAL_TRAFFIC_LIMIT_GB")}
|
||||||
|
>
|
||||||
|
<div class="admin-setting-meta">
|
||||||
|
<strong>
|
||||||
|
{at("tariffs_trial_traffic", {}, "Лимит трафика, GB")}
|
||||||
|
{#if isSettingDirty("TRIAL_TRAFFIC_LIMIT_GB")}
|
||||||
|
<AdminBadge variant="warning"
|
||||||
|
>{at("settings_badge_dirty", {}, "Изменено")}</AdminBadge
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</strong>
|
||||||
|
<code>TRIAL_TRAFFIC_LIMIT_GB</code>
|
||||||
|
</div>
|
||||||
|
<div class="admin-setting-control">
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="0.1"
|
||||||
|
value={valueForKey("TRIAL_TRAFFIC_LIMIT_GB")}
|
||||||
|
oninput={(event) =>
|
||||||
|
setSetting("TRIAL_TRAFFIC_LIMIT_GB", event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
{#if isSettingDirty("TRIAL_TRAFFIC_LIMIT_GB")}
|
||||||
|
<AdminButton
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onclick={() => resetSetting("TRIAL_TRAFFIC_LIMIT_GB")}
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
{at("reset", {}, "Сбросить")}
|
||||||
|
</AdminButton>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="admin-settings-field-group" class:is-dirty={dirtyCount(TRIAL_RESET_KEYS)}>
|
||||||
|
<header class="admin-settings-field-group-head">
|
||||||
|
<div class="admin-settings-field-group-head-copy">
|
||||||
|
<strong>{at("tariffs_trial_group_reset", {}, "Сброс трафика")}</strong>
|
||||||
|
<small>
|
||||||
|
{at(
|
||||||
|
"tariffs_trial_group_reset_hint",
|
||||||
|
{},
|
||||||
|
"Стратегия, по которой Remnawave обновляет лимит трафика для пробного периода."
|
||||||
|
)}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
{#if dirtyCount(TRIAL_RESET_KEYS)}
|
||||||
|
<AdminBadge variant="warning">
|
||||||
|
{at(
|
||||||
|
"settings_dirty_count",
|
||||||
|
{ count: dirtyCount(TRIAL_RESET_KEYS) },
|
||||||
|
`Изменений: ${dirtyCount(TRIAL_RESET_KEYS)}`
|
||||||
|
)}
|
||||||
|
</AdminBadge>
|
||||||
|
{/if}
|
||||||
|
</header>
|
||||||
|
<div class="admin-settings-field-group-body">
|
||||||
|
<div
|
||||||
|
class="admin-setting admin-trial-setting-row"
|
||||||
|
class:is-dirty={isSettingDirty("TRIAL_TRAFFIC_STRATEGY")}
|
||||||
|
>
|
||||||
|
<div class="admin-setting-meta">
|
||||||
|
<strong>
|
||||||
|
{at("tariffs_trial_strategy", {}, "Стратегия сброса трафика")}
|
||||||
|
{#if isSettingDirty("TRIAL_TRAFFIC_STRATEGY")}
|
||||||
|
<AdminBadge variant="warning"
|
||||||
|
>{at("settings_badge_dirty", {}, "Изменено")}</AdminBadge
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</strong>
|
||||||
|
<code>TRIAL_TRAFFIC_STRATEGY</code>
|
||||||
|
</div>
|
||||||
|
<div class="admin-setting-control">
|
||||||
|
<AdminSelect
|
||||||
|
class="admin-setting-select"
|
||||||
|
value={String(valueForKey("TRIAL_TRAFFIC_STRATEGY") || "NO_RESET")}
|
||||||
|
items={TRAFFIC_STRATEGY_OPTIONS}
|
||||||
|
ariaLabel={at("tariffs_trial_strategy", {}, "Стратегия сброса трафика")}
|
||||||
|
onValueChange={(value) => setSetting("TRIAL_TRAFFIC_STRATEGY", value)}
|
||||||
|
/>
|
||||||
|
{#if isSettingDirty("TRIAL_TRAFFIC_STRATEGY")}
|
||||||
|
<AdminButton
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onclick={() => resetSetting("TRIAL_TRAFFIC_STRATEGY")}
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
{at("reset", {}, "Сбросить")}
|
||||||
|
</AdminButton>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="admin-settings-field-group" class:is-dirty={dirtyCount(TRIAL_SQUAD_KEYS)}>
|
||||||
|
<header class="admin-settings-field-group-head">
|
||||||
|
<div class="admin-settings-field-group-head-copy">
|
||||||
|
<strong>{at("tariffs_trial_group_squads", {}, "Сквады")}</strong>
|
||||||
|
<small>
|
||||||
|
{at(
|
||||||
|
"tariffs_trial_group_squads_hint",
|
||||||
|
{},
|
||||||
|
"Сквады, которые будут назначены пользователю при активации триала."
|
||||||
|
)}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
{#if dirtyCount(TRIAL_SQUAD_KEYS)}
|
||||||
|
<AdminBadge variant="warning">
|
||||||
|
{at(
|
||||||
|
"settings_dirty_count",
|
||||||
|
{ count: dirtyCount(TRIAL_SQUAD_KEYS) },
|
||||||
|
`Изменений: ${dirtyCount(TRIAL_SQUAD_KEYS)}`
|
||||||
|
)}
|
||||||
|
</AdminBadge>
|
||||||
|
{/if}
|
||||||
|
</header>
|
||||||
|
<div class="admin-settings-field-group-body">
|
||||||
|
<div
|
||||||
|
class="admin-setting admin-trial-setting-row"
|
||||||
|
class:is-dirty={isSettingDirty("TRIAL_SQUAD_UUIDS")}
|
||||||
|
>
|
||||||
|
<div class="admin-setting-meta">
|
||||||
|
<strong>
|
||||||
|
{at("tariffs_trial_squads", {}, "Internal Squads для триала")}
|
||||||
|
{#if isSettingDirty("TRIAL_SQUAD_UUIDS")}
|
||||||
|
<AdminBadge variant="warning"
|
||||||
|
>{at("settings_badge_dirty", {}, "Изменено")}</AdminBadge
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</strong>
|
||||||
|
<code>TRIAL_SQUAD_UUIDS</code>
|
||||||
|
<small>
|
||||||
|
{at(
|
||||||
|
"tariffs_trial_squads_hint",
|
||||||
|
{},
|
||||||
|
"Эти сквады применяются при активации триала. Если поле пустое, используются USER_SQUAD_UUIDS."
|
||||||
|
)}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="admin-setting-control admin-trial-squad-control">
|
||||||
|
{#key trialSquadSelectKey}
|
||||||
|
<AdminSelect
|
||||||
|
value={selectedTrialSquad}
|
||||||
|
items={panelSquadOptions}
|
||||||
|
disabled={panelSquadsLoading || !panelSquadOptions.length}
|
||||||
|
placeholder={panelSquadsLoading
|
||||||
|
? at("loading", {}, "Загрузка...")
|
||||||
|
: at("tariffs_trial_add_squad", {}, "Добавить сквад из панели")}
|
||||||
|
ariaLabel={at("tariffs_trial_add_squad", {}, "Добавить сквад из панели")}
|
||||||
|
onValueChange={handleTrialSquadSelect}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
type="text"
|
||||||
|
placeholder={valueForKey("USER_SQUAD_UUIDS") || "uuid-a,uuid-b"}
|
||||||
|
value={valueForKey("TRIAL_SQUAD_UUIDS")}
|
||||||
|
oninput={(event) => setSetting("TRIAL_SQUAD_UUIDS", event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
{#if isSettingDirty("TRIAL_SQUAD_UUIDS")}
|
||||||
|
<AdminButton
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onclick={() => resetSetting("TRIAL_SQUAD_UUIDS")}
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
{at("reset", {}, "Сбросить")}
|
||||||
|
</AdminButton>
|
||||||
|
{/if}
|
||||||
|
<div class="admin-chip-list">
|
||||||
|
{#each csvList("TRIAL_SQUAD_UUIDS") as uuid}
|
||||||
|
<button type="button" class="admin-chip" onclick={() => removeTrialSquad(uuid)}>
|
||||||
|
{trialSquadLabel(uuid)}
|
||||||
|
<X size={12} />
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -2045,6 +2045,10 @@
|
|||||||
background: color-mix(in srgb, var(--admin-bg) 52%, transparent);
|
background: color-mix(in srgb, var(--admin-bg) 52%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-settings-field-group.is-dirty {
|
||||||
|
border-color: color-mix(in srgb, var(--warning-border) 74%, var(--admin-border));
|
||||||
|
}
|
||||||
|
|
||||||
.admin-settings-field-group-head {
|
.admin-settings-field-group-head {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
@@ -2082,6 +2086,10 @@
|
|||||||
background: color-mix(in srgb, var(--accent) 5%, transparent);
|
background: color-mix(in srgb, var(--accent) 5%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-setting.is-dirty {
|
||||||
|
background: color-mix(in srgb, var(--warning) 7%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
.admin-setting-meta {
|
.admin-setting-meta {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
@@ -2822,6 +2830,38 @@
|
|||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-trial-settings-body .admin-settings-field-groups {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-trial-settings-groups .admin-settings-field-group-head {
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
align-items: start;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-settings-field-group-head-copy {
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-trial-setting-row .input,
|
||||||
|
.admin-trial-setting-row .admin-select-trigger {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-trial-squad-control {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-trial-squad-control .admin-select-trigger,
|
||||||
|
.admin-trial-squad-control .admin-chip-list {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
.admin-tariff-settings-save-row {
|
.admin-tariff-settings-save-row {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
|||||||
@@ -1113,6 +1113,7 @@
|
|||||||
"admin_settings_secret_configured": "Secret is set",
|
"admin_settings_secret_configured": "Secret is set",
|
||||||
"admin_settings_secret_empty": "Not set",
|
"admin_settings_secret_empty": "Not set",
|
||||||
"admin_settings_badge_override": "Override",
|
"admin_settings_badge_override": "Override",
|
||||||
|
"admin_settings_badge_dirty": "Changed",
|
||||||
"admin_status_limited": "Limited",
|
"admin_status_limited": "Limited",
|
||||||
"admin_status_expired": "Expired",
|
"admin_status_expired": "Expired",
|
||||||
"admin_status_disabled": "Disabled",
|
"admin_status_disabled": "Disabled",
|
||||||
@@ -1314,6 +1315,14 @@
|
|||||||
"admin_tariffs_trial_squads": "Trial Internal Squads",
|
"admin_tariffs_trial_squads": "Trial Internal Squads",
|
||||||
"admin_tariffs_trial_squads_hint": "These squads are applied when trial is activated. Empty value falls back to USER_SQUAD_UUIDS.",
|
"admin_tariffs_trial_squads_hint": "These squads are applied when trial is activated. Empty value falls back to USER_SQUAD_UUIDS.",
|
||||||
"admin_tariffs_trial_add_squad": "Add squad from panel",
|
"admin_tariffs_trial_add_squad": "Add squad from panel",
|
||||||
|
"admin_tariffs_trial_group_switch": "Access",
|
||||||
|
"admin_tariffs_trial_group_switch_hint": "Turns trial activation on or off for users.",
|
||||||
|
"admin_tariffs_trial_group_general": "General settings",
|
||||||
|
"admin_tariffs_trial_group_general_hint": "Trial duration and traffic volume granted to the user.",
|
||||||
|
"admin_tariffs_trial_group_reset": "Traffic reset",
|
||||||
|
"admin_tariffs_trial_group_reset_hint": "Strategy Remnawave uses to refresh the trial traffic limit.",
|
||||||
|
"admin_tariffs_trial_group_squads": "Squads",
|
||||||
|
"admin_tariffs_trial_group_squads_hint": "Squads assigned to the user when trial access is activated.",
|
||||||
"admin_tariffs_legacy_title": "Legacy tariff compatibility",
|
"admin_tariffs_legacy_title": "Legacy tariff compatibility",
|
||||||
"admin_tariffs_legacy_subtitle": "Old remnawave-tg-shop periods and traffic packages used only when the JSON tariff catalog is not configured.",
|
"admin_tariffs_legacy_subtitle": "Old remnawave-tg-shop periods and traffic packages used only when the JSON tariff catalog is not configured.",
|
||||||
"admin_tariffs_legacy_period": "Period",
|
"admin_tariffs_legacy_period": "Period",
|
||||||
|
|||||||
@@ -1113,6 +1113,7 @@
|
|||||||
"admin_settings_secret_configured": "Секрет задан",
|
"admin_settings_secret_configured": "Секрет задан",
|
||||||
"admin_settings_secret_empty": "Не задан",
|
"admin_settings_secret_empty": "Не задан",
|
||||||
"admin_settings_badge_override": "Оверрайд",
|
"admin_settings_badge_override": "Оверрайд",
|
||||||
|
"admin_settings_badge_dirty": "Изменено",
|
||||||
"admin_status_limited": "Ограничен",
|
"admin_status_limited": "Ограничен",
|
||||||
"admin_status_expired": "Истёк",
|
"admin_status_expired": "Истёк",
|
||||||
"admin_status_disabled": "Выключен",
|
"admin_status_disabled": "Выключен",
|
||||||
@@ -1314,6 +1315,14 @@
|
|||||||
"admin_tariffs_trial_squads": "Internal Squads для триала",
|
"admin_tariffs_trial_squads": "Internal Squads для триала",
|
||||||
"admin_tariffs_trial_squads_hint": "Эти сквады применяются при активации триала. Если поле пустое, используются USER_SQUAD_UUIDS.",
|
"admin_tariffs_trial_squads_hint": "Эти сквады применяются при активации триала. Если поле пустое, используются USER_SQUAD_UUIDS.",
|
||||||
"admin_tariffs_trial_add_squad": "Добавить сквад из панели",
|
"admin_tariffs_trial_add_squad": "Добавить сквад из панели",
|
||||||
|
"admin_tariffs_trial_group_switch": "Доступ",
|
||||||
|
"admin_tariffs_trial_group_switch_hint": "Включает или выключает выдачу пробного периода пользователям.",
|
||||||
|
"admin_tariffs_trial_group_general": "Общие настройки",
|
||||||
|
"admin_tariffs_trial_group_general_hint": "Длительность пробного доступа и объём трафика, который получает пользователь.",
|
||||||
|
"admin_tariffs_trial_group_reset": "Сброс трафика",
|
||||||
|
"admin_tariffs_trial_group_reset_hint": "Стратегия, по которой Remnawave обновляет лимит трафика для пробного периода.",
|
||||||
|
"admin_tariffs_trial_group_squads": "Сквады",
|
||||||
|
"admin_tariffs_trial_group_squads_hint": "Сквады, которые будут назначены пользователю при активации триала.",
|
||||||
"admin_tariffs_legacy_title": "Совместимость с legacy-тарифами",
|
"admin_tariffs_legacy_title": "Совместимость с legacy-тарифами",
|
||||||
"admin_tariffs_legacy_subtitle": "Старые периоды и пакеты трафика remnawave-tg-shop, которые используются только без JSON-каталога.",
|
"admin_tariffs_legacy_subtitle": "Старые периоды и пакеты трафика remnawave-tg-shop, которые используются только без JSON-каталога.",
|
||||||
"admin_tariffs_legacy_period": "Период",
|
"admin_tariffs_legacy_period": "Период",
|
||||||
|
|||||||
@@ -42,6 +42,14 @@ ADMIN_TARIFF_SETTINGS_PAGE_KEYS = {
|
|||||||
"admin_tariffs_trial_squads",
|
"admin_tariffs_trial_squads",
|
||||||
"admin_tariffs_trial_squads_hint",
|
"admin_tariffs_trial_squads_hint",
|
||||||
"admin_tariffs_trial_add_squad",
|
"admin_tariffs_trial_add_squad",
|
||||||
|
"admin_tariffs_trial_group_switch",
|
||||||
|
"admin_tariffs_trial_group_switch_hint",
|
||||||
|
"admin_tariffs_trial_group_general",
|
||||||
|
"admin_tariffs_trial_group_general_hint",
|
||||||
|
"admin_tariffs_trial_group_reset",
|
||||||
|
"admin_tariffs_trial_group_reset_hint",
|
||||||
|
"admin_tariffs_trial_group_squads",
|
||||||
|
"admin_tariffs_trial_group_squads_hint",
|
||||||
"admin_tariffs_legacy_title",
|
"admin_tariffs_legacy_title",
|
||||||
"admin_tariffs_legacy_subtitle",
|
"admin_tariffs_legacy_subtitle",
|
||||||
"admin_tariffs_legacy_period",
|
"admin_tariffs_legacy_period",
|
||||||
@@ -61,6 +69,7 @@ ADMIN_VISIBLE_RU_KEYS = {
|
|||||||
"admin_search",
|
"admin_search",
|
||||||
"admin_settings_badge_override",
|
"admin_settings_badge_override",
|
||||||
"admin_settings_badge_secret",
|
"admin_settings_badge_secret",
|
||||||
|
"admin_settings_badge_dirty",
|
||||||
"admin_settings_icon_current",
|
"admin_settings_icon_current",
|
||||||
"admin_settings_icon_default_value",
|
"admin_settings_icon_default_value",
|
||||||
"admin_settings_icon_empty",
|
"admin_settings_icon_empty",
|
||||||
|
|||||||
Reference in New Issue
Block a user