feat: improve admin trial settings controls

This commit is contained in:
3252a8
2026-05-24 19:19:39 +03:00
parent 3c4ff66150
commit 00b54e1f15
5 changed files with 409 additions and 89 deletions
+342 -89
View File
@@ -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>
+40
View File
@@ -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;
+9
View File
@@ -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",
+9
View File
@@ -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",