Compare commits

...

4 Commits

Author SHA1 Message Date
dreamhunter2333
1572f4cd0f feat: upgrade version to v1.11.1 2026-08-22 16:18:52 +08:00
Dream Hunter
aeb2ac687d fix: 修复 Admin 二级标签页状态丢失 (#1118)
fix: reset admin nested tabs on section switch
2026-08-20 02:20:40 +08:00
Dream Hunter
108b8ef4ac feat: show D1 storage capacity in admin (#1117)
* feat: show D1 storage usage in admin

* test: add D1 storage E2E coverage

* test: use exact D1 storage labels
2026-08-19 16:16:54 +08:00
Dream Hunter
2762741226 feat: upgrade version to v1.12.0 (#1116)
- Update version number to 1.12.0 in all package.json files
- Add v1.12.0 placeholder in CHANGELOG.md
- Move (main) marker from v1.11.0 to v1.12.0
2026-08-19 13:56:17 +08:00
19 changed files with 500 additions and 17 deletions

View File

@@ -6,7 +6,23 @@
<a href="CHANGELOG_EN.md">English</a>
</p>
## v1.11.0(main)
## v1.11.1(main)
### Features
- feat: |Admin| 数据库页面新增 D1 存储容量展示,支持选择并保存 Free 或 Workers Paid 套餐,对比当前数据库大小和容量上限
### Bug Fixes
- fix: |Admin| 修复切换一级标签页时二级标签页偶发无选中项、内容不显示及指示条偏移的问题
### Improvements
### Testing
- test: |E2E| 覆盖 D1 数据库大小响应、配置键隔离,以及数据库页面套餐选择的持久化与刷新恢复
## v1.11.0
### Features

View File

@@ -6,7 +6,23 @@
<a href="CHANGELOG_EN.md">English</a>
</p>
## v1.11.0(main)
## v1.11.1(main)
### Features
- feat: |Admin| Add D1 storage capacity details to the database page, with persistent Free and Workers Paid plan selection and a comparison between the current database size and capacity limit
### Bug Fixes
- fix: |Admin| Fix secondary tabs occasionally losing their active item, hiding content, and leaving the indicator offset after switching primary tabs
### Improvements
### Testing
- test: |E2E| Cover the D1 database-size response, config-key isolation, and persistence of the database-page plan selection across reloads
## v1.11.0
### Features

View File

@@ -0,0 +1,73 @@
import { expect, test } from '@playwright/test';
import { WORKER_URL } from '../../fixtures/test-helpers';
const ADMIN_HEADERS = { 'x-admin-auth': 'e2e-admin-pass' };
test.describe('Admin Config and D1 Storage', () => {
test('reports database size in the database status response', async ({ request }) => {
const response = await request.get(`${WORKER_URL}/admin/db_version`, {
headers: ADMIN_HEADERS,
});
expect(response.ok()).toBe(true);
const body = await response.json();
expect(body.database_size).toEqual(expect.any(Number));
expect(body.database_size).toBeGreaterThan(0);
});
test('saves and retrieves a namespaced config value', async ({ request }) => {
const saveResponse = await request.post(`${WORKER_URL}/admin/config`, {
headers: ADMIN_HEADERS,
data: { key: 'd1_storage_plan', value: 'free' },
});
expect(saveResponse.ok()).toBe(true);
expect(await saveResponse.json()).toEqual({
success: true,
key: 'd1_storage_plan',
value: 'free',
});
const getResponse = await request.get(`${WORKER_URL}/admin/config/d1_storage_plan`, {
headers: ADMIN_HEADERS,
});
expect(getResponse.ok()).toBe(true);
expect(await getResponse.json()).toEqual({
key: 'd1_storage_plan',
value: 'free',
});
});
test('keeps config values isolated from internal settings', async ({ request }) => {
const versionBeforeResponse = await request.get(`${WORKER_URL}/admin/db_version`, {
headers: ADMIN_HEADERS,
});
const versionBefore = (await versionBeforeResponse.json()).current_db_version;
const saveResponse = await request.post(`${WORKER_URL}/admin/config`, {
headers: ADMIN_HEADERS,
data: { key: 'db_version', value: 'shadow-version' },
});
expect(saveResponse.ok()).toBe(true);
const versionAfterResponse = await request.get(`${WORKER_URL}/admin/db_version`, {
headers: ADMIN_HEADERS,
});
expect((await versionAfterResponse.json()).current_db_version).toBe(versionBefore);
});
test('rejects invalid keys and non-string values', async ({ request }) => {
const invalidKeyResponse = await request.post(`${WORKER_URL}/admin/config`, {
headers: ADMIN_HEADERS,
data: { key: 'invalid key', value: 'value' },
});
expect(invalidKeyResponse.status()).toBe(400);
const invalidValueResponse = await request.post(`${WORKER_URL}/admin/config`, {
headers: ADMIN_HEADERS,
data: { key: 'valid_key', value: 1 },
});
expect(invalidValueResponse.status()).toBe(400);
});
});

View File

@@ -0,0 +1,37 @@
import { expect, test } from '@playwright/test';
import { FRONTEND_URL, WORKER_URL } from '../../fixtures/test-helpers';
const ADMIN_HEADERS = { 'x-admin-auth': 'e2e-admin-pass' };
test('persists the selected D1 plan and restores it after reload', async ({ page, request }) => {
const seedResponse = await request.post(`${WORKER_URL}/admin/config`, {
headers: ADMIN_HEADERS,
data: { key: 'd1_storage_plan', value: 'free' },
});
expect(seedResponse.ok()).toBe(true);
await page.addInitScript(() => {
localStorage.setItem('adminAuth', 'e2e-admin-pass');
sessionStorage.setItem('adminTab', 'qucickSetup');
});
await page.goto(`${FRONTEND_URL}/en/admin`);
const storagePanel = page.locator('.storage-panel');
const planSelect = storagePanel.locator('.plan-select .n-select');
await expect(storagePanel.getByText('Current Database Size', { exact: true })).toBeVisible();
await expect(storagePanel.getByText('Database Capacity Limit', { exact: true })).toBeVisible();
await expect(storagePanel.getByText('Capacity Usage', { exact: true })).toBeVisible();
await expect(planSelect).toContainText('Free');
await planSelect.click();
await page.locator('.n-base-select-option').filter({ hasText: 'Workers Paid' }).click();
await expect(page.getByText('Workers plan saved')).toBeVisible();
await expect(storagePanel).toContainText('10.0 GB');
await page.reload();
await expect(planSelect).toContainText('Workers Paid');
await expect(storagePanel).toContainText('10.0 GB');
});

View File

@@ -1,6 +1,6 @@
{
"name": "cloudflare_temp_email",
"version": "1.11.0",
"version": "1.11.1",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,4 +1,16 @@
export const deMessages = {
"views.admin.DatabaseManager.current_database_size": "Aktuelle Datenbankgröße",
"views.admin.DatabaseManager.free_plan": "Free",
"views.admin.DatabaseManager.paid_plan": "Workers Paid",
"views.admin.DatabaseManager.plan": "Workers-Tarif",
"views.admin.DatabaseManager.plan_placeholder": "Cloudflare-Workers-Tarif auswählen",
"views.admin.DatabaseManager.planSaved": "Workers-Tarif gespeichert",
"views.admin.DatabaseManager.single_database_limit": "Datenbank-Kapazitätslimit",
"views.admin.DatabaseManager.storage_description": "Vergleiche die aktuelle Datenbankgröße mit den Tariflimits.",
"views.admin.DatabaseManager.storage_tip": "Die Auslastung wird anhand der aktuellen Datenbankgröße und des gewählten Tariflimits berechnet.",
"views.admin.DatabaseManager.storage_title": "D1-Speicherkapazität",
"views.admin.DatabaseManager.storage_usage": "Kapazitätsauslastung",
"views.admin.DatabaseManager.unavailable": "Nicht verfügbar",
"views.index.SimpleIndex.mailCount": "{current} / {total} E-Mails",
"views.admin.Statistics.activeAddressCount30days": "Aktive Adressanzahl in 30 Tagen",
"views.admin.Statistics.activeAddressCount7days": "Aktive Adressanzahl in 7 Tagen",

View File

@@ -1,4 +1,16 @@
export const esMessages = {
"views.admin.DatabaseManager.current_database_size": "Tamaño actual de la base de datos",
"views.admin.DatabaseManager.free_plan": "Free",
"views.admin.DatabaseManager.paid_plan": "Workers Paid",
"views.admin.DatabaseManager.plan": "Plan de Workers",
"views.admin.DatabaseManager.plan_placeholder": "Selecciona tu plan de Cloudflare Workers",
"views.admin.DatabaseManager.planSaved": "Plan de Workers guardado",
"views.admin.DatabaseManager.single_database_limit": "Límite de capacidad de la base de datos",
"views.admin.DatabaseManager.storage_description": "Compara el tamaño actual de la base de datos con los límites de tu plan.",
"views.admin.DatabaseManager.storage_tip": "El uso se calcula con el tamaño actual de la base de datos y el límite del plan seleccionado.",
"views.admin.DatabaseManager.storage_title": "Capacidad de almacenamiento D1",
"views.admin.DatabaseManager.storage_usage": "Uso de capacidad",
"views.admin.DatabaseManager.unavailable": "No disponible",
"views.index.SimpleIndex.mailCount": "{current} / {total} correos",
"views.admin.Statistics.activeAddressCount30days": "Cantidad de direcciones activas en 30 días",
"views.admin.Statistics.activeAddressCount7days": "Cantidad de direcciones activas en 7 días",

View File

@@ -1,4 +1,16 @@
export const jaMessages = {
"views.admin.DatabaseManager.current_database_size": "現在のデータベースサイズ",
"views.admin.DatabaseManager.free_plan": "Free",
"views.admin.DatabaseManager.paid_plan": "Workers Paid",
"views.admin.DatabaseManager.plan": "Workers プラン",
"views.admin.DatabaseManager.plan_placeholder": "Cloudflare Workers プランを選択",
"views.admin.DatabaseManager.planSaved": "Workers プランを保存しました",
"views.admin.DatabaseManager.single_database_limit": "データベース容量上限",
"views.admin.DatabaseManager.storage_description": "現在のデータベースサイズとプラン上限を比較します。",
"views.admin.DatabaseManager.storage_tip": "使用率は現在のデータベースサイズと選択したプラン上限から計算されます。",
"views.admin.DatabaseManager.storage_title": "D1 ストレージ容量",
"views.admin.DatabaseManager.storage_usage": "容量使用率",
"views.admin.DatabaseManager.unavailable": "利用不可",
"views.index.SimpleIndex.mailCount": "{current} / {total} 件のメール",
"views.admin.Statistics.activeAddressCount30days": "30日間のアクティブなアドレス数",
"views.admin.Statistics.activeAddressCount7days": "7日間のアクティブなアドレス数",

View File

@@ -1,4 +1,16 @@
export const ptBRMessages = {
"views.admin.DatabaseManager.current_database_size": "Tamanho atual do banco de dados",
"views.admin.DatabaseManager.free_plan": "Free",
"views.admin.DatabaseManager.paid_plan": "Workers Paid",
"views.admin.DatabaseManager.plan": "Plano do Workers",
"views.admin.DatabaseManager.plan_placeholder": "Selecione seu plano do Cloudflare Workers",
"views.admin.DatabaseManager.planSaved": "Plano do Workers salvo",
"views.admin.DatabaseManager.single_database_limit": "Limite de capacidade do banco de dados",
"views.admin.DatabaseManager.storage_description": "Compare o tamanho atual do banco de dados com os limites do seu plano.",
"views.admin.DatabaseManager.storage_tip": "O uso é calculado com o tamanho atual do banco de dados e o limite do plano selecionado.",
"views.admin.DatabaseManager.storage_title": "Capacidade de armazenamento D1",
"views.admin.DatabaseManager.storage_usage": "Uso da capacidade",
"views.admin.DatabaseManager.unavailable": "Indisponível",
"views.index.SimpleIndex.mailCount": "{current} / {total} e-mails",
"views.admin.Statistics.activeAddressCount30days": "Quantidade de endereços ativos em 30 dias",
"views.admin.Statistics.activeAddressCount7days": "Quantidade de endereços ativos em 7 dias",

View File

@@ -1694,6 +1694,14 @@ export const MESSAGE_REGISTRY = {
"en": "Current DB Version",
"zh": "当前数据库版本"
},
"current_database_size": {
"en": "Current Database Size",
"zh": "当前数据库大小"
},
"free_plan": {
"en": "Free",
"zh": "Free"
},
"init": {
"en": "Initialize Database",
"zh": "初始化数据库"
@@ -1717,6 +1725,46 @@ export const MESSAGE_REGISTRY = {
"need_migration_tip": {
"en": "Database migration is required. Please migrate the database.",
"zh": "需要迁移数据库,请迁移数据库"
},
"paid_plan": {
"en": "Workers Paid",
"zh": "Workers Paid"
},
"plan": {
"en": "Workers Plan",
"zh": "Workers 套餐"
},
"plan_placeholder": {
"en": "Select your Cloudflare Workers plan",
"zh": "请选择 Cloudflare Workers 套餐"
},
"planSaved": {
"en": "Workers plan saved",
"zh": "Workers 套餐已保存"
},
"single_database_limit": {
"en": "Database Capacity Limit",
"zh": "数据库容量上限"
},
"storage_description": {
"en": "Compare the current database size with your plan limits.",
"zh": "将当前数据库大小与套餐容量上限进行对比"
},
"storage_tip": {
"en": "Usage is calculated from the current database size and the selected plan limit.",
"zh": "使用率按当前数据库大小与所选套餐的数据库容量上限计算。"
},
"storage_title": {
"en": "D1 Storage Capacity",
"zh": "D1 存储容量"
},
"storage_usage": {
"en": "Capacity Usage",
"zh": "容量使用率"
},
"unavailable": {
"en": "Unavailable",
"zh": "暂不可用"
}
},
"views.admin.IpBlacklistSettings": {

View File

@@ -120,7 +120,7 @@ onMounted(async () => {
</n-modal>
<n-tabs v-if="showAdminPage" type="card" v-model:value="adminTab" :placement="globalTabplacement">
<n-tab-pane name="qucickSetup" :tab="t('qucickSetup')">
<n-tabs type="bar" justify-content="center" animated>
<n-tabs key="quick-setup-tabs" type="bar" justify-content="center" animated>
<n-tab-pane name="database" :tab="t('database')">
<DatabaseManager />
</n-tab-pane>
@@ -136,7 +136,7 @@ onMounted(async () => {
</n-tabs>
</n-tab-pane>
<n-tab-pane name="account" :tab="t('account')">
<n-tabs type="bar" justify-content="center" animated>
<n-tabs key="account-tabs" type="bar" justify-content="center" animated>
<n-tab-pane name="account" :tab="t('account')">
<Account />
</n-tab-pane>
@@ -161,7 +161,7 @@ onMounted(async () => {
</n-tabs>
</n-tab-pane>
<n-tab-pane name="user" :tab="t('user')">
<n-tabs type="bar" justify-content="center" animated>
<n-tabs key="user-tabs" type="bar" justify-content="center" animated>
<n-tab-pane name="user_management" :tab="t('user_management')">
<UserManagement />
</n-tab-pane>
@@ -177,7 +177,7 @@ onMounted(async () => {
</n-tabs>
</n-tab-pane>
<n-tab-pane name="mails" :tab="t('mails')">
<n-tabs type="bar" justify-content="center" animated>
<n-tabs key="mails-tabs" type="bar" justify-content="center" animated>
<n-tab-pane name="mails" :tab="t('mails')">
<Mails />
</n-tab-pane>
@@ -202,7 +202,7 @@ onMounted(async () => {
<Statistics />
</n-tab-pane>
<n-tab-pane name="maintenance" :tab="t('maintenance')">
<n-tabs type="bar" justify-content="center" animated>
<n-tabs key="maintenance-tabs" type="bar" justify-content="center" animated>
<n-tab-pane name="database" :tab="t('database')">
<DatabaseManager />
</n-tab-pane>

View File

@@ -1,5 +1,5 @@
<script setup>
import { ref, onMounted } from 'vue';
import { computed, ref, onMounted } from 'vue';
import { useScopedI18n } from '@/i18n/app'
import { CleaningServicesFilled } from '@vicons/material'
@@ -7,24 +7,95 @@ import { api } from '../../api'
import { init } from 'vooks/lib/on-fonts-ready';
const message = useMessage()
const D1_STORAGE_PLAN_CONFIG_KEY = 'd1_storage_plan'
const dbVersionData = ref({
need_initialization: false,
need_migration: false,
current_db_version: '',
code_db_version: ''
code_db_version: '',
database_size: null
})
const selectedPlan = ref(null)
const savedPlan = ref(null)
const savingPlan = ref(false)
const planOptions = computed(() => [
{
label: t('free_plan'),
value: 'free',
databaseLimit: 500 * 1024 ** 2
},
{
label: t('paid_plan'),
value: 'paid',
databaseLimit: 10 * 1024 ** 3
}
])
const selectedPlanDetails = computed(() => (
planOptions.value.find((plan) => plan.value === selectedPlan.value)
))
const storagePercentage = computed(() => {
if (!selectedPlanDetails.value || dbVersionData.value.database_size === null) return 0
return dbVersionData.value.database_size / selectedPlanDetails.value.databaseLimit * 100
})
const progressPercentage = computed(() => Math.min(storagePercentage.value, 100))
const progressStatus = computed(() => {
if (storagePercentage.value >= 90) return 'error'
if (storagePercentage.value >= 75) return 'warning'
return 'success'
})
const { t } = useScopedI18n('views.admin.DatabaseManager')
const formatBytes = (bytes) => {
if (bytes === null || bytes === undefined) return t('unavailable')
if (bytes === 0) return '0 B'
const units = ['B', 'KB', 'MB', 'GB', 'TB']
const unitIndex = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1)
const value = bytes / 1024 ** unitIndex
return `${value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2)} ${units[unitIndex]}`
}
const fetchData = async () => {
try {
const res = await api.fetch('/admin/db_version');
if (res) Object.assign(dbVersionData.value, res);
const [versionRes, configRes] = await Promise.all([
api.fetch('/admin/db_version'),
api.fetch(`/admin/config/${D1_STORAGE_PLAN_CONFIG_KEY}`)
]);
if (versionRes) Object.assign(dbVersionData.value, versionRes);
const configuredPlan = configRes?.value
if (planOptions.value.some((plan) => plan.value === configuredPlan)) {
selectedPlan.value = configuredPlan
savedPlan.value = configuredPlan
}
} catch (error) {
message.error(error.message || "error");
}
}
const savePlan = async (plan) => {
savingPlan.value = true
try {
await api.fetch('/admin/config', {
method: 'POST',
body: { key: D1_STORAGE_PLAN_CONFIG_KEY, value: plan }
})
savedPlan.value = plan
message.success(t('planSaved'))
} catch (error) {
selectedPlan.value = savedPlan.value
message.error(error.message || "error")
} finally {
savingPlan.value = false
}
}
const initialization = async () => {
try {
await api.fetch('/admin/db_initialize', {
@@ -77,6 +148,58 @@ onMounted(async () => {
</span>
</n-alert>
<div class="storage-panel">
<div class="storage-heading">
<div>
<h3>{{ t('storage_title') }}</h3>
<p>{{ t('storage_description') }}</p>
</div>
<div class="plan-select">
<span>{{ t('plan') }}</span>
<n-select
v-model:value="selectedPlan"
:options="planOptions"
:placeholder="t('plan_placeholder')"
:disabled="dbVersionData.need_initialization"
:loading="savingPlan"
@update:value="savePlan"
/>
</div>
</div>
<n-grid cols="1 s:2" responsive="screen" :x-gap="12" :y-gap="12">
<n-grid-item>
<div class="storage-stat">
<span>{{ t('current_database_size') }}</span>
<strong>{{ formatBytes(dbVersionData.database_size) }}</strong>
</div>
</n-grid-item>
<n-grid-item>
<div class="storage-stat">
<span>{{ t('single_database_limit') }}</span>
<strong>{{ selectedPlanDetails ? formatBytes(selectedPlanDetails.databaseLimit) : '—' }}</strong>
</div>
</n-grid-item>
</n-grid>
<div v-if="selectedPlanDetails" class="storage-progress">
<div class="storage-progress-label">
<span>{{ t('storage_usage') }}</span>
<span>{{ storagePercentage.toFixed(2) }}%</span>
</div>
<n-progress
type="line"
:percentage="progressPercentage"
:status="progressStatus"
:show-indicator="false"
/>
</div>
<n-alert class="storage-tip" type="default" :show-icon="false" :bordered="false">
{{ t('storage_tip') }}
</n-alert>
</div>
</n-card>
</div>
</template>
@@ -100,4 +223,88 @@ onMounted(async () => {
.n-button {
margin-top: 10px;
}
.storage-panel {
margin-top: 18px;
padding-top: 18px;
border-top: 1px solid var(--n-border-color);
text-align: left;
}
.storage-heading {
display: flex;
gap: 24px;
align-items: flex-end;
justify-content: space-between;
margin-bottom: 16px;
}
.storage-heading h3,
.storage-heading p {
margin: 0;
}
.storage-heading p {
margin-top: 4px;
color: var(--n-text-color-3);
}
.plan-select {
width: 220px;
}
.plan-select > span {
display: block;
margin-bottom: 6px;
color: var(--n-text-color-2);
}
.storage-stat {
display: flex;
min-height: 72px;
padding: 14px;
box-sizing: border-box;
flex-direction: column;
justify-content: space-between;
border: 1px solid var(--n-border-color);
border-radius: var(--n-border-radius);
}
.storage-stat span {
color: var(--n-text-color-3);
}
.storage-stat strong {
margin-top: 8px;
font-size: 18px;
color: var(--n-text-color);
}
.storage-progress {
margin-top: 16px;
}
.storage-progress-label {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
color: var(--n-text-color-2);
}
.storage-tip {
margin-top: 16px;
margin-bottom: 0;
}
@media (max-width: 640px) {
.storage-heading {
align-items: stretch;
flex-direction: column;
gap: 12px;
}
.plan-select {
width: 100%;
}
}
</style>

View File

@@ -1,6 +1,6 @@
{
"name": "temp-email-pages",
"version": "1.11.0",
"version": "1.11.1",
"description": "",
"main": "index.js",
"scripts": {

View File

@@ -1,7 +1,7 @@
{
"name": "temp-mail-docs",
"private": true,
"version": "1.11.0",
"version": "1.11.1",
"type": "module",
"devDependencies": {
"@types/node": "^26.2.0",

View File

@@ -1,6 +1,6 @@
{
"name": "cloudflare_temp_email",
"version": "1.11.0",
"version": "1.11.1",
"private": true,
"type": "module",
"scripts": {

View File

@@ -0,0 +1,31 @@
import { Context } from "hono";
import { getSetting, saveSetting } from "../utils";
const CONFIG_KEY_PREFIX = "admin-config:";
const CONFIG_KEY_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
const getStorageKey = (key: string) => `${CONFIG_KEY_PREFIX}${key}`;
export default {
get: async (c: Context<HonoCustomType>) => {
const key = c.req.param("key");
if (!CONFIG_KEY_PATTERN.test(key)) {
return c.text("Invalid config key", 400);
}
const value = await getSetting(c, getStorageKey(key));
return c.json({ key, value });
},
save: async (c: Context<HonoCustomType>) => {
const { key, value } = await c.req.json<{ key?: unknown, value?: unknown }>();
if (typeof key !== "string" || !CONFIG_KEY_PATTERN.test(key)) {
return c.text("Invalid config key", 400);
}
if (typeof value !== "string") {
return c.text("Config value must be a string", 400);
}
await saveSetting(c, getStorageKey(key), value);
return c.json({ success: true, key, value });
},
}

View File

@@ -219,11 +219,13 @@ export default {
},
getVersion: async (c: Context<HonoCustomType>) => {
const version = await utils.getSetting(c, CONSTANTS.DB_VERSION_KEY);
const sizeResult = await c.env.DB.prepare("SELECT 1").run();
return c.json({
need_initialization: !version,
need_migration: version && version != CONSTANTS.DB_VERSION,
current_db_version: version,
code_db_version: CONSTANTS.DB_VERSION
code_db_version: CONSTANTS.DB_VERSION,
database_size: sizeResult.meta.size_after ?? null
});
},
}

View File

@@ -18,6 +18,7 @@ import db_api from './db_api'
import ip_blacklist_settings from './ip_blacklist_settings'
import ai_extract_settings from './ai_extract_settings'
import e2e_test_api from './e2e_test_api'
import config_api from './config_api'
export const api = new Hono<HonoCustomType>()
@@ -96,6 +97,10 @@ api.get('admin/db_version', db_api.getVersion)
api.post('admin/db_initialize', db_api.initialize)
api.post('admin/db_migration', db_api.migrate)
// generic admin config
api.get('/admin/config/:key', config_api.get)
api.post('/admin/config', config_api.save)
// IP blacklist settings
api.get('/admin/ip_blacklist/settings', ip_blacklist_settings.getIpBlacklistSettings)
api.post('/admin/ip_blacklist/settings', ip_blacklist_settings.saveIpBlacklistSettings)

View File

@@ -1,5 +1,5 @@
export const CONSTANTS = {
VERSION: 'v' + '1.11.0',
VERSION: 'v' + '1.11.1',
// DB Version
DB_VERSION_KEY: 'db_version',