Skip to content

Commit

Permalink
feat(status): support username-password login
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Mar 17, 2021
1 parent 064810c commit bbfac8a
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 55 deletions.
8 changes: 7 additions & 1 deletion packages/plugin-status/client/components/card.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ $paddingX: 1.2rem;
$paddingY: 1.6rem;
.k-card {
margin: 0 auto;
margin: 0 auto 2rem;
width: 100%;
border-radius: 8px;
background: rgba(0, 0, 0, .24);
Expand All @@ -39,6 +39,12 @@ $paddingY: 1.6rem;
.k-card-body {
padding: $paddingX $paddingY;
> :first-child {
margin-top: 0;
}
> :last-child {
margin-bottom: 0;
}
}
&.frameless .k-card-body {
Expand Down
14 changes: 7 additions & 7 deletions packages/plugin-status/client/components/input.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div class="k-input" :class="{ focused, disabled }">
<i v-if="prefixIcon" :class="'fas fa-' + prefixIcon" class="prefix"/>
<i v-if="prefix" :class="'fas fa-' + prefix" class="prefix" @click="$emit('clickPrefix')"/>
<input
ref="input"
:value="modelValue"
Expand All @@ -21,7 +21,7 @@
@blur="onBlur"
@keydown.enter.stop="$emit('enter', $event)"
/>
<i v-if="suffixIcon" :class="'fas fa-' + suffixIcon" class="suffix"/>
<i v-if="suffix" :class="'fas fa-' + suffix" class="suffix" @click="$emit('clickSuffix')"/>
</div>
</template>

Expand All @@ -30,8 +30,8 @@
import { defineProps, ref, computed, defineEmit } from 'vue'
const props = defineProps({
prefixIcon: String,
suffixIcon: String,
prefix: String,
suffix: String,
placeholder: String,
disabled: Boolean,
validate: Function,
Expand All @@ -50,11 +50,11 @@ const invalid = ref(false)
const inputStyle = computed(() => ({
fontSize: props.size + 'em',
paddingLeft: +!!(props.prefixIcon) + 1 + 'em',
paddingRight: +!!(props.suffixIcon) + 1 + 'em',
paddingLeft: +!!(props.prefix) + 1 + 'em',
paddingRight: +!!(props.suffix) + 1 + 'em',
}))
const emit = defineEmit(['update:modelValue', 'focus', 'blur', 'enter'])
const emit = defineEmit(['update:modelValue', 'focus', 'blur', 'enter', 'clickPrefix', 'clickSuffix'])
function onInput (event) {
if (props.validate) {
Expand Down
32 changes: 30 additions & 2 deletions packages/plugin-status/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-undef */

import { ref } from 'vue'
import { ref, watch } from 'vue'
import type { User } from 'koishi-core'
import type { Payload } from '~/server'

Expand All @@ -20,9 +20,26 @@ export namespace storage {
if (typeof localStorage === 'undefined') return
localStorage.setItem(prefix + key, JSON.stringify(value))
}

export function create<T>(key: string, fallback?: T) {
const wrapper = ref<T>({ ...fallback, ...get(key) })
watch(wrapper, () => set(key, wrapper.value), {
deep: typeof fallback === 'object',
})
return wrapper
}
}

interface Config {
authType?: 0 | 1
username?: string
password?: string
platform?: string
userId?: string
}

export const user = ref<User>(storage.get('user'))
export const user = storage.create<User>('user')
export const config = storage.create<Config>('config', { authType: 0 })
export const status = ref<Payload>(null)
export const socket = ref<WebSocket>(null)

Expand Down Expand Up @@ -51,3 +68,14 @@ export function send(type: string, body: any) {
export function receive<T = any>(event: string, listener: (data: T) => void) {
listeners[event] = listener
}

export async function sha256(password: string) {
const data = new TextEncoder().encode(password)
const buffer = await crypto.subtle.digest('SHA-256', data)
const view = new DataView(buffer)
let output = ''
for (let i = 0; i < view.byteLength; i += 4) {
output += ('00000000' + view.getUint32(i).toString(16)).slice(-8)
}
return output
}
13 changes: 7 additions & 6 deletions packages/plugin-status/client/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ import 'element-plus/lib/theme-chalk/el-button.css'

import '@fortawesome/fontawesome-free/css/fontawesome.css'
import '@fortawesome/fontawesome-free/css/brands.css'
import '@fortawesome/fontawesome-free/css/regular.css'
import '@fortawesome/fontawesome-free/css/solid.css'

import './index.scss'

declare module 'vue-router' {
interface RouteMeta {
icon?: string
status?: boolean
auth?: boolean
hidden?: boolean
authorize?: boolean
frameless?: boolean
}
}
Expand All @@ -50,17 +51,17 @@ const router = createRouter({
}, {
path: '/sandbox',
name: '沙盒',
meta: { icon: 'laptop-code', auth: true },
meta: { icon: 'laptop-code', authorize: true },
component: () => import('./views/sandbox.vue'),
}, {
path: '/profile',
name: '资料',
meta: { icon: 'user-circle', auth: true },
meta: { icon: 'user-circle', authorize: true, hidden: true },
component: () => import('./views/profile.vue'),
}, {
path: '/login',
name: '登录',
meta: { icon: 'sign-in-alt', frameless: true },
meta: { icon: 'sign-in-alt', frameless: true, hidden: true },
component: () => import('./views/login.vue'),
}],
})
Expand All @@ -77,7 +78,7 @@ app.use(ElCollapseTransition)
app.use(router)

router.beforeEach((route) => {
if (route.meta.auth && !user.value) {
if (route.meta.authorize && !user.value) {
return '/login'
}
})
Expand Down
8 changes: 6 additions & 2 deletions packages/plugin-status/client/views/home/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="basic-stats">
<div class="stats-grid basic-stats">
<card-numeric title="当前消息频率" icon="paper-plane">{{ upRate }} / min</card-numeric>
<card-numeric title="近期消息频率" icon="history">{{ recentRate }} / d</card-numeric>
<card-numeric title="命名插件数量" icon="plug">{{ status.pluginCount }}</card-numeric>
Expand All @@ -8,7 +8,7 @@
<card-numeric title="活跃群数量" icon="users">32</card-numeric>
</div>
<load-chart :status="status"/>
<div class="chart-stats">
<div class="stats-grid chart-stats">
<history-chart :status="status"/>
<hour-chart :status="status"/>
<group-chart :status="status"/>
Expand Down Expand Up @@ -39,6 +39,10 @@ const recentRate = computed(() => {

<style lang="scss">
.stats-grid .k-card {
margin: 0;
}
.basic-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
Expand Down
1 change: 0 additions & 1 deletion packages/plugin-status/client/views/layout/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ const frameless = computed(() => route.meta.frameless)
body {
margin: 0;
font-size: 14px;
min-height: 100vh;
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
Expand Down
31 changes: 29 additions & 2 deletions packages/plugin-status/client/views/layout/navbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,28 @@
<nav>
<span class="title">Koishi 控制台</span>
<span class="right">
<router-link v-if="user" to="/profile">{{ user.name }}</router-link>
<router-link v-else to="/login">登录</router-link>
<template v-if="user">
<router-link to="/profile">{{ user.name }}<i class="fas fa-user-circle"/></router-link>
<el-button type="text" @click="logout">登出<i class="fas fa-sign-in-alt"/></el-button>
</template>
<template v-else>
<router-link to="/login">登录<i class="fas fa-sign-in-alt"/></router-link>
</template>
</span>
</nav>
</template>

<script lang="ts" setup>
import { user } from '~/client'
import { useRouter } from 'vue-router'
const router = useRouter()
async function logout() {
await router.push('/login')
user.value = null
}
</script>

Expand All @@ -30,6 +43,20 @@ nav {
.right {
font-size: 1.05rem;
float: right;
i {
margin-left: 0.4rem;
}
> * + * {
margin-left: 1rem;
}
}
.el-button {
font-size: 1.05rem !important;
min-height: auto !important;
padding: 0 !important;
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-status/client/views/layout/sidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<aside>
<ul>
<template v-for="(route, index) in $router.getRoutes()" :key="index">
<li v-if="!route.meta.frameless" :class="{ current: route.name === $route.name }">
<li v-if="!route.meta.hidden" :class="{ current: route.name === $route.name }">
<router-link :to="route.path">
<i :class="['fas', `fa-${route.meta.icon}`]"/>
<i :class="`fas fa-${route.meta.icon}`"/>
{{ route.name }}
</router-link>
</li>
Expand Down
56 changes: 33 additions & 23 deletions packages/plugin-status/client/views/login.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
<template>
<k-card class="login">
<h1>
<span :class="{ inactive: type === 1 }" @click="type = 0">平台账户登录</span>
<span :class="{ inactive: config.authType === 1 }" @click="config.authType = 0">平台账户登录</span>
/
<span :class="{ inactive: type === 0 }" @click="type = 1">用户名密码登录</span>
<span :class="{ inactive: config.authType === 0 }" @click="config.authType = 1">用户名密码登录</span>
</h1>
<template v-if="data.token">
<p class="hint">欢迎你,{{ data.name || 'Koishi 用户' }}!</p>
<p class="hint">请用上述账号将下面的验证码私聊发送给任意机器人:</p>
<p class="token">{{ data.token }}</p>
</template>
<template v-else-if="config.authType === 0">
<k-input prefix="at" placeholder="平台名" v-model="config.platform"/>
<k-input prefix="user" placeholder="账号" v-model="config.userId" @enter="enter"/>
<p class="error" v-if="data.message">{{ data.message }}</p>
<div class="control">
<k-button @click="$router.back()">返回</k-button>
<k-button @click="enter">获取验证码</k-button>
</div>
</template>
<template v-else>
<k-input :prefix-icon="presets[type][0]" :placeholder="presets[type][1]" v-model="form1"/>
<k-input :prefix-icon="presets[type][2]" :placeholder="presets[type][3]" v-model="form2" @enter="enter"/>
<k-input prefix="user" placeholder="用户名" v-model="config.username"/>
<k-input prefix="lock" placeholder="密码" v-model="config.password" type="password" @enter="enter"/>
<p class="error" v-if="data.message">{{ data.message }}</p>
<div class="control">
<k-button @click="$router.back()">返回</k-button>
<k-button @click="enter">{{ presets[type][4] }}</k-button>
<k-button @click="login">登录</k-button>
</div>
</template>
</k-card>
Expand All @@ -26,41 +35,38 @@
import { ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { send, receive, user } from '~/client'
const presets = [
['at', '平台名', 'user', '账号', '获取验证码'],
['user', '用户名', 'lock', '密码', '登录'],
]
import { send, receive, user, config, sha256 } from '~/client'
interface TokenData {
interface LoginData {
token?: string
name?: string
message?: string
}
const type = ref(0)
const data = ref<TokenData>({})
const form1 = ref('')
const form2 = ref('')
const data = ref<LoginData>({})
const router = useRouter()
receive('token', body => data.value = body)
receive('login', body => data.value = body)
watch(user, (value) => {
if (!value) return
router.push('/profile')
})
const timestamp = ref(0)
let timestamp = 0
async function enter() {
const now = Date.now()
if (now < timestamp.value) return
if (!form1.value || !form2.value) return
timestamp.value = now + 10000
send('token', { platform: form1.value, userId: form2.value })
if (now < timestamp) return
const { platform, userId } = config.value
if (!platform || !userId) return
timestamp = now + 1000
send('token', { platform, userId })
}
async function login() {
const { username, password } = config.value
send('login', { username, password: await sha256(password) })
}
</script>
Expand All @@ -75,6 +81,10 @@ section.login {
flex-direction: column;
justify-content: center;
.k-card-body {
padding: 3rem 0 !important;
}
h1 {
font-size: 1.5rem;
margin: 2.5rem auto;
Expand Down
Loading

0 comments on commit bbfac8a

Please sign in to comment.