mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-05-12 02:20:12 +08:00
feat: add admin query emails (#39)
* feat: add NPagination * feat: admin query emails
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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') }}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
Reference in New Issue
Block a user