Skip to content

Commit

Permalink
feat(webui): support webui without database, fix #234
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Apr 23, 2021
1 parent 8d05514 commit b211cd8
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 31 deletions.
17 changes: 11 additions & 6 deletions packages/koishi-core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,19 +317,21 @@ export class Context {
}
}

on<K extends EventName>(name: K, listener: EventMap[K], prepend = false) {
on<K extends EventName>(name: K, listener: EventMap[K], prepend?: boolean): () => boolean
on(name: string & EventName, listener: Disposable, prepend = false) {
const method = prepend ? 'unshift' : 'push'

// handle plugin-related events
const _listener = listener as Disposable
// handle special events
if (name === 'connect' && this.app.status === App.Status.open) {
return _listener(), () => false
return listener(), () => false
} else if (name === 'before-disconnect') {
this.state.disposables[method](_listener)
return () => remove(this.state.disposables, _listener)
this.state.disposables[method](listener)
return () => remove(this.state.disposables, listener)
} else if (name === 'before-connect') {
// before-connect is side effect
this.addSideEffect()
} else if (typeof name === 'string' && name.startsWith('delegate/')) {
if (this[name.slice(9)]) return listener(), () => false
}

const hooks = this.app._hooks[name] ||= []
Expand Down Expand Up @@ -513,6 +515,7 @@ export class Context {
return value
},
set(value) {
if (!this.app[privateKey]) this.emit('delegate/' + key)
defineProperty(this.app, privateKey, value)
},
})
Expand Down Expand Up @@ -544,6 +547,8 @@ export interface EventMap extends SessionEventMap {
[Context.middleware]: Middleware

// Koishi events
'delegate/assets'(): void
'delegate/database'(): void
'appellation'(name: string, session: Session): string
'before-parse'(content: string, session: Session): Argv
'parse'(argv: Argv, session: Session): string
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-teach/client/teach.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="card-grid profile-grid">
<k-numeric title="总问题数量" icon="quote-left">{{ meta.questions }}</k-numeric>
<k-numeric title="总问答数量" icon="quote-right">{{ meta.dialogues }}</k-numeric>
<k-numeric title="图片服务器" icon="hdd" type="size" :value="meta.assetSize"/>
<k-numeric title="图片服务器" icon="hdd" type="size" :value="meta.assetSize" fallback="暂无数据"/>
</div>
<div class="card-grid chart-grid">
<word-cloud/>
Expand Down
9 changes: 5 additions & 4 deletions packages/plugin-webui/client/components/numeric.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@ const props = defineProps<{
icon: string
type?: 'size'
value?: number
fallback?: string
}>()
const text = computed(() => {
if (!props.value) return '暂无数据'
if (!props.value) return props.fallback
if (props.type === 'size') {
if (props.value >= (1 << 20) * 1000) {
return +(props.value / (1 << 30)).toFixed(1) + ' GB'
return (props.value / (1 << 30)).toFixed(1) + ' GB'
} else if (props.value >= (1 << 10) * 1000) {
return +(props.value / (1 << 20)).toFixed(1) + ' MB'
return (props.value / (1 << 20)).toFixed(1) + ' MB'
} else {
return +(props.value / (1 << 10)).toFixed(1) + ' KB'
return (props.value / (1 << 10)).toFixed(1) + ' KB'
}
}
})
Expand Down
6 changes: 4 additions & 2 deletions packages/plugin-webui/client/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,19 @@ self['KoishiClient'] = client

const app = Vue.createApp(App)

const statsPrerequisite: 'stats'[] = KOISHI_CONFIG.database ? ['stats'] : []

router.addRoute({
path: '/',
name: '仪表盘',
meta: { icon: 'tachometer-alt', require: ['stats', 'meta', 'profile', 'registry'] },
meta: { icon: 'tachometer-alt', require: [...statsPrerequisite, 'meta', 'profile', 'registry'] },
component: () => import('./views/home/home.vue'),
})

router.addRoute({
path: '/bots',
name: '机器人',
meta: { icon: 'robot', require: ['stats', 'profile'] },
meta: { icon: 'robot', require: [...statsPrerequisite, 'profile'] },
component: () => import('./views/bots.vue'),
})

Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-webui/client/views/bots.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
<th>用户名</th>
<th>运行状态</th>
<th>当前消息频率</th>
<th>近期消息频率</th>
<th v-if="stats">近期消息频率</th>
</tr>
<tr v-for="{ platform, username, selfId, code, currentRate } in profile.bots">
<td>{{ platform }}</td>
<td>{{ username }}</td>
<td>{{ codes[code] }}</td>
<td>发送 {{ currentRate[0] }}/min,接收 {{ currentRate[1] }}/min</td>
<td>发送 {{ stats.botSend[`${platform}:${selfId}`] || 0 }}/d,接收 {{ stats.botReceive[`${platform}:${selfId}`] || 0 }}/d</td>
<td v-if="stats">发送 {{ stats.botSend[`${platform}:${selfId}`] || 0 }}/d,接收 {{ stats.botReceive[`${platform}:${selfId}`] || 0 }}/d</td>
</tr>
</table>
<p v-else>暂无数据。</p>
Expand Down
24 changes: 14 additions & 10 deletions packages/plugin-webui/client/views/home/home.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
<template>
<div class="card-grid profile-grid">
<k-numeric title="当前消息频率" icon="paper-plane">{{ currentRate }} / min</k-numeric>
<k-numeric title="近期消息频率" icon="history">{{ recentRate }} / d</k-numeric>
<k-numeric title="近期消息频率" icon="history" v-if="config.database">{{ recentRate }} / d</k-numeric>
<k-numeric title="命名插件数量" icon="plug">{{ registry.pluginCount }}</k-numeric>
<k-numeric title="数据库体积" icon="database" type="size" :value="meta.storageSize"/>
<k-numeric title="活跃用户数量" icon="heart">{{ meta.activeUsers }}</k-numeric>
<k-numeric title="活跃群数量" icon="users">{{ meta.activeGroups }}</k-numeric>
</div>
<load-chart/>
<div class="card-grid chart-grid">
<history-chart/>
<hour-chart/>
<group-chart/>
<k-numeric title="数据库体积" icon="database" type="size" :value="meta.storageSize" v-if="meta" fallback="未安装"/>
<k-numeric title="活跃用户数量" icon="heart" v-if="config.database">{{ meta.activeUsers }}</k-numeric>
<k-numeric title="活跃群数量" icon="users" v-if="config.database">{{ meta.activeGroups }}</k-numeric>
</div>
<template v-if="config.database">
<load-chart/>
<div class="card-grid chart-grid">
<history-chart/>
<hour-chart/>
<group-chart/>
</div>
</template>
</template>

<script setup lang="ts">
Expand All @@ -24,6 +26,8 @@ import HistoryChart from './history-chart.vue'
import HourChart from './hour-chart.vue'
import LoadChart from './load-chart.vue'
const config = KOISHI_CONFIG
const currentRate = computed(() => {
return profile.value.bots.reduce((sum, bot) => sum + bot.currentRate[0], 0)
})
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-webui/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export class Meta implements DataSource<Meta.Payload> {

constructor(private ctx: Context, public config: Meta.Config) {
this.extend(async () => ctx.assets?.stats())
this.extend(async () => ctx.database.getStats())
this.extend(async () => ctx.database?.getStats())

ctx.all().on('command', ({ session }: Argv<'lastCall'>) => {
session.user.lastCall = new Date()
Expand Down
10 changes: 8 additions & 2 deletions packages/plugin-webui/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface Config extends BaseConfig, Profile.Config, Meta.Config, Registr
}

export interface ClientConfig extends Required<BaseConfig> {
database: boolean
endpoint: string
extensions: string[]
}
Expand Down Expand Up @@ -71,7 +72,7 @@ export class WebServer extends Adapter {

const { apiPath, uiPath, devMode, selfUrl, title } = config
const endpoint = selfUrl + apiPath
this.global = { title, uiPath, endpoint, devMode, extensions: [] }
this.global = { title, uiPath, endpoint, devMode, extensions: [], database: false }
this.root = resolve(__dirname, '..', devMode ? 'client' : 'dist')

this.server = new WebSocket.Server({
Expand Down Expand Up @@ -100,6 +101,10 @@ export class WebServer extends Adapter {

ctx.on('connect', () => this.start())
ctx.before('disconnect', () => this.stop())

ctx.on('delegate/database', () => {
this.global.database = !!ctx.database
})
}

broadcast(type: string, body: any) {
Expand Down Expand Up @@ -165,6 +170,7 @@ export class WebServer extends Adapter {
})

socket.on('message', async (data) => {
if (!this.ctx.database) return
const { type, body } = JSON.parse(data.toString())
const method = WebServer.listeners[type]
if (method) {
Expand Down Expand Up @@ -240,7 +246,7 @@ export class WebServer extends Adapter {
export namespace WebServer {
export interface Sources extends Record<string, DataSource> {
meta: Meta
stats: Statistics
stats?: Statistics
profile: Profile
registry: Registry
}
Expand Down
11 changes: 8 additions & 3 deletions packages/plugin-webui/src/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ export class Statistics implements DataSource<Statistics.Payload> {
average = average

constructor(private ctx: Context, public config: Statistics.Config = {}) {
this.sync = ctx.database.createSynchronizer()

if (config.handleSignals !== false) {
const handleSignal = (signal: NodeJS.Signals) => {
new Logger('app').info(`terminated by ${signal}`)
Expand All @@ -103,6 +101,10 @@ export class Statistics implements DataSource<Statistics.Payload> {
})
}

ctx.on('delegate/database', () => {
this.sync = ctx.database.createSynchronizer()
})

ctx.before('disconnect', async () => {
// rollback to default implementation to prevent infinite call stack
if (Session.prototype.send[customTag]) {
Expand All @@ -111,6 +113,8 @@ export class Statistics implements DataSource<Statistics.Payload> {
await this.upload(true)
})

ctx = ctx.select('database')

ctx.before('command', ({ command, session }) => {
if (command.parent?.name !== 'test') {
const [name] = command.name.split('.', 1)
Expand Down Expand Up @@ -150,7 +154,7 @@ export class Statistics implements DataSource<Statistics.Payload> {
if (forced || +date - +this.lastUpdate > REFRESH_INTERVAL || dateHour !== this.updateHour) {
this.lastUpdate = date
this.updateHour = dateHour
await this.sync.upload(date)
await this.sync?.upload(date)
}
}

Expand Down Expand Up @@ -229,6 +233,7 @@ export class Statistics implements DataSource<Statistics.Payload> {
}

async get() {
if (!this.sync) return
const date = new Date()
const dateNumber = Time.getDateNumber(date, date.getTimezoneOffset())
if (dateNumber !== this.cachedDate) {
Expand Down

0 comments on commit b211cd8

Please sign in to comment.