This commit is contained in:
parent
c06a187fd6
commit
627be9a4f1
|
@ -861,6 +861,44 @@ const AdminPage: Component<AdminPageProps> = (props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Кнопка копирования значения переменной окружения
|
||||||
|
* @param value - значение для копирования
|
||||||
|
*/
|
||||||
|
function CopyButton({ value }: { value: string }) {
|
||||||
|
/**
|
||||||
|
* Копирует значение в буфер обмена
|
||||||
|
* @param e - событие клика
|
||||||
|
*/
|
||||||
|
const handleCopy = async (e: MouseEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(value)
|
||||||
|
// Можно добавить всплывающее уведомление
|
||||||
|
} catch (err) {
|
||||||
|
alert('Ошибка копирования: ' + (err as Error).message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<button class="copy-btn" title="Скопировать" onClick={handleCopy} style="margin-left: 6px">
|
||||||
|
📋
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Кнопка показать/скрыть значение переменной
|
||||||
|
* @param shown - показывать ли значение
|
||||||
|
* @param onToggle - обработчик переключения
|
||||||
|
*/
|
||||||
|
function ShowHideButton({ shown, onToggle }: { shown: boolean, onToggle: () => void }) {
|
||||||
|
return (
|
||||||
|
<button class="show-btn" title={shown ? 'Скрыть' : 'Показать'} onClick={onToggle} style="margin-left: 6px">
|
||||||
|
{shown ? '🙈' : '👁️'}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Компонент модального окна для редактирования переменной окружения
|
* Компонент модального окна для редактирования переменной окружения
|
||||||
*/
|
*/
|
||||||
|
@ -909,6 +947,17 @@ const AdminPage: Component<AdminPageProps> = (props) => {
|
||||||
* Компонент для отображения переменных окружения
|
* Компонент для отображения переменных окружения
|
||||||
*/
|
*/
|
||||||
const EnvVariablesTab: Component = () => {
|
const EnvVariablesTab: Component = () => {
|
||||||
|
// Сигналы для показа/скрытия значений по ключу
|
||||||
|
const [shownVars, setShownVars] = createSignal<{ [key: string]: boolean }>({})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Переключает показ значения переменной
|
||||||
|
* @param key - ключ переменной
|
||||||
|
*/
|
||||||
|
const toggleShow = (key: string) => {
|
||||||
|
setShownVars((prev) => ({ ...prev, [key]: !prev[key] }))
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="env-variables-container">
|
<div class="env-variables-container">
|
||||||
<Show when={envLoading()}>
|
<Show when={envLoading()}>
|
||||||
|
@ -928,7 +977,6 @@ const AdminPage: Component<AdminPageProps> = (props) => {
|
||||||
<Show when={section.description}>
|
<Show when={section.description}>
|
||||||
<p class="section-description">{section.description}</p>
|
<p class="section-description">{section.description}</p>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<div class="variables-list">
|
<div class="variables-list">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -941,25 +989,32 @@ const AdminPage: Component<AdminPageProps> = (props) => {
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<For each={section.variables}>
|
<For each={section.variables}>
|
||||||
{(variable) => (
|
{(variable) => {
|
||||||
<tr>
|
const shown = shownVars()[variable.key] || false
|
||||||
<td>{variable.key}</td>
|
return (
|
||||||
<td>
|
<tr>
|
||||||
{variable.isSecret
|
<td>{variable.key}</td>
|
||||||
? '••••••••'
|
<td>
|
||||||
: (variable.value || <span class="empty-value">не задано</span>)}
|
{variable.isSecret && !shown
|
||||||
</td>
|
? '••••••••'
|
||||||
<td>{variable.description || '-'}</td>
|
: (variable.value || <span class="empty-value">не задано</span>)}
|
||||||
<td class="actions">
|
<CopyButton value={variable.value || ''} />
|
||||||
<button
|
{variable.isSecret && (
|
||||||
class="edit-button"
|
<ShowHideButton shown={shown} onToggle={() => toggleShow(variable.key)} />
|
||||||
onClick={() => openVariableModal(variable)}
|
)}
|
||||||
>
|
</td>
|
||||||
Изменить
|
<td>{variable.description || '-'}</td>
|
||||||
</button>
|
<td class="actions">
|
||||||
</td>
|
<button
|
||||||
</tr>
|
class="edit-button"
|
||||||
)}
|
onClick={() => openVariableModal(variable)}
|
||||||
|
>
|
||||||
|
Изменить
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}}
|
||||||
</For>
|
</For>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -86,7 +86,9 @@ class EnvManager:
|
||||||
# Переменные, которые следует всегда помечать как секретные
|
# Переменные, которые следует всегда помечать как секретные
|
||||||
SECRET_VARS_PATTERNS = [
|
SECRET_VARS_PATTERNS = [
|
||||||
r".*TOKEN.*", r".*SECRET.*", r".*PASSWORD.*", r".*KEY.*",
|
r".*TOKEN.*", r".*SECRET.*", r".*PASSWORD.*", r".*KEY.*",
|
||||||
r".*PWD.*", r".*PASS.*", r".*CRED.*"
|
r".*PWD.*", r".*PASS.*", r".*CRED.*",
|
||||||
|
r".*JWT.*", r".*SESSION.*", r".*OAUTH.*",
|
||||||
|
r".*GITHUB.*", r".*GOOGLE.*", r".*FACEBOOK.*"
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -178,9 +180,27 @@ class EnvManager:
|
||||||
|
|
||||||
def _is_secret_variable(self, key: str) -> bool:
|
def _is_secret_variable(self, key: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Проверяет, является ли переменная секретной
|
Проверяет, является ли переменная секретной.
|
||||||
|
Секретными считаются:
|
||||||
|
- переменные, подходящие под SECRET_VARS_PATTERNS
|
||||||
|
- переменные с ключами DATABASE_URL, REDIS_URL, DB_URL (точное совпадение, без учета регистра)
|
||||||
|
|
||||||
|
>>> EnvManager()._is_secret_variable('MY_SECRET_TOKEN')
|
||||||
|
True
|
||||||
|
>>> EnvManager()._is_secret_variable('database_url')
|
||||||
|
True
|
||||||
|
>>> EnvManager()._is_secret_variable('REDIS_URL')
|
||||||
|
True
|
||||||
|
>>> EnvManager()._is_secret_variable('DB_URL')
|
||||||
|
True
|
||||||
|
>>> EnvManager()._is_secret_variable('SOME_PUBLIC_KEY')
|
||||||
|
True
|
||||||
|
>>> EnvManager()._is_secret_variable('SOME_PUBLIC_VAR')
|
||||||
|
False
|
||||||
"""
|
"""
|
||||||
key_upper = key.upper()
|
key_upper = key.upper()
|
||||||
|
if key_upper in {"DATABASE_URL", "REDIS_URL", "DB_URL"}:
|
||||||
|
return True
|
||||||
return any(re.match(pattern, key_upper) for pattern in self.SECRET_VARS_PATTERNS)
|
return any(re.match(pattern, key_upper) for pattern in self.SECRET_VARS_PATTERNS)
|
||||||
|
|
||||||
def _determine_variable_type(self, value: str) -> str:
|
def _determine_variable_type(self, value: str) -> str:
|
||||||
|
|
|
@ -75,7 +75,7 @@ class ViewedStorage:
|
||||||
await redis.connect()
|
await redis.connect()
|
||||||
|
|
||||||
# Логируем настройки Redis соединения
|
# Логируем настройки Redis соединения
|
||||||
logger.info(f" * Redis connection: {redis._client}")
|
logger.info("* Redis connected")
|
||||||
|
|
||||||
# Получаем список всех ключей migrated_views_* и находим самый последний
|
# Получаем список всех ключей migrated_views_* и находим самый последний
|
||||||
keys = await redis.execute("KEYS", "migrated_views_*")
|
keys = await redis.execute("KEYS", "migrated_views_*")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user