upload: dashboard from Soulter/AstrBot-Dashboard

This commit is contained in:
Soulter
2024-12-17 23:40:32 +08:00
parent 63c604359b
commit 6df0e78b22
74 changed files with 3325 additions and 1 deletions
@@ -0,0 +1,126 @@
<template>
<h3 style="margin-bottom: 8px;" v-if="iterable && metadata[metadataKey]?.type === 'object'">
{{metadata[metadataKey]?.description }}
</h3>
<v-card-text>
<div v-for="(index, key) in iterable" :key="key" style="margin-bottom: 0.5px;" v-if="metadata[metadataKey]?.type === 'object' || metadata[metadataKey]?.config_template">
<v-alert v-if="metadata[metadataKey].items[key]?.obvious_hint && metadata[metadataKey].items[key]?.hint && metadata[metadataKey].items[key]?.type !== 'object'" style="margin-bottom: 16px"
:text="metadata[metadataKey].items[key]?.hint" :title="'💡 关于' + metadata[metadataKey].items[key]?.description"
type="info" variant="tonal">
</v-alert>
<div style="display: flex; align-items: center; justify-content: center; gap: 16px">
<div style="width: 100%;">
<v-select v-if="metadata[metadataKey].items[key]?.options && !metadata[metadataKey].items[key]?.invisible" v-model="iterable[key]"
variant="outlined" :items="metadata[metadataKey].items[key]?.options"
:label="metadata[metadataKey].items[key]?.description + '(' + key + ')'" dense :disabled="metadata[metadataKey].items[key]?.readonly"></v-select>
<v-text-field v-else-if="metadata[metadataKey].items[key]?.type === 'string' && !metadata[metadataKey].items[key]?.invisible"
v-model="iterable[key]" :label="metadata[metadataKey].items[key]?.description + '(' + key + ')'"
variant="outlined" dense ></v-text-field>
<v-text-field
v-else-if="(metadata[metadataKey].items[key]?.type === 'int' || metadata[metadataKey].items[key]?.type === 'float') && !metadata[metadataKey].items[key]?.invisible"
v-model="iterable[key]" :label="metadata[metadataKey].items[key]?.description + '(' + key + ')'"
variant="outlined" dense></v-text-field>
<v-textarea v-else-if="metadata[metadataKey].items[key]?.type === 'text' && !metadata[metadataKey].items[key]?.invisible" v-model="iterable[key]"
:label="metadata[metadataKey].items[key]?.description + '(' + key + ')'" variant="outlined"
dense></v-textarea>
<v-switch v-else-if="metadata[metadataKey].items[key]?.type === 'bool' && !metadata[metadataKey].items[key]?.invisible" v-model="iterable[key]"
:label="metadata[metadataKey].items[key]?.description + '(' + key + ')'" color="primary"
inset></v-switch>
<v-combobox variant="outlined" v-else-if="metadata[metadataKey].items[key]?.type === 'list' && !metadata[metadataKey].items[key]?.invisible"
v-model="iterable[key]" chips clearable
:label="metadata[metadataKey].items[key]?.description + '(' + key + ')'" multiple
prepend-icon="mdi-tag-multiple-outline">
<template v-slot:selection="{ attrs, item, select, selected }">
<v-chip v-bind="attrs" :model-value="selected" closable @click="select"
@click:close="remove(item)">
<strong>{{ item }}</strong>
</v-chip>
</template>
</v-combobox>
<div v-else-if="metadata[metadataKey].items[key]?.type === 'object' && !metadata[metadataKey].items[key]?.invisible"
style="border: 1px solid #e0e0e0; padding: 8px; margin-bottom: 16px; border-radius: 10px;">
<AstrBotConfig :metadata="metadata[metadataKey].items" :iterable="iterable[key]"
:metadataKey=key>
</AstrBotConfig>
</div>
</div>
<div
v-if="!metadata[metadataKey].items[key]?.obvious_hint && metadata[metadataKey].items[key]?.hint && metadata[metadataKey].items[key]?.type !== 'object' && !metadata[metadataKey].items[key]?.invisible">
<v-btn icon size="x-small" style="margin-bottom: 22px;">
<v-icon size="x-small">mdi-help</v-icon>
<v-tooltip activator="parent" location="start">{{ metadata[metadataKey].items[key]?.hint
}}</v-tooltip>
</v-btn>
</div>
</div>
</div>
<div v-else>
<v-alert v-if="metadata[metadataKey]?.obvious_hint && metadata[metadataKey]?.hint && metadata[metadataKey]?.type !== 'object'" style="margin-bottom: 16px"
:text="metadata[metadataKey]?.hint" :title="'💡 关于' + metadata[metadataKey]?.description"
type="info" variant="tonal">
</v-alert>
<div style="display: flex; align-items: center; justify-content: center; gap: 16px">
<div style="width: 100%;">
<v-select v-if="metadata[metadataKey]?.options && !metadata[metadataKey]?.invisible" v-model="iterable[metadataKey]"
variant="outlined" :items="metadata[metadataKey]?.options"
:label="metadata[metadataKey]?.description + '(' + metadataKey+ ')'" dense :disabled="metadata[metadataKey]?.readonly"></v-select>
<v-text-field v-else-if="metadata[metadataKey]?.type === 'string' && !metadata[metadataKey]?.invisible"
v-model="iterable[metadataKey]" :label="metadata[metadataKey]?.description + '(' + metadataKey+ ')'"
variant="outlined" dense ></v-text-field>
<v-text-field
v-else-if="(metadata[metadataKey]?.type === 'int' || metadata[metadataKey]?.type === 'float') && !metadata[metadataKey]?.invisible"
v-model="iterable[metadataKey]" :label="metadata[metadataKey]?.description + '(' + metadataKey+ ')'"
variant="outlined" dense></v-text-field>
<v-textarea v-else-if="metadata[metadataKey]?.type === 'text' && !metadata[metadataKey]?.invisible" v-model="iterable[metadataKey]"
:label="metadata[metadataKey]?.description + '(' + metadataKey+ ')'" variant="outlined"
dense></v-textarea>
<v-switch v-else-if="metadata[metadataKey]?.type === 'bool' && !metadata[metadataKey]?.invisible" v-model="iterable[metadataKey]"
:label="metadata[metadataKey]?.description + '(' + metadataKey+ ')'" color="primary"
inset></v-switch>
<v-combobox variant="outlined" v-else-if="metadata[metadataKey]?.type === 'list' && !metadata[metadataKey]?.invisible"
v-model="iterable[metadataKey]" chips clearable
:label="metadata[metadataKey]?.description + '(' + metadataKey+ ')'" multiple
prepend-icon="mdi-tag-multiple-outline">
<template v-slot:selection="{ attrs, item, select, selected }">
<v-chip v-bind="attrs" :model-value="selected" closable @click="select"
@click:close="remove(item)">
<strong>{{ item }}</strong>
</v-chip>
</template>
</v-combobox>
<div v-else-if="metadata[metadataKey]?.type === 'object' && !metadata[metadataKey]?.invisible"
style="border: 1px solid #e0e0e0; padding: 8px; margin-bottom: 16px; border-radius: 10px;">
<AstrBotConfig :metadata="metadata[metadataKey].items" :iterable="iterable[metadataKey]"
:metadataKey=key>
</AstrBotConfig>
</div>
</div>
<div
v-if="!metadata[metadataKey]?.obvious_hint && metadata[metadataKey]?.hint && metadata[metadataKey]?.type !== 'object' && !metadata[metadataKey]?.invisible">
<v-btn icon size="x-small" style="margin-bottom: 22px;">
<v-icon size="x-small">mdi-help</v-icon>
<v-tooltip activator="parent" location="start">{{ metadata[metadataKey]?.hint
}}</v-tooltip>
</v-btn>
</div>
</div>
</div>
</v-card-text>
</template>
<script>
import { readonly } from 'vue';
export default {
props: {
metadata: Object,
iterable: Object,
metadataKey: String
}
}
</script>
@@ -0,0 +1,41 @@
<script setup>
import UiParentCard from '@/components/shared/UiParentCard.vue';
const props = defineProps({
config: Array
});
</script>
<template>
<a v-show="config.length === 0">该插件没有配置</a>
<UiParentCard v-for="group in config" :key="group.name" :title="group.name" style="margin-bottom: 16px;">
<template v-for="item in group.body">
<template v-if="item.config_type === 'item'">
<template v-if="item.val_type === 'bool'">
<v-switch v-model="item.value" :label="item.name" :hint="item.description" color="primary" inset></v-switch>
</template>
<template v-else-if="item.val_type === 'str'">
<v-text-field v-model="item.value" :label="item.name" :hint="item.description" style="margin-bottom: 8px;"
variant="outlined"></v-text-field>
</template>
<template v-else-if="item.val_type === 'int'">
<v-text-field v-model="item.value" :label="item.name" :hint="item.description" style="margin-bottom: 8px;"
variant="outlined"></v-text-field>
</template>
<template v-else-if="item.val_type === 'list'">
<span>{{ item.name }}</span>
<v-combobox v-model="item.value" chips clearable label="请添加" multiple prepend-icon="mdi-tag-multiple-outline">
<template v-slot:selection="{ attrs, item, select, selected }">
<v-chip v-bind="attrs" :model-value="selected" closable @click="select" @click:close="remove(item)">
<strong>{{ item }}</strong>
</v-chip>
</template>
</v-combobox>
</template>
</template>
<template v-else-if="item.config_type === 'divider'">
<v-divider style="margin-top: 8px; margin-bottom: 8px;"></v-divider>
</template>
</template>
</UiParentCard>
</template>
@@ -0,0 +1,77 @@
<script setup>
import { useCommonStore } from '@/stores/common';
</script>
<template>
<div id="term"
style="background-color: #1e1e1e; padding: 16px; border-radius: 8px; overflow-y:scroll">
</div>
</template>
<script>
export default {
name: 'ConsoleDisplayer',
data() {
return {
logColorAnsiMap: {
'\u001b[1;34m': 'color: #0000FF; font-weight: bold;', // bold_blue
'\u001b[1;36m': 'color: #00FFFF; font-weight: bold;', // bold_cyan
'\u001b[1;33m': 'color: #FFFF00; font-weight: bold;', // bold_yellow
'\u001b[31m': 'color: #FF0000;', // red
'\u001b[1;31m': 'color: #FF0000; font-weight: bold;', // bold_red
'\u001b[0m': 'color: inherit; font-weight: normal;', // reset
'\u001b[32m': 'color: #00FF00;', // green
'default': 'color: #FFFFFF;'
},
logCache: useCommonStore().getLogCache(),
historyNum_: -1
}
},
props: {
historyNum: {
type: String,
default: -1
}
},
watch: {
logCache: {
handler(val) {
this.printLog(val[this.logCache.length - 1])
},
deep: true
}
},
mounted() {
this.historyNum_ = parseInt(this.historyNum)
let i = 0
for (let log of this.logCache) {
if (this.historyNum_ != -1 && i >= this.logCache.length - this.historyNum_) {
this.printLog(log)
++i
} else if (this.historyNum_ == -1) {
this.printLog(log)
}
}
},
methods: {
printLog(log) {
// append 一个 span 标签到 termblock 的方式
let ele = document.getElementById('term')
let span = document.createElement('pre')
let style = this.logColorAnsiMap['default']
for (let key in this.logColorAnsiMap) {
if (log.startsWith(key)) {
style = this.logColorAnsiMap[key]
log = log.replace(key, '').replace('\u001b[0m', '')
break
}
}
span.style = style + 'display: block; font-size: 12px; font-family: Consolas, monospace;'
span.classList.add('fade-in')
span.innerText = log
ele.appendChild(span)
ele.scrollTop = ele.scrollHeight
}
},
}
</script>
@@ -0,0 +1,26 @@
<script setup lang="ts">
const props = defineProps({
title: String,
link: String
});
const open = (link: string | undefined) => {
window.open(link, '_blank');
};
</script>
<template>
<v-card variant="outlined" elevation="0" class="withbg">
<v-card-item style="padding: 10px 14px">
<div class="d-sm-flex align-center justify-space-between">
<v-card-title style="font-size: 17px;">{{ props.title }}</v-card-title>
<v-spacer></v-spacer>
<v-btn variant="plain" @click="open(props.link)">仓库</v-btn>
</div>
</v-card-item>
<v-divider></v-divider>
<v-card-text>
<slot />
</v-card-text>
</v-card>
</template>
@@ -0,0 +1,20 @@
<script setup lang="ts">
const props = defineProps({
title: String
});
</script>
<template>
<v-card variant="outlined" elevation="0" class="withbg">
<v-card-item>
<div class="d-sm-flex align-center justify-space-between">
<v-card-title>{{ props.title }}</v-card-title>
<slot name="action"></slot>
</div>
</v-card-item>
<v-divider></v-divider>
<v-card-text>
<slot />
</v-card-text>
</v-card>
</template>
@@ -0,0 +1,86 @@
<template>
<v-dialog v-model="visible" persistent max-width="400">
<v-card>
<v-card-title>正在等待 AstrBot 重启...</v-card-title>
<v-card-text>
<v-progress-linear indeterminate color="primary"></v-progress-linear>
<div style="margin-top: 16px;">
<div class="py-12 text-center" v-if="newStartTime != -1">
<v-icon class="mb-6" color="success" icon="mdi-check-circle-outline" size="128"></v-icon>
<p>重启成功</p>
</div>
<small v-if="startTime != -1" style="display: block;">当前实例标识{{ startTime }}</small>
<small v-if="newStartTime != -1" style="display: block;">检查到新实例{{ newStartTime }}即将自动刷新页面</small>
<small v-if="status" style="display: block;">{{ status }}</small>
<small style="display: block;">尝试次数{{ cnt }} / 60</small>
</div>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script>
import axios from 'axios'
import { useCommonStore } from '@/stores/common';
export default {
name: 'WaitingForRestart',
data() {
return {
visible: false,
startTime: -1,
newStartTime: -1,
status: '',
cnt: 0,
}
},
methods: {
async check() {
this.newStartTime = -1
this.startTime = useCommonStore().getStartTime()
this.visible = true
this.status = ""
console.log('start wfr')
setTimeout(() => {
this.timeoutInternal()
}, 1000)
},
timeoutInternal() {
console.log('wfr: timeoutInternal', this.newStartTime, this.startTime)
if (this.newStartTime === -1 && this.cnt < 60 && this.visible) {
this.checkStartTime()
this.cnt++
setTimeout(() => {
this.timeoutInternal()
}, 1000)
} else {
if (this.cnt == 10) {
this.status = '拉取状态达到最大次数,请手动检查。'
}
this.cnt = 0
setTimeout(() => {
this.visible = false
}, 1000)
}
},
async checkStartTime() {
let res = await axios.get('/api/stat/start-time', { timeout: 3000 })
let newStartTime = res.data.data.start_time
console.log('wfr: checkStartTime', this.newStartTime, this.startTime)
if (this.newStartTime !== this.startTime) {
this.newStartTime = newStartTime
console.log('wfr: restarted')
setTimeout(() => {
this.visible = false
// reload
window.location.reload()
}, 2000)
}
return this.newStartTime
}
}
}
</script>