Skip to content

Commit

Permalink
feat(webui): add npm integration
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Apr 17, 2021
1 parent 69a6920 commit 2827a5c
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 141 deletions.
8 changes: 6 additions & 2 deletions packages/plugin-webui/client/components/badge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { defineProps } from 'vue'
defineProps<{ type?: 'warn' }>()
defineProps<{ type?: 'warning' }>()
</script>

Expand All @@ -31,7 +31,11 @@ defineProps<{ type?: 'warn' }>()
background-color: $default;
}
&.warn {
&.success {
background-color: $success;
}
&.warning {
background-color: $warning;
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-webui/client/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ $border2: #ebe7ed;
$border3: #ebeef5;
$border4: #f2f6fc;
$default: #2477ff;
$success: #67c23a;
$success: #2d8515;
$error: #F56C6C;
$warning: #e49400;

Expand Down
3 changes: 2 additions & 1 deletion packages/plugin-webui/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export namespace storage {

interface Config {
authType: 0 | 1
pluginTab: 0 | 1
username?: string
password?: string
platform?: string
Expand All @@ -71,7 +72,7 @@ interface Config {
}

export const user = storage.create<User>('user')
export const config = storage.create<Config>('config', { authType: 0 }, true)
export const config = storage.create<Config>('config', { authType: 0, pluginTab: 0 }, true)
export const meta = ref<Meta.Payload>(null)
export const profile = ref<Profile.Payload>(null)
export const registry = ref<Registry.Payload>(null)
Expand Down
33 changes: 1 addition & 32 deletions packages/plugin-webui/client/views/bots.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<th>当前消息频率</th>
<th>近期消息频率</th>
</tr>
<tr v-for="({ platform, username, selfId, code, currentRate }, index) in profile.bots" :key="index">
<tr v-for="{ platform, username, selfId, code, currentRate } in profile.bots">
<td>{{ platform }}</td>
<td>{{ username }}</td>
<td>{{ codes[code] }}</td>
Expand All @@ -27,34 +27,3 @@ import { stats, profile } from '~/client'
const codes = ['运行中', '闲置', '离线', '网络异常', '服务器异常', '封禁中', '尝试连接']
</script>

<style lang="scss">
@import '~/variables';
.bot-table {
table {
text-align: center;
width: 100%;
border-collapse: collapse;
}
tr {
transition: 0.3s ease;
}
tr:hover {
background-color: #474d8450;
}
td, th {
padding: .5em 1em;
border-bottom: 1px solid $borderColor;
}
th {
border-top: 1px solid $borderColor;
}
}
</style>
23 changes: 23 additions & 0 deletions packages/plugin-webui/client/views/layout/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,27 @@ main.frameless {
}
}
table {
text-align: center;
width: 100%;
border-collapse: collapse;
}
tr {
transition: 0.3s ease;
}
tr:hover {
background-color: #474d8450;
}
td, th {
padding: .5em 1em;
border-bottom: 1px solid $borderColor;
}
th {
border-top: 1px solid $borderColor;
}
</style>
2 changes: 1 addition & 1 deletion packages/plugin-webui/client/views/plugins/plugin-view.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<i class="fas fa-angle-right" :class="{ show }"/>
{{ data.name }}
<k-badge type="default" v-if="data.webExtension" title="拥有网页扩展的插件停用和启用后将刷新页面。">网页扩展</k-badge>
<k-badge type="warn" v-if="data.sideEffect" title="拥有副作用的插件无法被停用。">副作用</k-badge>
<k-badge type="warning" v-if="data.sideEffect" title="拥有副作用的插件无法被停用。">副作用</k-badge>
</span>
<span class="complexity">{{ data.complexity || '-' }}</span>
<span class="operation">
Expand Down
80 changes: 69 additions & 11 deletions packages/plugin-webui/client/views/plugins/plugins.vue
Original file line number Diff line number Diff line change
@@ -1,28 +1,73 @@
<template>
<k-card class="page-plugins" :class="{ authorized: user?.authority >= 4 }" title="插件列表">
<div class="header plugin-item">
<span class="title">插件名</span>
<span class="complexity">复杂度</span>
<span class="operation">操作</span>
</div>
<div class="plugin-list root">
<plugin-view :data="data" v-for="(data, index) in registry.plugins" :key="index"/>
</div>
<k-card class="page-plugins" :class="{ authorized: user?.authority >= 4 }">
<template #header>
<span :class="{ inactive: config.pluginTab === 1 }" @click="config.pluginTab = 0">插件列表</span>
/
<span :class="{ inactive: config.pluginTab === 0 }" @click="config.pluginTab = 1">依赖管理</span>
</template>
<template v-if="config.pluginTab === 0">
<div class="table-header plugin-item">
<span class="title">插件名</span>
<span class="complexity">复杂度</span>
<span class="operation">操作</span>
</div>
<div class="plugin-list root">
<plugin-view :data="data" v-for="(data) in registry.plugins"/>
</div>
</template>
<table v-else-if="registry.packages">
<tr>
<th>模块名称</th>
<th>当前版本</th>
<th>最新版本</th>
</tr>
<tr v-for="{ name, version, latest, isLocal } in registry.packages">
<td class="package-name">
<a :href="'http://npmjs.com/package/' + name" target="blank" rel="noopener noreferrer">{{ name }}</a>
<k-badge type="default" v-if="isLocal">本地</k-badge>
<k-badge type="danger" v-else-if="!latest">未知</k-badge>
<k-badge type="success" v-else-if="latest === version">最新</k-badge>
<k-badge type="warning" v-else>可更新</k-badge>
</td>
<td>{{ version }}</td>
<td>{{ isLocal ? '-' : latest || '无法获取' }}</td>
</tr>
</table>
<p v-else>暂无数据。</p>
</k-card>
</template>
<script setup lang="ts">
import PluginView from './plugin-view.vue'
import { registry, user } from '~/client'
import { registry, user, config } from '~/client'
</script>
<style lang="scss">
@import '~/variables';
.header {
.page-plugins {
header {
color: rgba(244, 244, 245, .6);
span {
transition: 0.3s ease;
}
span.inactive:hover {
cursor: pointer;
color: rgba(244, 244, 245, .8);
}
span:not(.inactive) {
color: rgba(244, 244, 245);
}
}
}
.table-header {
font-weight: bold;
border-top: $borderColor 1px solid;
Expand All @@ -37,4 +82,17 @@ import { registry, user } from '~/client'
}
}
.package-name {
text-align: left;
a {
font-weight: bold;
transition: 0.3s ease;
color: rgba(244, 244, 245, 0.6);
}
a:hover {
color: rgba(244, 244, 245);
}
}
</style>
96 changes: 4 additions & 92 deletions packages/plugin-webui/src/data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Argv, Assets, Bot, Context, noop, Platform, Plugin, Time } from 'koishi-core'
import { Argv, Assets, Bot, Context, noop, Platform, Time } from 'koishi-core'
import { cpus } from 'os'
import { mem } from 'systeminformation'

Expand Down Expand Up @@ -138,7 +138,7 @@ export namespace Profile {

export class Meta implements DataSource<Meta.Payload> {
timestamp = 0
cachedMeta: Promise<Meta.Payload>
cached: Promise<Meta.Payload>
callbacks: Meta.Extension[] = []

constructor(private ctx: Context, public config: Meta.Config) {
Expand All @@ -152,9 +152,9 @@ export class Meta implements DataSource<Meta.Payload> {

async get(): Promise<Meta.Payload> {
const now = Date.now()
if (this.timestamp > now) return this.cachedMeta
if (this.timestamp > now) return this.cached
this.timestamp = now + Time.hour
return this.cachedMeta = Promise
return this.cached = Promise
.all(this.callbacks.map(cb => cb().catch(noop)))
.then(data => Object.assign({}, ...data))
}
Expand All @@ -181,91 +181,3 @@ export namespace Meta {

export type Extension = () => Promise<Partial<Payload>>
}

function debounce(callback: Function, ms: number) {
let timer: number
return function () {
if (timer) clearTimeout(timer)
timer = setTimeout(callback, ms)
}
}

export class Registry implements DataSource<Registry.Payload> {
payload: Registry.Payload
promise: Promise<void>

static readonly placeholder = Symbol('webui.registry.placeholder')
static readonly webExtension = Symbol('webui.registry.web-extension')

constructor(private ctx: Context, public config: Registry.Config) {
ctx.on('plugin-added', this.update)
ctx.on('plugin-removed', this.update)
}

get registry() {
return this.ctx.app.registry
}

update = debounce(async () => {
this.ctx.webui.broadcast('registry', await this.get(true))
}, 0)

async get(forced = false) {
if (this.payload && !forced) return this.payload
this.payload = { pluginCount: 0 } as Registry.Payload
this.payload.plugins = this.traverse(null).children
return this.payload
}

async switch(id: string) {
await this.promise
for (const [plugin, state] of this.registry) {
if (id !== state.id) continue
const replacer = plugin[Registry.placeholder] || {
[Registry.placeholder]: state.plugin,
name: state.name,
apply: () => {},
}
this.promise = this.ctx.dispose(plugin)
state.context.plugin(replacer, state.config)
}
}

traverse = (plugin: Plugin): Registry.PluginData => {
const state = this.registry.get(plugin)
this.payload.pluginCount += 1
let webExtension = state[Registry.webExtension]
let complexity = plugin?.[Registry.placeholder] ? 0 : 1 + state.disposables.length
const children: Registry.PluginData[] = []
state.children.forEach((plugin) => {
const data = this.traverse(plugin)
complexity += data.complexity
webExtension ||= data.webExtension
if (data.name) {
children.push(data)
} else {
children.push(...data.children)
}
})
const { id, name, sideEffect } = state
children.sort((a, b) => a.name > b.name ? 1 : -1)
return { id, name, sideEffect, children, complexity, webExtension }
}
}

export namespace Registry {
export interface Config {
}

export interface PluginData extends Plugin.Meta {
id: string
children: PluginData[]
complexity: number
webExtension: boolean
}

export interface Payload {
plugins: PluginData[]
pluginCount: number
}
}

0 comments on commit 2827a5c

Please sign in to comment.