feat: 前端搭建-未完成
This commit is contained in:
53
web/src/views/Dashboard.vue
Normal file
53
web/src/views/Dashboard.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="dashboard-page">
|
||||
<h1>仪表盘</h1>
|
||||
<div v-if="loading">加载中...</div>
|
||||
<div v-if="stats" class="stats-grid">
|
||||
<el-card>
|
||||
<el-statistic title="总请求数" :value="stats.total_requests" />
|
||||
</el-card>
|
||||
<el-card>
|
||||
<el-statistic title="成功请求数" :value="stats.success_requests" />
|
||||
</el-card>
|
||||
<el-card>
|
||||
<el-statistic title="成功率" :value="stats.success_rate" :formatter="rateFormatter" />
|
||||
</el-card>
|
||||
</div>
|
||||
<el-card v-if="stats && stats.group_stats.length > 0" class="chart-card">
|
||||
<StatsChart :data="stats.group_stats" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useDashboardStore } from '@/stores/dashboardStore';
|
||||
import StatsChart from '@/components/StatsChart.vue';
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
const { stats, loading } = storeToRefs(dashboardStore);
|
||||
|
||||
onMounted(() => {
|
||||
dashboardStore.fetchStats();
|
||||
});
|
||||
|
||||
const rateFormatter = (rate: number) => {
|
||||
return `${(rate * 100).toFixed(2)}%`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard-page {
|
||||
padding: 20px;
|
||||
}
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.chart-card {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
53
web/src/views/Groups.vue
Normal file
53
web/src/views/Groups.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="groups-view">
|
||||
<el-row :gutter="20" class="main-layout">
|
||||
<el-col :span="6" class="left-panel">
|
||||
<group-list />
|
||||
</el-col>
|
||||
<el-col :span="18" class="right-panel">
|
||||
<div class="config-section">
|
||||
<group-config-form />
|
||||
</div>
|
||||
<div class="keys-section">
|
||||
<key-table />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import GroupList from '@/components/GroupList.vue';
|
||||
import GroupConfigForm from '@/components/GroupConfigForm.vue';
|
||||
import KeyTable from '@/components/KeyTable.vue';
|
||||
import { ElRow, ElCol } from 'element-plus';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.groups-view {
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.main-layout, .left-panel, .right-panel {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.config-section, .keys-section {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
63
web/src/views/Logs.vue
Normal file
63
web/src/views/Logs.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="logs-page">
|
||||
<h1>日志查询</h1>
|
||||
<LogFilter />
|
||||
<el-table :data="logs" v-loading="loading" style="width: 100%">
|
||||
<el-table-column prop="timestamp" label="时间" width="180" :formatter="formatDate" />
|
||||
<el-table-column prop="group_id" label="分组ID" width="100" />
|
||||
<el-table-column prop="key_id" label="密钥ID" width="100" />
|
||||
<el-table-column prop="source_ip" label="源IP" width="150" />
|
||||
<el-table-column prop="status_code" label="状态码" width="100" />
|
||||
<el-table-column prop="request_path" label="请求路径" />
|
||||
<el-table-column prop="request_body_snippet" label="请求体片段" />
|
||||
</el-table>
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next, sizes"
|
||||
:total="pagination.total"
|
||||
:page-size="pagination.size"
|
||||
:current-page="pagination.page"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
class="pagination-container"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useLogStore } from '@/stores/logStore';
|
||||
import LogFilter from '@/components/LogFilter.vue';
|
||||
import type { RequestLog } from '@/types/models';
|
||||
|
||||
const logStore = useLogStore();
|
||||
const { logs, loading, pagination } = storeToRefs(logStore);
|
||||
|
||||
onMounted(() => {
|
||||
logStore.fetchLogs();
|
||||
});
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
logStore.setPage(page);
|
||||
};
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
logStore.setSize(size);
|
||||
};
|
||||
|
||||
const formatDate = (_row: RequestLog, _column: any, cellValue: string) => {
|
||||
return new Date(cellValue).toLocaleString();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.logs-page {
|
||||
padding: 20px;
|
||||
}
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
44
web/src/views/Settings.vue
Normal file
44
web/src/views/Settings.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<h1>Settings</h1>
|
||||
<el-form :model="form" label-width="200px">
|
||||
<el-form-item v-for="setting in settings" :key="setting.key" :label="setting.key">
|
||||
<el-input v-model="form[setting.key]"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveSettings">Save</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { useSettingStore } from '@/stores/settingStore';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { Setting } from '@/types/models';
|
||||
|
||||
const settingStore = useSettingStore();
|
||||
const { settings, loading } = storeToRefs(settingStore);
|
||||
|
||||
const form = ref<Record<string, string>>({});
|
||||
|
||||
onMounted(() => {
|
||||
settingStore.fetchSettings();
|
||||
});
|
||||
|
||||
watch(settings, (newSettings) => {
|
||||
form.value = newSettings.reduce((acc, setting) => {
|
||||
acc[setting.key] = setting.value;
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
}, { immediate: true, deep: true });
|
||||
|
||||
const saveSettings = () => {
|
||||
const settingsToUpdate: Setting[] = Object.entries(form.value).map(([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
}));
|
||||
settingStore.updateSettings(settingsToUpdate);
|
||||
};
|
||||
</script>
|
Reference in New Issue
Block a user