refactor(config): streamline sensitive words editing and simplify UI copy
This commit is contained in:
1 parent
8f716f9e01
commit
395ac1c4fc
5 files changed
+242
-93
No files matched your search
+6
-10
@@ -682,16 +682,12 @@ export const jaOverrides = {
|
||||
'config.tabs.software': 'ソフトウェア設定',
|
||||
'config.tabs.sensitiveWords': '機密語の難読化',
|
||||
'config.sensitiveWords.title': 'システムプロンプトの機密語',
|
||||
'config.sensitiveWords.intro': 'CPA コアの antigravity.sensitive-words と devin.sensitive-words を個別に設定します。1 行に 1 語またはフレーズを入力してください。保存時に空行と前後の空白を除去します。',
|
||||
'config.sensitiveWords.antigravity': 'Antigravity の機密語の難読化',
|
||||
'config.sensitiveWords.antigravityDescription': 'システムプロンプト内の一致した語にゼロ幅文字を挿入し、上流の完全一致を避けます。',
|
||||
'config.sensitiveWords.antigravityPlaceholder': '例:proxy',
|
||||
'config.sensitiveWords.antigravityHint': '大文字と小文字は区別しません。2 文字未満の項目はコア側で無視されます。',
|
||||
'config.sensitiveWords.devin': 'Devin のシステムプロンプト機密語',
|
||||
'config.sensitiveWords.devinDescription': '一致した語を含むシステムプロンプトの行全体を削除し、残りを難読化します。通常のメッセージやツール引数は変更しません。',
|
||||
'config.sensitiveWords.devinPlaceholder': '例:機密フレーズ',
|
||||
'config.sensitiveWords.devinHint': '注意:一致するとシステムプロンプトの行全体が削除されます。',
|
||||
'config.sensitiveWords.restartHint': '実行中のコアが新しい設定を自動で読み込まない場合は、保存後にコアを再起動してください。',
|
||||
'config.sensitiveWords.addPlaceholder': '語句を入力して Enter で追加',
|
||||
'config.sensitiveWords.empty': '項目なし',
|
||||
'config.sensitiveWords.discardTitle': '未保存の機密語を破棄しますか?',
|
||||
'config.sensitiveWords.discardMessage': '更新すると、未保存の項目は現在のコア設定で上書きされます。',
|
||||
'config.sensitiveWords.antigravity': 'Antigravity',
|
||||
'config.sensitiveWords.devin': 'Devin',
|
||||
'config.error.emptyKey': '認証キーを入力してください',
|
||||
'config.error.invalidKey': '空白を含まない表示可能な ASCII 文字のみ使用できます',
|
||||
'config.error.duplicateKey': '同じ認証キーがすでに存在します',
|
||||
|
||||
+6
-10
@@ -679,16 +679,12 @@ export const en: Record<MessageKey, string> = {
|
||||
'config.tabs.software': 'Software Settings',
|
||||
'config.tabs.sensitiveWords': 'Sensitive Words',
|
||||
'config.sensitiveWords.title': 'System Prompt Sensitive Words',
|
||||
'config.sensitiveWords.intro': 'Configure antigravity.sensitive-words and devin.sensitive-words in the CPA core separately. Enter one word or phrase per line; blank lines and surrounding spaces are removed on save.',
|
||||
'config.sensitiveWords.antigravity': 'Antigravity Word Obfuscation',
|
||||
'config.sensitiveWords.antigravityDescription': 'Inserts zero-width characters into matching system-prompt terms to reduce exact upstream keyword matches.',
|
||||
'config.sensitiveWords.antigravityPlaceholder': 'For example: proxy',
|
||||
'config.sensitiveWords.antigravityHint': 'Case-insensitive; the core ignores entries shorter than two characters.',
|
||||
'config.sensitiveWords.devin': 'Devin System Prompt Sensitive Words',
|
||||
'config.sensitiveWords.devinDescription': 'Removes entire system-prompt lines containing a configured term, then obfuscates the remaining prompt. Other messages and tool arguments are not changed.',
|
||||
'config.sensitiveWords.devinPlaceholder': 'For example: sensitive phrase',
|
||||
'config.sensitiveWords.devinHint': 'Use caution: a match deletes the entire system-prompt line.',
|
||||
'config.sensitiveWords.restartHint': 'If the running core does not reload the new configuration automatically, restart it after saving.',
|
||||
'config.sensitiveWords.addPlaceholder': 'Enter a word and press Enter',
|
||||
'config.sensitiveWords.empty': 'No entries',
|
||||
'config.sensitiveWords.discardTitle': 'Discard unsaved sensitive words?',
|
||||
'config.sensitiveWords.discardMessage': 'Refreshing replaces your unsaved entries with the current core configuration.',
|
||||
'config.sensitiveWords.antigravity': 'Antigravity',
|
||||
'config.sensitiveWords.devin': 'Devin',
|
||||
'config.error.emptyKey': 'The authentication key cannot be empty',
|
||||
'config.error.invalidKey': 'Use visible ASCII characters only, without spaces',
|
||||
'config.error.duplicateKey': 'This authentication key already exists',
|
||||
|
||||
@@ -678,16 +678,12 @@ export const zhCN = {
|
||||
'config.tabs.software': '软件设置',
|
||||
'config.tabs.sensitiveWords': '敏感词混淆',
|
||||
'config.sensitiveWords.title': '系统提示词敏感词',
|
||||
'config.sensitiveWords.intro': '分别配置 CPA 内核的 antigravity.sensitive-words 与 devin.sensitive-words。每行输入一个词或短语,保存时自动去除空行和首尾空格。',
|
||||
'config.sensitiveWords.antigravity': 'Antigravity 敏感词混淆',
|
||||
'config.sensitiveWords.antigravityDescription': '在系统提示词匹配项中插入零宽字符,降低上游精确关键词匹配。',
|
||||
'config.sensitiveWords.antigravityPlaceholder': '例如:proxy',
|
||||
'config.sensitiveWords.antigravityHint': '不区分大小写;少于 2 个字符的词条会被内核忽略。',
|
||||
'config.sensitiveWords.devin': 'Devin 系统提示词敏感词',
|
||||
'config.sensitiveWords.devinDescription': '含配置词的整行系统提示词会被删除,其余系统提示词再进行混淆;普通消息及工具参数不受影响。',
|
||||
'config.sensitiveWords.devinPlaceholder': '例如:敏感短语',
|
||||
'config.sensitiveWords.devinHint': '请谨慎配置:命中内容会导致整行系统提示词被删除。',
|
||||
'config.sensitiveWords.restartHint': '保存后若运行中的内核未自动加载新配置,请重启内核。',
|
||||
'config.sensitiveWords.addPlaceholder': '输入词条,回车添加',
|
||||
'config.sensitiveWords.empty': '暂无词条',
|
||||
'config.sensitiveWords.discardTitle': '放弃未保存的敏感词?',
|
||||
'config.sensitiveWords.discardMessage': '刷新会用当前内核配置覆盖尚未保存的词条。',
|
||||
'config.sensitiveWords.antigravity': 'Antigravity',
|
||||
'config.sensitiveWords.devin': 'Devin',
|
||||
'config.error.emptyKey': '鉴权密钥不能为空',
|
||||
'config.error.invalidKey': '只能使用 ASCII 可见字符,且不能包含空格',
|
||||
'config.error.duplicateKey': '该鉴权密钥已经存在',
|
||||
|
||||
@@ -1,62 +1,204 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState, type ClipboardEvent } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { Check, RefreshCw, ShieldAlert } from 'lucide-react';
|
||||
import { Check, Plus, RefreshCw, ShieldAlert, Trash2 } from 'lucide-react';
|
||||
import { useI18n } from '../i18n';
|
||||
import { useConfirmation } from '../components/ConfirmationDialog';
|
||||
|
||||
type SensitiveWordsSettings = {
|
||||
antigravitySensitiveWords: string[];
|
||||
devinSensitiveWords: string[];
|
||||
};
|
||||
|
||||
const wordsFromText = (text: string): string[] => text.split(/\r?\n/).map((word) => word.trim()).filter(Boolean);
|
||||
type Provider = 'antigravity' | 'devin';
|
||||
type WordEntry = { id: number; value: string };
|
||||
type WordDraft = Record<Provider, WordEntry[]>;
|
||||
|
||||
const normalizedWords = (entries: WordEntry[]) => entries.map(({ value }) => value.trim()).filter(Boolean);
|
||||
|
||||
type WordEditorProps = {
|
||||
provider: Provider;
|
||||
entries: WordEntry[];
|
||||
composer: string;
|
||||
disabled: boolean;
|
||||
onAdd: (provider: Provider, value: string) => void;
|
||||
onCompose: (provider: Provider, value: string) => void;
|
||||
onChange: (provider: Provider, id: number, value: string) => void;
|
||||
onRemove: (provider: Provider, id: number) => void;
|
||||
onPaste: (provider: Provider, id: number, event: ClipboardEvent<HTMLInputElement>) => void;
|
||||
};
|
||||
|
||||
function WordEditor({ provider, entries, composer, disabled, onAdd, onCompose, onChange, onRemove, onPaste }: WordEditorProps) {
|
||||
const { t } = useI18n();
|
||||
const title = t(provider === 'antigravity' ? 'config.sensitiveWords.antigravity' : 'config.sensitiveWords.devin');
|
||||
|
||||
return (
|
||||
<section className="config-sensitive-words-card">
|
||||
<div className="config-sensitive-words-card-heading">
|
||||
<h3>{title}</h3>
|
||||
<span className="config-sensitive-words-count" aria-label={`${entries.length} ${title}`}>{entries.length}</span>
|
||||
</div>
|
||||
<div className="config-sensitive-words-composer">
|
||||
<input
|
||||
className="config-network-input"
|
||||
type="text"
|
||||
aria-label={`${title} ${t('common.add')}`}
|
||||
value={composer}
|
||||
disabled={disabled}
|
||||
placeholder={t('config.sensitiveWords.addPlaceholder')}
|
||||
onChange={(event) => onCompose(provider, event.currentTarget.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== 'Enter' || event.nativeEvent.isComposing) return;
|
||||
event.preventDefault();
|
||||
onAdd(provider, composer);
|
||||
}}
|
||||
onPaste={(event) => {
|
||||
const pasted = event.clipboardData.getData('text');
|
||||
if (!/[\r\n]/.test(pasted)) return;
|
||||
event.preventDefault();
|
||||
const input = event.currentTarget;
|
||||
const start = input.selectionStart ?? input.value.length;
|
||||
const end = input.selectionEnd ?? start;
|
||||
onAdd(provider, input.value.slice(0, start) + pasted + input.value.slice(end));
|
||||
}}
|
||||
/>
|
||||
<button type="button" className="icon-button quiet" disabled={disabled || !composer.trim()}
|
||||
title={t('common.add')} aria-label={`${t('common.add')} ${title}`}
|
||||
onClick={() => onAdd(provider, composer)}>
|
||||
<Plus size={16} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
{entries.length === 0 ? (
|
||||
<p className="config-sensitive-words-empty">{t('config.sensitiveWords.empty')}</p>
|
||||
) : (
|
||||
<div className="config-sensitive-words-list">
|
||||
{entries.map((entry, index) => (
|
||||
<div className="config-sensitive-words-row" key={entry.id}>
|
||||
<input
|
||||
id={`config-${provider}-word-${entry.id}`}
|
||||
className="config-network-input"
|
||||
type="text"
|
||||
aria-label={`${title} ${index + 1}`}
|
||||
value={entry.value}
|
||||
disabled={disabled}
|
||||
onChange={(event) => onChange(provider, entry.id, event.currentTarget.value)}
|
||||
onPaste={(event) => onPaste(provider, entry.id, event)}
|
||||
/>
|
||||
<button type="button" className="icon-button quiet" disabled={disabled}
|
||||
title={t('common.delete')} aria-label={`${t('common.delete')} ${title} ${index + 1}`}
|
||||
onClick={() => onRemove(provider, entry.id)}>
|
||||
<Trash2 size={15} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function SensitiveWordsPage() {
|
||||
const { t } = useI18n();
|
||||
const { askConfirmation, confirmationDialog } = useConfirmation();
|
||||
const nextEntryId = useRef(0);
|
||||
const [saved, setSaved] = useState<SensitiveWordsSettings | null>(null);
|
||||
const [antigravityDraft, setAntigravityDraft] = useState('');
|
||||
const [devinDraft, setDevinDraft] = useState('');
|
||||
const [draft, setDraft] = useState<WordDraft>({ antigravity: [], devin: [] });
|
||||
const [composer, setComposer] = useState<Record<Provider, string>>({ antigravity: '', devin: '' });
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [savedNotice, setSavedNotice] = useState(false);
|
||||
|
||||
const replaceDraft = useCallback((settings: SensitiveWordsSettings) => {
|
||||
const toEntries = (words: string[]) => words.map((value) => ({ id: nextEntryId.current++, value }));
|
||||
setDraft({
|
||||
antigravity: toEntries(settings.antigravitySensitiveWords),
|
||||
devin: toEntries(settings.devinSensitiveWords),
|
||||
});
|
||||
setComposer({ antigravity: '', devin: '' });
|
||||
}, []);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const result = await invoke<SensitiveWordsSettings>('get_core_sensitive_words_settings');
|
||||
setSaved(result);
|
||||
setAntigravityDraft(result.antigravitySensitiveWords.join('\n'));
|
||||
setDevinDraft(result.devinSensitiveWords.join('\n'));
|
||||
replaceDraft(result);
|
||||
setSavedNotice(false);
|
||||
} catch (cause) {
|
||||
setSaved(null);
|
||||
setError(String(cause));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
}, [replaceDraft]);
|
||||
|
||||
useEffect(() => { void load(); }, [load]);
|
||||
|
||||
const antigravitySensitiveWords = wordsFromText(antigravityDraft);
|
||||
const devinSensitiveWords = wordsFromText(devinDraft);
|
||||
const words: SensitiveWordsSettings = {
|
||||
antigravitySensitiveWords: [...normalizedWords(draft.antigravity), ...(composer.antigravity.trim() ? [composer.antigravity.trim()] : [])],
|
||||
devinSensitiveWords: [...normalizedWords(draft.devin), ...(composer.devin.trim() ? [composer.devin.trim()] : [])],
|
||||
};
|
||||
const dirty = saved !== null && (
|
||||
JSON.stringify(antigravitySensitiveWords) !== JSON.stringify(saved.antigravitySensitiveWords)
|
||||
|| JSON.stringify(devinSensitiveWords) !== JSON.stringify(saved.devinSensitiveWords)
|
||||
JSON.stringify(words.antigravitySensitiveWords) !== JSON.stringify(saved.antigravitySensitiveWords)
|
||||
|| JSON.stringify(words.devinSensitiveWords) !== JSON.stringify(saved.devinSensitiveWords)
|
||||
);
|
||||
|
||||
const changeWord = (provider: Provider, id: number, value: string) => {
|
||||
setDraft((current) => ({
|
||||
...current,
|
||||
[provider]: current[provider].map((entry) => entry.id === id ? { ...entry, value } : entry),
|
||||
}));
|
||||
setSavedNotice(false);
|
||||
};
|
||||
|
||||
const composeWord = (provider: Provider, value: string) => {
|
||||
setComposer((current) => ({ ...current, [provider]: value }));
|
||||
setSavedNotice(false);
|
||||
};
|
||||
|
||||
const addWords = (provider: Provider, value: string) => {
|
||||
const entries = value.split(/\r\n|\n|\r/).map((word) => word.trim()).filter(Boolean)
|
||||
.map((word) => ({ id: nextEntryId.current++, value: word }));
|
||||
if (entries.length === 0) return;
|
||||
setDraft((current) => ({ ...current, [provider]: [...current[provider], ...entries] }));
|
||||
setComposer((current) => ({ ...current, [provider]: '' }));
|
||||
setSavedNotice(false);
|
||||
};
|
||||
|
||||
const removeWord = (provider: Provider, id: number) => {
|
||||
setDraft((current) => ({ ...current, [provider]: current[provider].filter((entry) => entry.id !== id) }));
|
||||
setSavedNotice(false);
|
||||
};
|
||||
|
||||
const pasteWords = (provider: Provider, id: number, event: ClipboardEvent<HTMLInputElement>) => {
|
||||
const pasted = event.clipboardData.getData('text');
|
||||
if (!/[\r\n]/.test(pasted)) return;
|
||||
event.preventDefault();
|
||||
const input = event.currentTarget;
|
||||
const lines = pasted.split(/\r\n|\n|\r/).filter((line) => line.trim());
|
||||
if (lines.length === 0) return;
|
||||
const start = input.selectionStart ?? input.value.length;
|
||||
const end = input.selectionEnd ?? start;
|
||||
const entries = lines.map((value, index) => ({ id: index === 0 ? id : nextEntryId.current++, value }));
|
||||
entries[0].value = input.value.slice(0, start) + entries[0].value;
|
||||
entries[entries.length - 1].value += input.value.slice(end);
|
||||
setDraft((current) => ({
|
||||
...current,
|
||||
[provider]: current[provider].flatMap((entry) => entry.id === id ? entries : [entry]),
|
||||
}));
|
||||
setSavedNotice(false);
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
if (!dirty || saving) return;
|
||||
setSaving(true);
|
||||
setError('');
|
||||
try {
|
||||
const result = await invoke<SensitiveWordsSettings>('save_core_sensitive_words_settings', {
|
||||
settings: { antigravitySensitiveWords, devinSensitiveWords },
|
||||
settings: words,
|
||||
});
|
||||
setSaved(result);
|
||||
setAntigravityDraft(result.antigravitySensitiveWords.join('\n'));
|
||||
setDevinDraft(result.devinSensitiveWords.join('\n'));
|
||||
replaceDraft(result);
|
||||
setSavedNotice(true);
|
||||
} catch (cause) {
|
||||
setError(String(cause));
|
||||
@@ -65,6 +207,15 @@ export function SensitiveWordsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const refresh = async () => {
|
||||
if (dirty && !await askConfirmation({
|
||||
title: t('config.sensitiveWords.discardTitle'),
|
||||
message: t('config.sensitiveWords.discardMessage'),
|
||||
confirmText: t('common.refresh'),
|
||||
})) return;
|
||||
await load();
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="panel config-sensitive-words-panel">
|
||||
<div className="config-panel-heading">
|
||||
@@ -76,7 +227,7 @@ export function SensitiveWordsPage() {
|
||||
{dirty ? <span className="state-pill">{t('config.network.unsaved')}</span>
|
||||
: savedNotice ? <span className="state-pill success">{t('config.network.saved')}</span> : null}
|
||||
<button type="button" className="icon-button quiet" title={t('common.refresh')} aria-label={t('common.refresh')}
|
||||
disabled={loading || saving} onClick={() => void load()}>
|
||||
disabled={loading || saving} onClick={() => void refresh()}>
|
||||
<RefreshCw size={16} aria-hidden="true" />
|
||||
</button>
|
||||
<button type="button" className="primary-button compact-button" disabled={loading || saving || !dirty}
|
||||
@@ -86,31 +237,20 @@ export function SensitiveWordsPage() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="config-sensitive-words-intro">{t('config.sensitiveWords.intro')}</p>
|
||||
{error ? <div className="config-form-message error" role="alert">{error}</div> : null}
|
||||
{loading ? <p>{t('common.loading')}</p> : saved === null ? (
|
||||
<button type="button" className="secondary-button compact-button" onClick={() => void load()}>{t('common.retry')}</button>
|
||||
) : (
|
||||
<div className="config-sensitive-words-grid">
|
||||
<div className="config-sensitive-words-card">
|
||||
<label htmlFor="config-antigravity-sensitive-words">{t('config.sensitiveWords.antigravity')}</label>
|
||||
<p>{t('config.sensitiveWords.antigravityDescription')}</p>
|
||||
<textarea id="config-antigravity-sensitive-words" className="config-network-input config-sensitive-words-input"
|
||||
value={antigravityDraft} disabled={saving} placeholder={t('config.sensitiveWords.antigravityPlaceholder')}
|
||||
onChange={(event) => { setAntigravityDraft(event.currentTarget.value); setSavedNotice(false); }} />
|
||||
<small>{t('config.sensitiveWords.antigravityHint')}</small>
|
||||
</div>
|
||||
<div className="config-sensitive-words-card">
|
||||
<label htmlFor="config-devin-sensitive-words">{t('config.sensitiveWords.devin')}</label>
|
||||
<p>{t('config.sensitiveWords.devinDescription')}</p>
|
||||
<textarea id="config-devin-sensitive-words" className="config-network-input config-sensitive-words-input"
|
||||
value={devinDraft} disabled={saving} placeholder={t('config.sensitiveWords.devinPlaceholder')}
|
||||
onChange={(event) => { setDevinDraft(event.currentTarget.value); setSavedNotice(false); }} />
|
||||
<small>{t('config.sensitiveWords.devinHint')}</small>
|
||||
</div>
|
||||
<WordEditor provider="antigravity" entries={draft.antigravity} composer={composer.antigravity}
|
||||
disabled={saving} onAdd={addWords} onCompose={composeWord} onChange={changeWord}
|
||||
onRemove={removeWord} onPaste={pasteWords} />
|
||||
<WordEditor provider="devin" entries={draft.devin} composer={composer.devin}
|
||||
disabled={saving} onAdd={addWords} onCompose={composeWord} onChange={changeWord}
|
||||
onRemove={removeWord} onPaste={pasteWords} />
|
||||
</div>
|
||||
)}
|
||||
{saved ? <small className="config-sensitive-words-intro">{t('config.sensitiveWords.restartHint')}</small> : null}
|
||||
{confirmationDialog}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
+49
-28
@@ -3260,51 +3260,73 @@ textarea:focus-visible {
|
||||
|
||||
.config-sensitive-words-panel {
|
||||
display: grid;
|
||||
width: min(960px, 100%);
|
||||
gap: 18px;
|
||||
width: min(780px, 100%);
|
||||
gap: 16px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.config-sensitive-words-intro,
|
||||
.config-sensitive-words-card p,
|
||||
.config-sensitive-words-card small {
|
||||
color: var(--theme-7a746c);
|
||||
font-size: var(--font-size-meta);
|
||||
line-height: var(--line-height-body);
|
||||
}
|
||||
|
||||
.config-sensitive-words-intro,
|
||||
.config-sensitive-words-card p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.config-sensitive-words-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.config-sensitive-words-card {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
align-content: start;
|
||||
gap: 10px;
|
||||
padding: 18px;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--theme-e3e1db);
|
||||
border-radius: 8px;
|
||||
background: var(--theme-faf8f2);
|
||||
background: var(--theme-fffdf8);
|
||||
}
|
||||
|
||||
.config-sensitive-words-card label {
|
||||
.config-sensitive-words-card-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.config-sensitive-words-card-heading h3 {
|
||||
margin: 0;
|
||||
color: var(--theme-39352f);
|
||||
font-size: var(--font-size-body);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.config-sensitive-words-input {
|
||||
height: 168px;
|
||||
padding: 10px 11px;
|
||||
resize: vertical;
|
||||
line-height: var(--line-height-body);
|
||||
.config-sensitive-words-count {
|
||||
min-width: 22px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
background: var(--theme-f4f1ea);
|
||||
color: var(--theme-7a746c);
|
||||
font-size: var(--font-size-meta);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.config-sensitive-words-composer,
|
||||
.config-sensitive-words-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 38px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.config-sensitive-words-composer .icon-button,
|
||||
.config-sensitive-words-row .icon-button {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.config-sensitive-words-list {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.config-sensitive-words-empty {
|
||||
margin: 0;
|
||||
padding: 6px 0;
|
||||
color: var(--theme-7a746c);
|
||||
font-size: var(--font-size-meta);
|
||||
}
|
||||
|
||||
.config-software-panel > .config-panel-heading {
|
||||
@@ -14649,7 +14671,6 @@ body.table-col-resizing * {
|
||||
}
|
||||
|
||||
.config-network-grid,
|
||||
.config-sensitive-words-grid,
|
||||
.config-tls-fields,
|
||||
.config-management-form,
|
||||
.client-api-grid,
|
||||
|
||||
Reference in new issue
Block a user