Skip to content

Commit

Permalink
feat: 添加后台Dashboard #1
Browse files Browse the repository at this point in the history
添加配置Config api 页面,现在可以在网页控制Config
  • Loading branch information
ChisatoNishikigi73 committed Sep 14, 2024
1 parent 38f7923 commit 01cfb89
Show file tree
Hide file tree
Showing 18 changed files with 776 additions and 352 deletions.
81 changes: 80 additions & 1 deletion src/core/server/v2Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Server as ServerType, ServerResponse, IncomingMessage } from 'http'
import { common, config } from 'karin/utils'
import { apiV2 } from 'karin/utils/api/v2/v2'
import cors from 'cors'

export const api2Server = new (class Api2Server {
reg: RegExp
list: string[]
Expand Down Expand Up @@ -36,6 +35,7 @@ export const api2Server = new (class Api2Server {
credentials: true,
}))

/** 获取服务器信息 **/
this.app.get('/api/v2/info/status', async (req, res) => {
res.json({
code: 200,
Expand All @@ -60,6 +60,85 @@ export const api2Server = new (class Api2Server {
})
})

/** Config处理 **/
this.app.get('/api/v2/config/get/:type', async (req, res) => {
const type = req.params.type
let _config = {}
switch (type) {
case 'app':
_config = config.App
break
case 'config':
_config = config.Config
break
case 'group':
_config = config.group
break
case 'pm2':
// Where is pm2 config?
_config = {}
break
case 'redis':
_config = config.redis
break
case 'server':
_config = config.Server
break
case 'all':
_config = config
break
}

res.json({
code: 200,
message: 'ok',
data: {
config: _config,
},
})
})

this.app.post('/api/v2/config/save/:type', async (req, res) => {
let type = req.params.type
if (type === 'app') {
type = 'App'
} else if (type === 'config') {
type = 'config'
} else if (type === 'group') {
type = 'group'
} else if (type === 'pm2') {
type = 'pm2'
} else if (type === 'redis') {
type = 'redis'
} else if (type === 'server') {
type = 'server'
} else {
res.status(400).json({
code: 400,
message: 'Invalid config type',
})
return
}

const data = req.body
try {
await config.saveConfig(type as keyof typeof config, data)
res.json({
code: 200,
message: 'ok',
data: {
// config: data,
},
})
} catch (error) {
logger.error(error)
res.status(500).json({
code: 500,
message: 'Error saving config',
})
}
})

const { host } = config.Server.http
this.api2Server.listen(9000, host, () => {
const localIP = common.getLocalIP()
Expand Down
238 changes: 63 additions & 175 deletions src/public/App.vue
Original file line number Diff line number Diff line change
@@ -1,196 +1,84 @@
<template>
<div class="flex p-6">
<!-- Sidebar -->
<!-- <div class="w-1/12 bg-gray-900 p-4 rounded-lg">
<div class="flex items-center mb-8">
<div class="icon-bg p-2">
<i class="fas fa-heart text-blue-500"></i>
</div>
<span class="ml-4 text-xl font-bold">Karin喵~</span>
</div>
<div class="space-y-6">
<div v-for="(tool, index) in hrTools" :key="index" class="icon-bg p-2">
<span class="ml-4 text-xl font-bold">{{ tool }}</span>
</div>
</div>
<div class="mt-8">
<div class="icon-bg p-2">
<span class="ml-4 text-xl font-bold">Karin 5</span>
</div>
</div>
</div> -->

<!-- Main Content -->
<div class="w-11/12 ml-6">
<div class="flex justify-between items-center mb-6">
<div>
<h1 class="text-2xl font-bold">Morning~ My Master</h1>
<p class="text-gray-400">Welcome to the Karin Management System</p>
</div>
<div class="flex space-x-4">
<div class="icon-bg p-2">
<i class="fas fa-bell text-gray-500"></i>
</div>
<div class="icon-bg p-2">
<i class="fas fa-cog text-gray-500"></i>
</div>
</div>
</div>

<div class="grid grid-cols-3 gap-6">
<!-- Status Overview Card -->
<StatusOverview class="col-span-1" />

<!-- Statistics Cards -->
<div class="col-span-2 grid grid-cols-3 gap-6">
<div v-for="(stat, index) in statistics" :key="index" class="card">
<div class="flex justify-between items-center">
<div>
<h3 class="text-2xl font-bold">{{ stat.value }}</h3>
<p class="text-gray-400">{{ stat.label }}</p>
</div>
<div :class="['progress-circle', stat.color]">
<span class="text-sm">{{ stat.percentage }}</span>
</div>
</div>
<p :class="['mt-2', `text-${stat.color}-500`]">{{ stat.change }}</p>
</div>

<div class="card col-span-3">
<div class="flex justify-between items-center">
<div>
<h3 class="text-2xl font-bold">24</h3>
<p class="text-gray-400">New Tasks Added This Week</p>
</div>
<div class="icon-bg p-2">
<i class="fas fa-tasks text-blue-500"></i>
</div>
</div>
</div>
</div>

<!-- Upcoming Birthdays -->
<div class="card col-span-1">
<h2 class="text-xl font-bold mb-4">Upcoming Birthdays</h2>
<div class="space-y-4">
<div v-for="(birthday, index) in upcomingBirthdays" :key="index" class="flex items-center">
<img :src="birthday.image" :alt="`Profile picture of ${birthday.name}`" class="rounded-full mr-4" height="40" width="40"/>
<div>
<h3 class="text-lg font-bold">{{ birthday.name }}</h3>
<p class="text-gray-400">{{ birthday.date }}</p>
</div>
</div>
</div>
</div>

<!-- Applicants Received Time -->
<div class="card col-span-2">
<h2 class="text-xl font-bold mb-4">Applicants Received Time</h2>
<div class="flex justify-between items-center mb-4">
<p class="text-gray-400">Week</p>
<i class="fas fa-chevron-down text-gray-500"></i>
</div>
<div class="h-40 bg-gray-800 rounded-lg flex items-center justify-center">
<p class="text-gray-500">Graph Placeholder</p>
</div>
</div>

<!-- Calendar -->
<div class="card col-span-1">
<h2 class="text-xl font-bold mb-4">Calendar</h2>
<!-- <div class="flex justify-between items-center mb-4">
<i class="fas fa-chevron-left text-gray-500"></i>
<p class="text-gray-400">July 2022</p>
<i class="fas fa-chevron-right text-gray-500"></i>
</div>
<div class="grid grid-cols-7 gap-2 text-center text-gray-400 mb-4">
<div v-for="day in ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']" :key="day">{{ day }}</div>
<div v-for="date in 31" :key="date" :class="{ 'bg-blue-500 text-white rounded-full': date === 12 }">{{ date }}</div>
</div>
<div class="space-y-4">
<div v-for="(event, index) in calendarEvents" :key="index" class="flex items-center justify-between">
<div class="flex items-center">
<div v-if="event.icon" class="icon-bg p-2">
<i :class="['fas', event.icon, 'text-blue-500']"></i>
</div>
<img v-else :src="event.image" :alt="`Profile picture of ${event.name}`" class="rounded-full mr-4" height="40" width="40"/>
<div class="ml-4">
<h3 class="text-lg font-bold">{{ event.title }}</h3>
<p class="text-gray-400">{{ event.description }}</p>
</div>
</div>
<p class="text-gray-400">{{ event.time }}</p>
</div>
</div> -->
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<button class="w-full mt-4 py-2 bg-blue-500 text-white rounded-lg">View 4 more events</button>
</div>
</div>
</div>
<div class="flex h-screen">
<!-- 侧边栏 -->
<el-menu
:default-active="activeIndex"
@select="handleSelect"
class="el-menu-vertical-demo w-64 h-full"
:collapse="isCollapse"
@open="handleOpen"
@close="handleClose"
background-color="#1e1e1e"
text-color="#ffffff"
active-text-color="#409EFF"
>
<el-menu-item index="1">
<el-icon><icon-menu /></el-icon>
<span>Karin喵~</span>
</el-menu-item>
<el-menu-item v-for="(tool, index) in hrTools" :key="index" :index="(index + 2).toString()">
<el-icon><icon-menu /></el-icon>
<span>{{ tool }}</span>
</el-menu-item>
<el-menu-item index="6">
<el-icon><icon-menu /></el-icon>
<span>Karin 5</span>
</el-menu-item>
</el-menu>

<!-- 主要内容 -->
<component :is="currentComponent" class="flex-1 overflow-auto" />
</div>
</template>

<script setup>
import { ref } from 'vue'
import StatusOverview from './components/StatusOverview.vue'
const hrTools = ref(['Karin 1', 'Karin 2', 'Karin 3', 'Karin 4'])
import { ref, computed } from 'vue'
import { Menu as IconMenu } from '@element-plus/icons-vue'
import MainContent from './components/Dashboard.vue'
import Config from './components/Config.vue'
const activeIndex = ref('1')
const currentComponent = computed(() => {
return activeIndex.value === '2' ? Config : MainContent
})
const statistics = ref([
{ value: 2864, label: 'Applications', color: 'blue', percentage: '+28%', change: '+12% Inc' },
{ value: 485, label: 'Candidates', color: 'purple', percentage: '+12%', change: '+4% Inc' },
{ value: 2864, label: 'Rejected', color: 'red', percentage: '+16%', change: '-8% Inc' }
])
const isCollapse = ref(false)
const hrTools = ref(['Config', 'Karin 2', 'Karin 3', 'Karin 4'])
const upcomingBirthdays = ref([
{ name: 'Sophie Richardson', date: 'Today', image: 'https://example.com/sophie.png' },
{ name: 'Alexia Graves', date: 'Tomorrow', image: 'https://example.com/alexia.png' },
{ name: 'Carl Grin', date: '14.07.2022', image: 'https://example.com/carl.png' }
])
const handleOpen = (key, keyPath) => {
// console.log(key, keyPath)
}
const handleClose = (key, keyPath) => {
// console.log(key, keyPath)
}
const calendarEvents = ref([
{ title: 'Video Call', description: 'with Tom Fell', time: '7-8 AM', icon: 'fa-video' },
{ title: 'Interview', description: 'Penny Porter', time: '9 AM', image: 'https://example.com/penny.png' },
{ title: 'English class', description: 'Individual lesson', time: '10-11 AM', icon: 'fa-graduation-cap' }
])
const handleSelect = (index) => {
activeIndex.value = index
}
</script>

<style>
body {
font-family: 'Inter', sans-serif;
background-color: #121212;
color: #ffffff;
margin: 0;
padding: 0;
}
.card {
background-color: #1e1e1e;
border-radius: 10px;
padding: 20px;
}
.icon-bg {
background-color: #2a2a2a;
border-radius: 50%;
padding: 10px;
}
.progress-circle {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
}
.progress-circle.blue {
background-color: #1e3a8a;
.el-menu {
border-right: none;
}
.progress-circle.purple {
background-color: #6b21a8;
.el-menu-item:hover {
background-color: #2c2c2c !important;
}
.progress-circle.red {
background-color: #b91c1c;
.el-menu-item.is-active {
background-color: #363636 !important;
}
</style>
Loading

0 comments on commit 01cfb89

Please sign in to comment.