f65b2ca8d8
- AdminBillingView: 4 stats (MRR, Выручка, Просрочка, Возвраты) + v-data-table 7 колонок (Тенант с ИНН / Тариф / Баланс с error-color / пополнения / списания / MRR / Статус-chip) + поиск
- AdminIncidentsView: 3 stats + 5 фильтров статуса + v-list с incident_id (INC-YYYY-MMDD-NNNN) + severity/status/РКН-pending chips + дедлайн 24ч по 152-ФЗ
- AdminSystemView: read-only warning + поиск + v-list 7 system_settings (webhook_rate_limit, login_max_attempts, retention и т.д.) с type-chip и updated_at
- composables/mockAdmin.ts: AdminBillingTenantRow + AdminIncidentRow + AdminSystemSetting + mock-данные
- Router: /admin/{billing,incidents,system} → реальные views (не placeholder)
- Vitest +13 (179/179 за 11.98с)
- TODO: edit-flow для system_settings + backend /api/admin/* endpoints
- Регресс: lint+type+format OK; build 743ms; story:build 21/28 за 31.5с
- CLAUDE.md v1.42→v1.43, реестр v1.51→v1.52
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
128 lines
4.1 KiB
Vue
128 lines
4.1 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* Админка SaaS → Система.
|
||
*
|
||
* Глобальные настройки SaaS-уровня (system_settings по schema v8.7 §10):
|
||
* лимиты квот, тарифные планы, фичефлаги, fallback supplier_id.
|
||
*
|
||
* MVP — display + read-only edit-режим. Backend `/api/admin/system-settings`
|
||
* + edit-flow подключаются отдельным коммитом.
|
||
*/
|
||
import { ADMIN_SYSTEM_SETTINGS } from '../../composables/mockAdmin';
|
||
import type { AdminSystemSetting } from '../../composables/mockAdmin';
|
||
import { computed, ref } from 'vue';
|
||
|
||
const search = ref('');
|
||
|
||
const filteredSettings = computed(() => {
|
||
const q = search.value.trim().toLowerCase();
|
||
if (!q) return ADMIN_SYSTEM_SETTINGS;
|
||
return ADMIN_SYSTEM_SETTINGS.filter(
|
||
(s) => s.key.toLowerCase().includes(q) || s.description.toLowerCase().includes(q),
|
||
);
|
||
});
|
||
|
||
const typeColor: Record<AdminSystemSetting['type'], string> = {
|
||
int: 'info',
|
||
string: 'success',
|
||
bool: 'warning',
|
||
json: 'secondary',
|
||
};
|
||
|
||
function formatDate(iso: string): string {
|
||
return new Date(iso).toLocaleDateString('ru-RU', {
|
||
day: '2-digit',
|
||
month: '2-digit',
|
||
year: 'numeric',
|
||
});
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<v-container fluid class="admin-system pa-6">
|
||
<header class="page-head mb-4">
|
||
<h1 class="text-h4 page-title">Система</h1>
|
||
<p class="text-body-2 text-medium-emphasis ma-0">
|
||
Глобальные настройки SaaS: лимиты квот, тарифные планы, фичефлаги.
|
||
</p>
|
||
</header>
|
||
|
||
<v-alert type="warning" variant="tonal" class="mb-4" density="compact">
|
||
<strong>Read-only режим.</strong> Edit-flow с двойным подтверждением и audit-log подключается отдельным
|
||
коммитом.
|
||
</v-alert>
|
||
|
||
<v-card variant="outlined" class="pa-4">
|
||
<div class="d-flex justify-space-between align-center mb-3">
|
||
<h2 class="text-h6 ma-0">system_settings</h2>
|
||
<v-text-field
|
||
v-model="search"
|
||
placeholder="Поиск по ключу или описанию"
|
||
prepend-inner-icon="mdi-magnify"
|
||
density="compact"
|
||
variant="outlined"
|
||
hide-details
|
||
clearable
|
||
style="max-width: 320px"
|
||
/>
|
||
</div>
|
||
|
||
<v-list class="settings-list">
|
||
<v-list-item
|
||
v-for="setting in filteredSettings"
|
||
:key="setting.key"
|
||
class="setting-row"
|
||
data-testid="setting-row"
|
||
>
|
||
<div class="setting-header">
|
||
<span class="setting-key font-mono">{{ setting.key }}</span>
|
||
<v-chip :color="typeColor[setting.type]" size="x-small" variant="tonal" class="ml-2">
|
||
{{ setting.type }}
|
||
</v-chip>
|
||
</div>
|
||
<div class="setting-value font-mono mt-1">{{ setting.value }}</div>
|
||
<div class="text-caption text-medium-emphasis mt-1">
|
||
{{ setting.description }} · обновлено {{ formatDate(setting.updated_at) }}
|
||
</div>
|
||
</v-list-item>
|
||
</v-list>
|
||
</v-card>
|
||
</v-container>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.admin-system {
|
||
max-width: 1100px;
|
||
}
|
||
.page-title {
|
||
font-variation-settings: 'opsz' 28;
|
||
letter-spacing: -0.018em;
|
||
}
|
||
.setting-row {
|
||
padding-block: 12px;
|
||
border-bottom: 1px solid #e1eeea;
|
||
}
|
||
.setting-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
.setting-header {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.setting-key {
|
||
font-weight: 500;
|
||
font-size: 14px;
|
||
color: #081319;
|
||
}
|
||
.setting-value {
|
||
font-size: 13px;
|
||
background: #f6f3ec;
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
display: inline-block;
|
||
}
|
||
.font-mono {
|
||
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
||
}
|
||
</style>
|