mirror of
https://gitee.com/bootx/dax-pay-ui
synced 2026-08-12 07:25:39 +08:00
refactor(admin): 开源版移除异常登录检测配置页(转商业版)
This commit is contained in:
@@ -60,18 +60,6 @@ export const SecurityApi = {
|
||||
updateTwoFactorAuthConfig(data: TwoFactorAuthConfig): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/platform/config/security/two-factor-auth/update', data });
|
||||
},
|
||||
/**
|
||||
* 获取异常登录检测配置
|
||||
*/
|
||||
getAnomalyDetectionConfig(): Promise<Result<AnomalyDetectionConfig>> {
|
||||
return defHttp.get({ url: '/platform/config/security/anomaly-detection/get' });
|
||||
},
|
||||
/**
|
||||
* 更新异常登录检测配置
|
||||
*/
|
||||
updateAnomalyDetectionConfig(data: AnomalyDetectionConfig): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/platform/config/security/anomaly-detection/update', data });
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -131,6 +119,8 @@ export interface SessionManagementConfig {
|
||||
enabled?: boolean;
|
||||
/** 最大在线时长(小时) */
|
||||
maxOnlineHours?: number;
|
||||
/** 最大活跃时长(小时),0表示不限制 */
|
||||
activeTimeoutHours?: number;
|
||||
/** 最大并发会话数 */
|
||||
maxConcurrentSessions?: number;
|
||||
/** 并发策略 */
|
||||
@@ -148,23 +138,3 @@ export interface TwoFactorAuthConfig {
|
||||
/** 备用码数量 */
|
||||
backupCodesCount?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常登录检测配置
|
||||
*/
|
||||
export interface AnomalyDetectionConfig {
|
||||
/** 是否启用 */
|
||||
enabled?: boolean;
|
||||
/** 是否检测异常IP */
|
||||
detectAnomalousIp?: boolean;
|
||||
/** IP风险阈值 */
|
||||
ipRiskThreshold?: number;
|
||||
/** 是否检测异常时间 */
|
||||
detectAnomalousTime?: boolean;
|
||||
/** 时间偏差阈值 */
|
||||
timeDeviationThreshold?: number;
|
||||
/** 是否检测异常设备 */
|
||||
detectAnomalousDevice?: boolean;
|
||||
/** 检测到异常时是否阻止 */
|
||||
blockOnAnomaly?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"title": "Anomaly Detection",
|
||||
"description": "Detect and block anomalous login behaviors",
|
||||
"summary": {
|
||||
"enabled": "Detection enabled",
|
||||
"disabled": "Detection disabled",
|
||||
"ipDetectionEnabled": "IP detection enabled, threshold {threshold} points",
|
||||
"ipDetectionDisabled": "IP detection disabled",
|
||||
"timeDetectionEnabled": "Time detection enabled, deviation threshold {hours} hours",
|
||||
"timeDetectionDisabled": "Time detection disabled",
|
||||
"deviceDetectionEnabled": "Device detection enabled",
|
||||
"deviceDetectionDisabled": "Device detection disabled",
|
||||
"blockEnabled": "Anomalous login will be blocked",
|
||||
"blockDisabled": "Anomalous login will only alert"
|
||||
},
|
||||
"section": {
|
||||
"basic": "Basic Settings",
|
||||
"ipDetection": "IP Detection",
|
||||
"timeDetection": "Time Detection",
|
||||
"deviceDetection": "Device Detection",
|
||||
"blockStrategy": "Block Strategy"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "Enable Anomaly Detection",
|
||||
"desc": "When enabled, the system will detect anomalous login behaviors."
|
||||
},
|
||||
"detectAnomalousIp": {
|
||||
"label": "Detect Anomalous IP",
|
||||
"desc": "Detect if login IP has risks, such as proxy IP or high-risk regions."
|
||||
},
|
||||
"ipRiskThreshold": {
|
||||
"label": "IP Risk Threshold",
|
||||
"desc": "IP risk score above this threshold is considered anomalous.",
|
||||
"placeholder": "Enter IP risk threshold"
|
||||
},
|
||||
"detectAnomalousTime": {
|
||||
"label": "Detect Anomalous Login Time",
|
||||
"desc": "Detect if login time deviates from user's typical login hours."
|
||||
},
|
||||
"timeDeviationThreshold": {
|
||||
"label": "Time Deviation Threshold",
|
||||
"desc": "Login time deviation from user's typical hours above this threshold is considered anomalous.",
|
||||
"placeholder": "Enter time deviation threshold"
|
||||
},
|
||||
"detectAnomalousDevice": {
|
||||
"label": "Detect Anomalous Device",
|
||||
"desc": "Detect if login device is user's commonly used device."
|
||||
},
|
||||
"blockOnAnomaly": {
|
||||
"label": "Block on Anomaly",
|
||||
"desc": "When enabled, anomalous login will be blocked; when disabled, only alerts will be recorded."
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"title": "异常登录检测",
|
||||
"description": "检测并拦截异常登录行为",
|
||||
"summary": {
|
||||
"enabled": "检测已启用",
|
||||
"disabled": "检测未启用",
|
||||
"ipDetectionEnabled": "IP检测已启用,阈值 {threshold} 分",
|
||||
"ipDetectionDisabled": "IP检测未启用",
|
||||
"timeDetectionEnabled": "时间检测已启用,偏离阈值 {hours} 小时",
|
||||
"timeDetectionDisabled": "时间检测未启用",
|
||||
"deviceDetectionEnabled": "设备检测已启用",
|
||||
"deviceDetectionDisabled": "设备检测未启用",
|
||||
"blockEnabled": "异常登录将拦截",
|
||||
"blockDisabled": "异常登录仅告警"
|
||||
},
|
||||
"section": {
|
||||
"basic": "基础设置",
|
||||
"ipDetection": "IP检测",
|
||||
"timeDetection": "时间检测",
|
||||
"deviceDetection": "设备检测",
|
||||
"blockStrategy": "拦截策略"
|
||||
},
|
||||
"enabled": {
|
||||
"label": "启用异常登录检测",
|
||||
"desc": "开启后,系统将检测异常登录行为。"
|
||||
},
|
||||
"detectAnomalousIp": {
|
||||
"label": "检测异常IP",
|
||||
"desc": "检测登录IP是否存在风险,如代理IP、高风险地区等。"
|
||||
},
|
||||
"ipRiskThreshold": {
|
||||
"label": "IP风险阈值",
|
||||
"desc": "IP风险评分超过该阈值时视为异常。",
|
||||
"placeholder": "请输入IP风险阈值"
|
||||
},
|
||||
"detectAnomalousTime": {
|
||||
"label": "检测异常登录时间",
|
||||
"desc": "检测登录时间是否偏离用户习惯的登录时段。"
|
||||
},
|
||||
"timeDeviationThreshold": {
|
||||
"label": "登录时间偏离阈值",
|
||||
"desc": "登录时间偏离用户习惯时段超过该阈值时视为异常。",
|
||||
"placeholder": "请输入时间偏离阈值"
|
||||
},
|
||||
"detectAnomalousDevice": {
|
||||
"label": "检测异常设备",
|
||||
"desc": "检测登录设备是否为用户常用设备。"
|
||||
},
|
||||
"blockOnAnomaly": {
|
||||
"label": "检测到异常登录时拦截",
|
||||
"desc": "开启后,检测到异常登录将直接拦截;关闭则仅记录告警。"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import AnomalyDetection from './components/AnomalyDetection.vue';
|
||||
import LoginSecurity from './components/LoginSecurity.vue';
|
||||
import PasswordPolicy from './components/PasswordPolicy.vue';
|
||||
import SessionManagement from './components/SessionManagement.vue';
|
||||
@@ -28,13 +27,6 @@
|
||||
// 登录安全描述
|
||||
description: $t('system.security.login-security.description'),
|
||||
},
|
||||
{
|
||||
key: 'anomalyDetection',
|
||||
// 异常登录检测标题
|
||||
label: $t('system.security.anomaly-detection.title'),
|
||||
// 异常登录检测描述
|
||||
description: $t('system.security.anomaly-detection.description'),
|
||||
},
|
||||
{
|
||||
key: 'sessionManagement',
|
||||
// 会话管理标题
|
||||
@@ -80,7 +72,6 @@
|
||||
<div class="security-content__scroll">
|
||||
<PasswordPolicy v-if="activeKey === 'passwordPolicy'" />
|
||||
<LoginSecurity v-else-if="activeKey === 'loginSecurity'" />
|
||||
<AnomalyDetection v-else-if="activeKey === 'anomalyDetection'" />
|
||||
<SessionManagement v-else-if="activeKey === 'sessionManagement'" />
|
||||
<TwoFactorAuth v-else-if="activeKey === 'twoFactorAuth'" />
|
||||
</div>
|
||||
|
||||
@@ -1,413 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { AnomalyDetectionConfig } from '#/api/system/security.api';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { SecurityApi } from '#/api/system/security.api';
|
||||
import { useMessage } from '#/hooks/useMessage';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
defineOptions({ name: 'AnomalyDetection' });
|
||||
|
||||
const { confirm, message } = useMessage();
|
||||
|
||||
const loading = ref(false);
|
||||
const formRef = ref();
|
||||
// 编辑状态
|
||||
const isEditing = ref(false);
|
||||
|
||||
const formState = ref<AnomalyDetectionConfig>({} as AnomalyDetectionConfig);
|
||||
|
||||
const summaryItems = computed(() => {
|
||||
return [
|
||||
// 启用状态
|
||||
formState.value.enabled
|
||||
? $t('system.security.anomaly-detection.summary.enabled')
|
||||
: $t('system.security.anomaly-detection.summary.disabled'),
|
||||
// IP检测
|
||||
formState.value.detectAnomalousIp
|
||||
? $t('system.security.anomaly-detection.summary.ipDetectionEnabled', {
|
||||
threshold: formState.value.ipRiskThreshold ?? 0,
|
||||
})
|
||||
: $t('system.security.anomaly-detection.summary.ipDetectionDisabled'),
|
||||
// 时间检测
|
||||
formState.value.detectAnomalousTime
|
||||
? $t('system.security.anomaly-detection.summary.timeDetectionEnabled', {
|
||||
hours: formState.value.timeDeviationThreshold ?? 0,
|
||||
})
|
||||
: $t('system.security.anomaly-detection.summary.timeDetectionDisabled'),
|
||||
// 设备检测
|
||||
formState.value.detectAnomalousDevice
|
||||
? $t('system.security.anomaly-detection.summary.deviceDetectionEnabled')
|
||||
: $t('system.security.anomaly-detection.summary.deviceDetectionDisabled'),
|
||||
// 拦截策略
|
||||
formState.value.blockOnAnomaly
|
||||
? $t('system.security.anomaly-detection.summary.blockEnabled')
|
||||
: $t('system.security.anomaly-detection.summary.blockDisabled'),
|
||||
];
|
||||
});
|
||||
|
||||
/**
|
||||
* 加载异常登录检测配置
|
||||
*/
|
||||
async function loadConfig() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data } = await SecurityApi.getAnomalyDetectionConfig();
|
||||
formState.value = data;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入编辑模式
|
||||
*/
|
||||
function handleEdit() {
|
||||
isEditing.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消编辑
|
||||
*/
|
||||
function handleCancel() {
|
||||
loadConfig();
|
||||
isEditing.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存异常登录检测配置
|
||||
*/
|
||||
function handleSave() {
|
||||
confirm({
|
||||
// 确认保存
|
||||
title: $t('system.security.common.confirmSave'),
|
||||
// 确定要保存当前配置吗?
|
||||
content: $t('system.security.common.confirmSaveContent'),
|
||||
okText: $t('common.okText'),
|
||||
cancelText: $t('common.cancelText'),
|
||||
onOk: async () => {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
} catch {
|
||||
// 校验失败:表单已显示错误提示
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
await SecurityApi.updateAnomalyDetectionConfig(formState.value);
|
||||
// 保存成功提示
|
||||
message.success($t('common.saveSuccess'));
|
||||
await loadConfig();
|
||||
isEditing.value = false;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-spin :spinning="loading" class="w-full">
|
||||
<div class="security-module-page">
|
||||
<div class="module-overview">
|
||||
<div class="module-overview__header">
|
||||
<!-- 异常登录检测标题 -->
|
||||
<div class="module-overview__title">{{ $t('system.security.anomaly-detection.title') }}</div>
|
||||
<div class="module-actions">
|
||||
<a-space>
|
||||
<!-- 非编辑状态:显示编辑按钮 -->
|
||||
<template v-if="!isEditing">
|
||||
<a-button type="primary" @click="handleEdit">{{ $t('common.edit') }}</a-button>
|
||||
</template>
|
||||
<!-- 编辑状态:显示取消和确认按钮 -->
|
||||
<template v-else>
|
||||
<a-button @click="handleCancel">{{ $t('system.security.common.cancel') }}</a-button>
|
||||
<a-button type="primary" :loading="loading" @click="handleSave">{{
|
||||
$t('system.security.common.confirm')
|
||||
}}</a-button>
|
||||
</template>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 异常登录检测描述 -->
|
||||
<div class="module-overview__desc">{{ $t('system.security.anomaly-detection.description') }}</div>
|
||||
<a-space wrap size="small" class="module-overview__tags">
|
||||
<a-tag v-for="item in summaryItems" :key="item">{{ item }}</a-tag>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<a-form ref="formRef" :model="formState" layout="vertical" class="module-form">
|
||||
<div class="config-section">
|
||||
<!-- 基础设置 -->
|
||||
<div class="config-section__title">{{ $t('system.security.anomaly-detection.section.basic') }}</div>
|
||||
|
||||
<div class="config-item">
|
||||
<div class="config-item__main">
|
||||
<!-- 启用异常登录检测标签 -->
|
||||
<div class="config-item__label">{{ $t('system.security.anomaly-detection.enabled.label') }}</div>
|
||||
<!-- 启用异常登录检测描述 -->
|
||||
<div class="config-item__desc">{{ $t('system.security.anomaly-detection.enabled.desc') }}</div>
|
||||
</div>
|
||||
<a-switch v-model:checked="formState.enabled" :disabled="!isEditing" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-section">
|
||||
<!-- IP检测 -->
|
||||
<div class="config-section__title">{{ $t('system.security.anomaly-detection.section.ipDetection') }}</div>
|
||||
|
||||
<div class="config-grid">
|
||||
<div class="config-item">
|
||||
<div class="config-item__main">
|
||||
<!-- 检测异常IP标签 -->
|
||||
<div class="config-item__label">{{
|
||||
$t('system.security.anomaly-detection.detectAnomalousIp.label')
|
||||
}}</div>
|
||||
<!-- 检测异常IP描述 -->
|
||||
<div class="config-item__desc">{{
|
||||
$t('system.security.anomaly-detection.detectAnomalousIp.desc')
|
||||
}}</div>
|
||||
</div>
|
||||
<a-switch v-model:checked="formState.detectAnomalousIp" :disabled="!isEditing" />
|
||||
</div>
|
||||
|
||||
<div class="config-item config-item--block">
|
||||
<div class="config-item__main">
|
||||
<!-- IP风险阈值标签 -->
|
||||
<div class="config-item__label">{{
|
||||
$t('system.security.anomaly-detection.ipRiskThreshold.label')
|
||||
}}</div>
|
||||
<!-- IP风险阈值描述 -->
|
||||
<div class="config-item__desc">{{ $t('system.security.anomaly-detection.ipRiskThreshold.desc') }}</div>
|
||||
</div>
|
||||
<div class="number-field">
|
||||
<!-- 国际化:请输入IP风险阈值 -->
|
||||
<a-input-number
|
||||
v-model:value="formState.ipRiskThreshold"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:placeholder="$t('system.security.anomaly-detection.ipRiskThreshold.placeholder')"
|
||||
:disabled="!isEditing"
|
||||
style="width: 180px"
|
||||
/>
|
||||
<!-- 单位:分 -->
|
||||
<span class="number-field__suffix">{{ $t('system.security.common.unit.score') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-section">
|
||||
<!-- 时间检测 -->
|
||||
<div class="config-section__title">{{ $t('system.security.anomaly-detection.section.timeDetection') }}</div>
|
||||
|
||||
<div class="config-grid">
|
||||
<div class="config-item">
|
||||
<div class="config-item__main">
|
||||
<!-- 检测异常登录时间标签 -->
|
||||
<div class="config-item__label">{{
|
||||
$t('system.security.anomaly-detection.detectAnomalousTime.label')
|
||||
}}</div>
|
||||
<!-- 检测异常登录时间描述 -->
|
||||
<div class="config-item__desc">{{
|
||||
$t('system.security.anomaly-detection.detectAnomalousTime.desc')
|
||||
}}</div>
|
||||
</div>
|
||||
<a-switch v-model:checked="formState.detectAnomalousTime" :disabled="!isEditing" />
|
||||
</div>
|
||||
|
||||
<div class="config-item config-item--block">
|
||||
<div class="config-item__main">
|
||||
<!-- 登录时间偏离阈值标签 -->
|
||||
<div class="config-item__label">{{
|
||||
$t('system.security.anomaly-detection.timeDeviationThreshold.label')
|
||||
}}</div>
|
||||
<!-- 登录时间偏离阈值描述 -->
|
||||
<div class="config-item__desc">{{
|
||||
$t('system.security.anomaly-detection.timeDeviationThreshold.desc')
|
||||
}}</div>
|
||||
</div>
|
||||
<div class="number-field">
|
||||
<!-- 国际化:请输入时间偏离阈值 -->
|
||||
<a-input-number
|
||||
v-model:value="formState.timeDeviationThreshold"
|
||||
:min="1"
|
||||
:max="24"
|
||||
:placeholder="$t('system.security.anomaly-detection.timeDeviationThreshold.placeholder')"
|
||||
:disabled="!isEditing"
|
||||
style="width: 180px"
|
||||
/>
|
||||
<!-- 单位:小时 -->
|
||||
<span class="number-field__suffix">{{ $t('system.security.common.unit.hour') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-section">
|
||||
<!-- 设备检测 -->
|
||||
<div class="config-section__title">{{ $t('system.security.anomaly-detection.section.deviceDetection') }}</div>
|
||||
|
||||
<div class="config-item">
|
||||
<div class="config-item__main">
|
||||
<!-- 检测异常设备标签 -->
|
||||
<div class="config-item__label">{{
|
||||
$t('system.security.anomaly-detection.detectAnomalousDevice.label')
|
||||
}}</div>
|
||||
<!-- 检测异常设备描述 -->
|
||||
<div class="config-item__desc">{{
|
||||
$t('system.security.anomaly-detection.detectAnomalousDevice.desc')
|
||||
}}</div>
|
||||
</div>
|
||||
<a-switch v-model:checked="formState.detectAnomalousDevice" :disabled="!isEditing" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-section">
|
||||
<!-- 拦截策略 -->
|
||||
<div class="config-section__title">{{ $t('system.security.anomaly-detection.section.blockStrategy') }}</div>
|
||||
|
||||
<div class="config-item">
|
||||
<div class="config-item__main">
|
||||
<!-- 检测到异常登录时拦截标签 -->
|
||||
<div class="config-item__label">{{ $t('system.security.anomaly-detection.blockOnAnomaly.label') }}</div>
|
||||
<!-- 检测到异常登录时拦截描述 -->
|
||||
<div class="config-item__desc">{{ $t('system.security.anomaly-detection.blockOnAnomaly.desc') }}</div>
|
||||
</div>
|
||||
<a-switch v-model:checked="formState.blockOnAnomaly" :disabled="!isEditing" />
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.security-module-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.module-overview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.module-overview__header {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.module-overview__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.module-overview__desc {
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.module-overview__tags {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.module-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.config-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.config-section__title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.config-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.config-item {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 12px;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.config-item:hover {
|
||||
border-color: hsl(var(--primary) / 30%);
|
||||
box-shadow: 0 1px 2px rgb(15 23 42 / 4%);
|
||||
}
|
||||
|
||||
.config-item--block {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.config-item--full {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.config-item__main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.config-item__label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.config-item__desc {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.number-field {
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.number-field__suffix {
|
||||
flex: 0 0 auto;
|
||||
font-size: 13px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.module-actions {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user