feat: add admin query emails (#39)

* feat: add NPagination

* feat: admin query emails
This commit is contained in:
Dream Hunter
2023-12-12 16:33:54 +08:00
committed by GitHub
parent 795f256bde
commit bf6374af52
5 changed files with 290 additions and 32 deletions

View File

@@ -14,6 +14,7 @@ const apiFetch = async (path, options = {}) => {
try {
const response = await instance.request(path, {
method: options.method || 'GET',
data: options.body || null,
headers: {
'x-custom-auth': auth.value,
'x-admin-auth': adminAuth.value,
@@ -88,20 +89,10 @@ const adminDeleteAddress = async (id) => {
}
}
const adminGetAddress = async () => {
try {
return await apiFetch("/admin/addresss");
} catch (error) {
throw error;
}
}
export const api = {
fetch: apiFetch,
getSettings: getSettings,
getOpenSettings: getOpenSettings,
adminShowPassword: adminShowPassword,
adminDeleteAddress: adminDeleteAddress,
adminGetAddress: adminGetAddress,
}

View File

@@ -1,8 +1,9 @@
<script setup>
import { NSpace, NLayoutHeader, NInput, c } from 'naive-ui'
import { NButton, NSelect, NModal } from 'naive-ui'
import { NSpace, NLayoutHeader, NInput, NPagination } from 'naive-ui'
import { NButton, NModal, NTabs, NTabPane, NInputGroup } from 'naive-ui'
import { NList, NListItem, NThing, NTag } from 'naive-ui'
import { NDataTable, NPopconfirm } from 'naive-ui'
import { ref, h, onMounted } from 'vue';
import { ref, h, onMounted, watch } from 'vue';
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useMessage } from 'naive-ui'
@@ -41,6 +42,7 @@ const { t } = useI18n({
delete: 'Delete',
deleteTip: 'Are you sure to delete this email?',
refresh: 'Refresh',
emails: 'Emails',
},
zh: {
title: '临时邮件 Admin',
@@ -55,10 +57,14 @@ const { t } = useI18n({
delete: '删除',
deleteTip: '确定要删除这个邮箱吗?',
refresh: '刷新',
emails: '邮件',
}
}
});
const data = ref([])
const count = ref(0)
const page = ref(1)
const pageSize = ref(20)
const showPassword = async (id) => {
try {
@@ -83,7 +89,15 @@ const deleteEmail = async (id) => {
const fetchData = async () => {
try {
data.value = await api.adminGetAddress()
const { results, count: addressCount } = await api.fetch(
`/admin/address`
+ `?limit=${pageSize.value}`
+ `&offset=${(page.value - 1) * pageSize.value}`
);
data.value = results;
if (addressCount > 0) {
count.value = addressCount;
}
} catch (error) {
console.log(error)
message.error(error.message || "error");
@@ -115,6 +129,16 @@ const columns = [
},
{ default: () => t('showPass') }
),
h(NButton,
{
type: 'success',
onClick: () => {
mailAddress.value = row.name
tab.value = "mails"
}
},
{ default: () => t('emails') }
),
h(NPopconfirm,
{
onPositiveClick: () => deleteEmail(row.id)
@@ -129,6 +153,10 @@ const columns = [
}
]
watch([page, pageSize], async () => {
await fetchData()
})
onMounted(async () => {
if (!adminAuth.value) {
@@ -137,11 +165,69 @@ onMounted(async () => {
await fetchData()
}
})
const tab = ref("account")
const mailAddress = ref("")
const mailData = ref([])
const mailCount = ref(0)
const mailPage = ref(1)
const mailPageSize = ref(20)
watch([mailPage, mailPageSize, mailAddress], async () => {
await fetchMailData()
})
const fetchMailData = async () => {
if (!mailAddress.value) {
return
}
try {
const { results, count } = await api.fetch(
`/admin/mails`
+ `?address=${mailAddress.value}`
+ `&limit=${mailPageSize.value}`
+ `&offset=${(mailPage.value - 1) * mailPageSize.value}`
);
mailData.value = results;
if (count > 0) {
mailCount.value = count;
}
} catch (error) {
console.log(error)
message.error(error.message || "error");
}
}
const mailUnknowData = ref([])
const mailUnknowCount = ref(0)
const mailUnknowPage = ref(1)
const mailUnknowPageSize = ref(20)
watch([mailUnknowPage, mailUnknowPageSize], async () => {
await fetchMailUnknowData()
})
const fetchMailUnknowData = async () => {
try {
const { results, count } = await api.fetch(
`/admin/mails_unknow`
+ `?limit=${mailPageSize.value}`
+ `&offset=${(mailPage.value - 1) * mailPageSize.value}`
);
mailUnknowData.value = results;
if (count > 0) {
mailUnknowCount.value = count;
}
} catch (error) {
console.log(error)
message.error(error.message || "error");
}
}
</script>
<template>
<n-space vertical>
<n-layout-header>
<div>
<h2>{{ t('title') }}</h2>
@@ -150,9 +236,6 @@ onMounted(async () => {
<n-button tertiary @click="() => router.push('/')" type="primary">
{{ t('home') }}
</n-button>
<n-button tertiary @click="fetchData" type="primary">
{{ t('refresh') }}
</n-button>
</div>
<n-modal v-model:show="showAdminAuth" :closable="false" :closeOnEsc="false" :maskClosable="false" preset="dialog"
title="Dialog">
@@ -183,7 +266,72 @@ onMounted(async () => {
</template>
</n-modal>
</n-layout-header>
<n-data-table :columns="columns" :data="data" :bordered="false" />
<n-tabs type="segment" v-model:value="tab">
<n-tab-pane name="account" tab="account">
<div style="display: inline-block;">
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" :page-sizes="[20, 50, 100]"
show-size-picker />
</div>
<n-button tertiary @click="fetchData" type="primary">
{{ t('refresh') }}
</n-button>
<n-data-table :columns="columns" :data="data" :bordered="false" />
</n-tab-pane>
<n-tab-pane name="mails" tab="mails">
<n-input-group>
<n-input v-model:value="mailAddress" />
<n-button @click="fetchMailData" type="primary" ghost>
{{ t('refresh') }}
</n-button>
</n-input-group>
<n-list hoverable clickable>
<div style="display: inline-block; margin-bottom: 10px;">
<n-pagination v-model:page="mailPage" v-model:page-size="mailPageSize" :item-count="mailCount" simple />
</div>
<n-list-item v-for="row in mailData" v-bind:key="row.id">
<n-thing class="center" :title="row.subject">
<template #description>
<n-space>
<n-tag type="info">
FROM: {{ row.source }}
</n-tag>
<n-tag type="info">
ID: {{ row.id }}
</n-tag>
</n-space>
</template>
<div v-html="row.message"></div>
</n-thing>
</n-list-item>
</n-list>
</n-tab-pane>
<n-tab-pane name="unknow" tab="unknown">
<n-button @click="fetchMailUnknowData" type="primary" ghost>
{{ t('refresh') }}
</n-button>
<n-list hoverable clickable>
<div style="display: inline-block; margin-bottom: 10px;">
<n-pagination v-model:page="mailUnknowPage" v-model:page-size="mailUnknowPageSize"
:item-count="mailUnknowCount" simple />
</div>
<n-list-item v-for="row in mailUnknowData" v-bind:key="row.id">
<n-thing class="center" :title="row.subject">
<template #description>
<n-space>
<n-tag type="info">
FROM: {{ row.source }}
</n-tag>
<n-tag type="info">
ID: {{ row.id }}
</n-tag>
</n-space>
</template>
<div v-html="row.message"></div>
</n-thing>
</n-list-item>
</n-list>
</n-tab-pane>
</n-tabs>
</n-space>
</template>
@@ -193,4 +341,9 @@ onMounted(async () => {
align-items: center;
justify-content: space-between;
}
.n-pagination {
margin-top: 10px;
margin-bottom: 10px;
}
</style>

View File

@@ -1,6 +1,6 @@
<script setup>
import { NSpace, NAlert, NSwitch, NCard, NInput, NInputGroupLabel } from 'naive-ui'
import { NButton, NLayout, NInputGroup, NModal, NSelect } from 'naive-ui'
import { NButton, NLayout, NInputGroup, NModal, NSelect, NPagination } from 'naive-ui'
import { NList, NListItem, NThing, NTag } from 'naive-ui'
import { watch, onMounted, ref } from "vue";
import useClipboard from 'vue-clipboard3'
@@ -22,6 +22,10 @@ const showNewEmail = ref(false)
const emailName = ref("")
const emailDomain = ref("")
const count = ref(0)
const page = ref(1)
const pageSize = ref(20)
const { t } = useI18n({
locale: 'zh',
messages: {
@@ -73,12 +77,26 @@ watch(autoRefresh, async (autoRefresh, old) => {
setupAutoRefresh(autoRefresh)
})
watch([page, pageSize], async ([page, pageSize], [oldPage, oldPageSize]) => {
if (page !== oldPage || pageSize !== oldPageSize) {
await refresh();
}
})
const refresh = async () => {
if (typeof address.value != 'string' || address.value.trim() === '') {
return;
}
try {
data.value = await api.fetch("/api/mails");
const { results, count: totalCount } = await api.fetch(
`/api/mails`
+ `?limit=${pageSize.value}`
+ `&offset=${(page.value - 1) * pageSize.value}`
);
data.value = results;
if (totalCount > 0) {
count.value = totalCount;
}
} catch (error) {
message.error(error.message || "error");
console.error(error);
@@ -150,6 +168,9 @@ onMounted(async () => {
{{ t('refresh') }}
</n-button>
<n-list hoverable clickable>
<div style="display: inline-block; margin-bottom: 10px;">
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" simple />
</div>
<n-list-item v-for="row in data" v-bind:key="row.id">
<n-thing class="center" :title="row.subject">
<template #description>

View File

@@ -4,11 +4,13 @@ import { NButton, NSelect, NModal } from 'naive-ui'
import { NSwitch, NPopconfirm } from 'naive-ui'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useGlobalState } from '../store'
import { api } from '../api'
const { jwt, localeCache, themeSwitch, showAuth, auth } = useGlobalState()
const { jwt, localeCache, themeSwitch, showAuth, adminAuth, auth } = useGlobalState()
const router = useRouter()
const showLogin = ref(false)
const password = ref('')
@@ -75,6 +77,9 @@ const { t } = useI18n({
<div>
<n-button v-if="localeCache == 'zh'" @click="changeLocale('en')">English</n-button>
<n-button v-else @click="changeLocale('zh')">中文</n-button>
<n-button v-if="adminAuth" tertiary @click="() => router.push('/admin')" type="primary">
Admin
</n-button>
<n-switch v-model:value="themeSwitch">
<template #checked>
{{ t('dark') }}

View File

@@ -8,10 +8,27 @@ api.get('/api/mails', async (c) => {
if (!address) {
return c.json({ "error": "No address" }, 400)
}
const { limit, offset } = c.req.query();
if (!limit || limit < 0 || limit > 100) {
return c.text("Invalid limit", 400)
}
if (!offset || offset < 0) {
return c.text("Invalid offset", 400)
}
const { results } = await c.env.DB.prepare(
`SELECT id, source, subject, message FROM mails where address = ? order by id desc limit 100`
).bind(address).all();
return c.json(results);
`SELECT id, source, subject, message FROM mails where address = ? order by id desc limit ? offset ?`
).bind(address, limit, offset).all();
let count = 0;
if (offset == 0) {
const { count: mailCount } = await c.env.DB.prepare(
`SELECT count(*) as count FROM mails where address = ?`
).bind(address).first();
count = mailCount;
}
return c.json({
results: results,
count: count
})
})
api.get('/api/settings', async (c) => {
@@ -66,14 +83,31 @@ api.get('/api/new_address', async (c) => {
})
})
api.get('/admin/addresss', async (c) => {
api.get('/admin/address', async (c) => {
const { limit, offset } = c.req.query();
if (!limit || limit < 0 || limit > 100) {
return c.text("Invalid limit", 400)
}
if (!offset || offset < 0) {
return c.text("Invalid offset", 400)
}
const { results } = await c.env.DB.prepare(
`SELECT * FROM address order by id desc`
).all();
return c.json(results.map((r) => {
r.name = c.env.PREFIX + r.name;
return r;
}));
`SELECT * FROM address order by id desc limit ? offset ?`
).bind(limit, offset).all();
let count = 0;
if (offset == 0) {
const { count: addressCount } = await c.env.DB.prepare(
`SELECT count(*) as count FROM address`
).first();
count = addressCount;
}
return c.json({
results: results.map((r) => {
r.name = c.env.PREFIX + r.name;
return r;
}),
count: count
})
})
api.delete('/admin/delete_address/:id', async (c) => {
@@ -104,4 +138,58 @@ api.get('/admin/show_password/:id', async (c) => {
})
})
api.get('/admin/mails', async (c) => {
const { address, limit, offset } = c.req.query();
if (!limit || limit < 0 || limit > 100) {
return c.text("Invalid limit", 400)
}
if (!offset || offset < 0) {
return c.text("Invalid offset", 400)
}
const { results } = await c.env.DB.prepare(
`SELECT id, source, subject, message FROM mails where address = ? order by id desc limit ? offset ?`
).bind(address, limit, offset).all();
let count = 0;
if (offset == 0) {
const { count: mailCount } = await c.env.DB.prepare(
`SELECT count(*) as count FROM mails where address = ?`
).bind(address).first();
count = mailCount;
}
return c.json({
results: results,
count: count
})
});
api.get('/admin/mails_unknow', async (c) => {
const { limit, offset } = c.req.query();
if (!limit || limit < 0 || limit > 100) {
return c.text("Invalid limit", 400)
}
if (!offset || offset < 0) {
return c.text("Invalid offset", 400)
}
const { results } = await c.env.DB.prepare(`
SELECT id, source, subject, message FROM mails
where address NOT IN (select concat('${c.env.PREFIX}', name) from address)
order by id desc limit ? offset ? `
).bind(limit, offset).all();
let count = 0;
if (offset == 0) {
const { count: mailCount } = await c.env.DB.prepare(`
SELECT count(*) FROM mails
where address NOT IN
(select concat('${c.env.PREFIX}', name) from address)`
).first();
count = mailCount;
}
return c.json({
results: results,
count: count
})
});
export { api }