feat: add functionality to delete invalid invites and update related components

This commit is contained in:
shuaiplus
2026-06-29 12:12:13 +08:00
parent e31f82c0d6
commit 0d1bb196e2
15 changed files with 96 additions and 3 deletions
+1
View File
@@ -56,6 +56,7 @@ NodeWarden-compat/
.codex-upstream/bitwarden-browser/ .codex-upstream/bitwarden-browser/
.reasonix/ .reasonix/
.upstream/
# Compatibility analysis documents # Compatibility analysis documents
BITWARDEN_COMPATIBILITY_ANALYSIS.md BITWARDEN_COMPATIBILITY_ANALYSIS.md
+10 -1
View File
@@ -277,12 +277,21 @@ export async function handleAdminDeleteAllInvites(
env: Env, env: Env,
actorUser: User actorUser: User
): Promise<Response> { ): Promise<Response> {
void request;
if (!isAdmin(actorUser)) { if (!isAdmin(actorUser)) {
return errorResponse('Forbidden', 403); return errorResponse('Forbidden', 403);
} }
const storage = new StorageService(env.DB); const storage = new StorageService(env.DB);
const url = new URL(request.url);
if (url.searchParams.get('scope') === 'invalid') {
const deleted = await storage.deleteInvalidInvites();
await writeAuditLog(storage, actorUser.id, 'admin.invite.delete_invalid', 'invite', null, {
deleted,
}, request);
return jsonResponse({ deleted }, 200);
}
const deleted = await storage.deleteAllInvites(); const deleted = await storage.deleteAllInvites();
await writeAuditLog(storage, actorUser.id, 'admin.invite.delete_all', 'invite', null, { await writeAuditLog(storage, actorUser.id, 'admin.invite.delete_all', 'invite', null, {
deleted, deleted,
+9
View File
@@ -159,6 +159,15 @@ export async function deleteInvite(db: D1Database, code: string): Promise<boolea
return (result.meta.changes ?? 0) > 0; return (result.meta.changes ?? 0) > 0;
} }
export async function deleteInvalidInvites(db: D1Database): Promise<number> {
const now = new Date().toISOString();
const result = await db
.prepare("DELETE FROM invites WHERE status != 'active' OR expires_at <= ?")
.bind(now)
.run();
return Number(result.meta.changes ?? 0);
}
export async function deleteAllInvites(db: D1Database): Promise<number> { export async function deleteAllInvites(db: D1Database): Promise<number> {
const result = await db.prepare('DELETE FROM invites').run(); const result = await db.prepare('DELETE FROM invites').run();
return Number(result.meta.changes ?? 0); return Number(result.meta.changes ?? 0);
+5
View File
@@ -25,6 +25,7 @@ import {
assignInviteUsedBy as assignStoredInviteUsedBy, assignInviteUsedBy as assignStoredInviteUsedBy,
createInvite as createStoredInvite, createInvite as createStoredInvite,
deleteInvite as deleteStoredInvite, deleteInvite as deleteStoredInvite,
deleteInvalidInvites as deleteStoredInvalidInvites,
deleteAllInvites as deleteStoredInvites, deleteAllInvites as deleteStoredInvites,
getInvite as findStoredInvite, getInvite as findStoredInvite,
listAuditLogs as listStoredAuditLogs, listAuditLogs as listStoredAuditLogs,
@@ -333,6 +334,10 @@ export class StorageService {
return deleteStoredInvite(this.db, code); return deleteStoredInvite(this.db, code);
} }
async deleteInvalidInvites(): Promise<number> {
return deleteStoredInvalidInvites(this.db);
}
async deleteAllInvites(): Promise<number> { async deleteAllInvites(): Promise<number> {
return deleteStoredInvites(this.db); return deleteStoredInvites(this.db);
} }
+1
View File
@@ -2036,6 +2036,7 @@ export default function App() {
onRemoveAllDevices: accountSecurityActions.openRemoveAllDevices, onRemoveAllDevices: accountSecurityActions.openRemoveAllDevices,
onRefreshAdmin: adminActions.refreshAdmin, onRefreshAdmin: adminActions.refreshAdmin,
onCreateInvite: adminActions.createInvite, onCreateInvite: adminActions.createInvite,
onDeleteInvalidInvites: adminActions.deleteInvalidInvites,
onDeleteAllInvites: adminActions.deleteAllInvites, onDeleteAllInvites: adminActions.deleteAllInvites,
onToggleUserStatus: adminActions.toggleUserStatus, onToggleUserStatus: adminActions.toggleUserStatus,
onDeleteUser: adminActions.deleteUser, onDeleteUser: adminActions.deleteUser,
+5 -1
View File
@@ -13,6 +13,7 @@ interface AdminPageProps {
error: string; error: string;
onRefresh: () => void; onRefresh: () => void;
onCreateInvite: (hours: number) => Promise<void>; onCreateInvite: (hours: number) => Promise<void>;
onDeleteInvalidInvites: () => Promise<void>;
onDeleteAllInvites: () => Promise<void>; onDeleteAllInvites: () => Promise<void>;
onToggleUserStatus: (userId: string, currentStatus: 'active' | 'banned') => Promise<void>; onToggleUserStatus: (userId: string, currentStatus: 'active' | 'banned') => Promise<void>;
onDeleteUser: (userId: string) => Promise<void>; onDeleteUser: (userId: string) => Promise<void>;
@@ -134,7 +135,10 @@ export default function AdminPage(props: AdminPageProps) {
<h3>{t('txt_invites')}</h3> <h3>{t('txt_invites')}</h3>
<div className="actions admin-invites-head-actions"> <div className="actions admin-invites-head-actions">
<button type="button" className="btn btn-secondary small" disabled={props.loading} onClick={props.onRefresh}> <button type="button" className="btn btn-secondary small" disabled={props.loading} onClick={props.onRefresh}>
<RefreshCw size={14} className="btn-icon" /> {t('txt_sync')} <RefreshCw size={14} className="btn-icon" /> {t('txt_refresh')}
</button>
<button type="button" className="btn btn-danger small" onClick={() => void props.onDeleteInvalidInvites()}>
<Trash2 size={14} className="btn-icon" /> {t('txt_delete_invalid')}
</button> </button>
<button type="button" className="btn btn-danger small" onClick={() => void props.onDeleteAllInvites()}> <button type="button" className="btn btn-danger small" onClick={() => void props.onDeleteAllInvites()}>
<Trash2 size={14} className="btn-icon" /> {t('txt_delete_all')} <Trash2 size={14} className="btn-icon" /> {t('txt_delete_all')}
+2
View File
@@ -137,6 +137,7 @@ export interface AppMainRoutesProps {
onRemoveAllDevices: () => void; onRemoveAllDevices: () => void;
onCreateInvite: (hours: number) => Promise<void>; onCreateInvite: (hours: number) => Promise<void>;
onRefreshAdmin: () => void; onRefreshAdmin: () => void;
onDeleteInvalidInvites: () => Promise<void>;
onDeleteAllInvites: () => Promise<void>; onDeleteAllInvites: () => Promise<void>;
onToggleUserStatus: (userId: string, status: 'active' | 'banned') => Promise<void>; onToggleUserStatus: (userId: string, status: 'active' | 'banned') => Promise<void>;
onDeleteUser: (userId: string) => Promise<void>; onDeleteUser: (userId: string) => Promise<void>;
@@ -413,6 +414,7 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
error={props.adminError} error={props.adminError}
onRefresh={props.onRefreshAdmin} onRefresh={props.onRefreshAdmin}
onCreateInvite={props.onCreateInvite} onCreateInvite={props.onCreateInvite}
onDeleteInvalidInvites={props.onDeleteInvalidInvites}
onDeleteAllInvites={props.onDeleteAllInvites} onDeleteAllInvites={props.onDeleteAllInvites}
onToggleUserStatus={props.onToggleUserStatus} onToggleUserStatus={props.onToggleUserStatus}
onDeleteUser={props.onDeleteUser} onDeleteUser={props.onDeleteUser}
+21 -1
View File
@@ -1,5 +1,5 @@
import { useMemo } from 'preact/hooks'; import { useMemo } from 'preact/hooks';
import { createInvite, deleteAllInvites, deleteInvite, deleteUser, setUserStatus } from '@/lib/api/admin'; import { createInvite, deleteAllInvites, deleteInvalidInvites, deleteInvite, deleteUser, setUserStatus } from '@/lib/api/admin';
import { t } from '@/lib/i18n'; import { t } from '@/lib/i18n';
import type { AppConfirmState } from '@/components/AppGlobalOverlays'; import type { AppConfirmState } from '@/components/AppGlobalOverlays';
import type { AuthedFetch } from '@/lib/api/shared'; import type { AuthedFetch } from '@/lib/api/shared';
@@ -65,6 +65,26 @@ export default function useAdminActions(options: UseAdminActionsOptions) {
}); });
}, },
async deleteInvalidInvites() {
onSetConfirm({
title: t('txt_delete_invalid_invites'),
message: t('txt_delete_invalid_invites_confirm_message'),
danger: true,
onConfirm: () => {
onSetConfirm(null);
void (async () => {
try {
await deleteInvalidInvites(authedFetch);
await refetchInvites();
onNotify('success', t('txt_invalid_invites_deleted'));
} catch (error) {
onNotify('error', error instanceof Error ? error.message : t('txt_delete_invalid_invites_failed'));
}
})();
},
});
},
async deleteAllInvites() { async deleteAllInvites() {
onSetConfirm({ onSetConfirm({
title: t('txt_delete_all_invites'), title: t('txt_delete_all_invites'),
+5
View File
@@ -29,6 +29,11 @@ export async function deleteInvite(authedFetch: AuthedFetch, code: string): Prom
if (!resp.ok) throw new Error('Delete invite failed'); if (!resp.ok) throw new Error('Delete invite failed');
} }
export async function deleteInvalidInvites(authedFetch: AuthedFetch): Promise<void> {
const resp = await authedFetch('/api/admin/invites?scope=invalid', { method: 'DELETE' });
if (!resp.ok) throw new Error('Delete invalid invites failed');
}
export async function deleteAllInvites(authedFetch: AuthedFetch): Promise<void> { export async function deleteAllInvites(authedFetch: AuthedFetch): Promise<void> {
const resp = await authedFetch('/api/admin/invites', { method: 'DELETE' }); const resp = await authedFetch('/api/admin/invites', { method: 'DELETE' });
if (!resp.ok) throw new Error('Delete all invites failed'); if (!resp.ok) throw new Error('Delete all invites failed');
+7
View File
@@ -1127,6 +1127,13 @@ export function createDemoMainRoutesProps(base: AppMainRoutesProps, notify: Noti
onRefreshAdmin: () => { onRefreshAdmin: () => {
notify('success', t('txt_demo_admin_refreshed')); notify('success', t('txt_demo_admin_refreshed'));
}, },
onDeleteInvalidInvites: async () => {
const now = Date.now();
state.setInvites((prev) => prev.filter((invite) => (
invite.status === 'active' && (!invite.expiresAt || new Date(invite.expiresAt).getTime() > now)
)));
notify('success', t('txt_invalid_invites_deleted'));
},
onDeleteAllInvites: async () => { onDeleteAllInvites: async () => {
state.setInvites([]); state.setInvites([]);
notify('success', t('txt_all_invites_deleted')); notify('success', t('txt_all_invites_deleted'));
+6
View File
@@ -582,12 +582,17 @@ const en: Record<string, string> = {
"txt_identity_details": "Identity Details", "txt_identity_details": "Identity Details",
"txt_ie_browser": "IE Browser", "txt_ie_browser": "IE Browser",
"txt_create_invite_failed": "Failed to create invite", "txt_create_invite_failed": "Failed to create invite",
"txt_delete_invalid": "Delete Invalid",
"txt_delete_invalid_invites": "Delete invalid invites",
"txt_delete_invalid_invites_confirm_message": "Delete all invalid invite codes? Active, unexpired invite codes will be kept.",
"txt_delete_invalid_invites_failed": "Failed to delete invalid invites",
"txt_delete_invite": "Delete invite", "txt_delete_invite": "Delete invite",
"txt_delete_invite_confirm_message": "Delete this invite code? This cannot be undone.", "txt_delete_invite_confirm_message": "Delete this invite code? This cannot be undone.",
"txt_delete_invite_failed": "Failed to delete invite", "txt_delete_invite_failed": "Failed to delete invite",
"txt_invite_code_required": "Invite Code (Required)", "txt_invite_code_required": "Invite Code (Required)",
"txt_invite_created": "Invite created", "txt_invite_created": "Invite created",
"txt_invite_deleted": "Invite deleted", "txt_invite_deleted": "Invite deleted",
"txt_invalid_invites_deleted": "Invalid invites deleted",
"txt_invite_revoked": "Invite revoked", "txt_invite_revoked": "Invite revoked",
"txt_revoke_invite_failed": "Failed to revoke invite", "txt_revoke_invite_failed": "Failed to revoke invite",
"txt_invite_validity_hours": "Invite validity (hours)", "txt_invite_validity_hours": "Invite validity (hours)",
@@ -1162,6 +1167,7 @@ const en: Record<string, string> = {
"txt_log_action_admin_invite_create": "Create invite", "txt_log_action_admin_invite_create": "Create invite",
"txt_log_action_admin_invite_delete": "Delete invite", "txt_log_action_admin_invite_delete": "Delete invite",
"txt_log_action_admin_invite_delete_all": "Clear invites", "txt_log_action_admin_invite_delete_all": "Clear invites",
"txt_log_action_admin_invite_delete_invalid": "Delete invalid invites",
"txt_log_action_admin_invite_revoke": "Revoke invite", "txt_log_action_admin_invite_revoke": "Revoke invite",
"txt_log_action_admin_user_delete": "Delete user", "txt_log_action_admin_user_delete": "Delete user",
"txt_log_action_admin_user_status": "Change user status", "txt_log_action_admin_user_status": "Change user status",
+6
View File
@@ -582,12 +582,17 @@ const es: Record<string, string> = {
"txt_identity_details": "Detalles de identidad", "txt_identity_details": "Detalles de identidad",
"txt_ie_browser": "Navegador Internet Explorer", "txt_ie_browser": "Navegador Internet Explorer",
"txt_create_invite_failed": "Error al crear invitación", "txt_create_invite_failed": "Error al crear invitación",
"txt_delete_invalid": "Eliminar inválidas",
"txt_delete_invalid_invites": "Eliminar invitaciones inválidas",
"txt_delete_invalid_invites_confirm_message": "¿Eliminar todos los códigos de invitación inválidos? Se conservarán los códigos activos y no vencidos.",
"txt_delete_invalid_invites_failed": "Error al eliminar invitaciones inválidas",
"txt_delete_invite": "Eliminar invitación", "txt_delete_invite": "Eliminar invitación",
"txt_delete_invite_confirm_message": "¿Eliminar este código de invitación? Esta acción no se puede deshacer.", "txt_delete_invite_confirm_message": "¿Eliminar este código de invitación? Esta acción no se puede deshacer.",
"txt_delete_invite_failed": "Error al eliminar invitación", "txt_delete_invite_failed": "Error al eliminar invitación",
"txt_invite_code_required": "Código de invitación (obligatorio)", "txt_invite_code_required": "Código de invitación (obligatorio)",
"txt_invite_created": "Invitación creada", "txt_invite_created": "Invitación creada",
"txt_invite_deleted": "Invitación eliminada", "txt_invite_deleted": "Invitación eliminada",
"txt_invalid_invites_deleted": "Invitaciones inválidas eliminadas",
"txt_invite_revoked": "Invitación revocada", "txt_invite_revoked": "Invitación revocada",
"txt_revoke_invite_failed": "Error al revocar invitación", "txt_revoke_invite_failed": "Error al revocar invitación",
"txt_invite_validity_hours": "Validez de la invitación en horas", "txt_invite_validity_hours": "Validez de la invitación en horas",
@@ -1162,6 +1167,7 @@ const es: Record<string, string> = {
"txt_log_action_admin_invite_create": "Create invite", "txt_log_action_admin_invite_create": "Create invite",
"txt_log_action_admin_invite_delete": "Delete invite", "txt_log_action_admin_invite_delete": "Delete invite",
"txt_log_action_admin_invite_delete_all": "Clear invites", "txt_log_action_admin_invite_delete_all": "Clear invites",
"txt_log_action_admin_invite_delete_invalid": "Delete invalid invites",
"txt_log_action_admin_invite_revoke": "Revoke invite", "txt_log_action_admin_invite_revoke": "Revoke invite",
"txt_log_action_admin_user_delete": "Delete user", "txt_log_action_admin_user_delete": "Delete user",
"txt_log_action_admin_user_status": "Change user status", "txt_log_action_admin_user_status": "Change user status",
+6
View File
@@ -582,12 +582,17 @@ const ru: Record<string, string> = {
"txt_identity_details": "Данные личности", "txt_identity_details": "Данные личности",
"txt_ie_browser": "IE-браузер", "txt_ie_browser": "IE-браузер",
"txt_create_invite_failed": "Не удалось создать приглашение", "txt_create_invite_failed": "Не удалось создать приглашение",
"txt_delete_invalid": "Удалить недействительные",
"txt_delete_invalid_invites": "Удалить недействительные приглашения",
"txt_delete_invalid_invites_confirm_message": "Удалить все недействительные пригласительные коды? Активные и не истекшие коды будут сохранены.",
"txt_delete_invalid_invites_failed": "Не удалось удалить недействительные приглашения",
"txt_delete_invite": "Удалить приглашение", "txt_delete_invite": "Удалить приглашение",
"txt_delete_invite_confirm_message": "Удалить этот пригласительный код? Это действие нельзя отменить.", "txt_delete_invite_confirm_message": "Удалить этот пригласительный код? Это действие нельзя отменить.",
"txt_delete_invite_failed": "Не удалось удалить приглашение", "txt_delete_invite_failed": "Не удалось удалить приглашение",
"txt_invite_code_required": "Пригласительный код (обязательно)", "txt_invite_code_required": "Пригласительный код (обязательно)",
"txt_invite_created": "Приглашение создано", "txt_invite_created": "Приглашение создано",
"txt_invite_deleted": "Приглашение удалено", "txt_invite_deleted": "Приглашение удалено",
"txt_invalid_invites_deleted": "Недействительные приглашения удалены",
"txt_invite_revoked": "Приглашение отозвано", "txt_invite_revoked": "Приглашение отозвано",
"txt_revoke_invite_failed": "Не удалось отозвать приглашение", "txt_revoke_invite_failed": "Не удалось отозвать приглашение",
"txt_invite_validity_hours": "Срок действия приглашения (часы)", "txt_invite_validity_hours": "Срок действия приглашения (часы)",
@@ -1162,6 +1167,7 @@ const ru: Record<string, string> = {
"txt_log_action_admin_invite_create": "Create invite", "txt_log_action_admin_invite_create": "Create invite",
"txt_log_action_admin_invite_delete": "Delete invite", "txt_log_action_admin_invite_delete": "Delete invite",
"txt_log_action_admin_invite_delete_all": "Clear invites", "txt_log_action_admin_invite_delete_all": "Clear invites",
"txt_log_action_admin_invite_delete_invalid": "Delete invalid invites",
"txt_log_action_admin_invite_revoke": "Revoke invite", "txt_log_action_admin_invite_revoke": "Revoke invite",
"txt_log_action_admin_user_delete": "Delete user", "txt_log_action_admin_user_delete": "Delete user",
"txt_log_action_admin_user_status": "Change user status", "txt_log_action_admin_user_status": "Change user status",
+6
View File
@@ -582,12 +582,17 @@ const zhCN: Record<string, string> = {
"txt_identity_details": "身份详情", "txt_identity_details": "身份详情",
"txt_ie_browser": "IE 浏览器", "txt_ie_browser": "IE 浏览器",
"txt_create_invite_failed": "创建邀请码失败", "txt_create_invite_failed": "创建邀请码失败",
"txt_delete_invalid": "删除无效",
"txt_delete_invalid_invites": "删除无效邀请码",
"txt_delete_invalid_invites_confirm_message": "确定删除所有无效邀请码吗?仍有效且未过期的邀请码会保留。",
"txt_delete_invalid_invites_failed": "删除无效邀请码失败",
"txt_delete_invite": "删除邀请码", "txt_delete_invite": "删除邀请码",
"txt_delete_invite_confirm_message": "确定删除该邀请码吗?删除后无法恢复。", "txt_delete_invite_confirm_message": "确定删除该邀请码吗?删除后无法恢复。",
"txt_delete_invite_failed": "删除邀请码失败", "txt_delete_invite_failed": "删除邀请码失败",
"txt_invite_code_required": "邀请码(必填)", "txt_invite_code_required": "邀请码(必填)",
"txt_invite_created": "邀请码已创建", "txt_invite_created": "邀请码已创建",
"txt_invite_deleted": "邀请码已删除", "txt_invite_deleted": "邀请码已删除",
"txt_invalid_invites_deleted": "无效邀请码已删除",
"txt_invite_revoked": "邀请码已撤销", "txt_invite_revoked": "邀请码已撤销",
"txt_revoke_invite_failed": "撤销邀请码失败", "txt_revoke_invite_failed": "撤销邀请码失败",
"txt_invite_validity_hours": "邀请码有效期(小时)", "txt_invite_validity_hours": "邀请码有效期(小时)",
@@ -1162,6 +1167,7 @@ const zhCN: Record<string, string> = {
"txt_log_action_admin_invite_create": "创建邀请", "txt_log_action_admin_invite_create": "创建邀请",
"txt_log_action_admin_invite_delete": "删除邀请", "txt_log_action_admin_invite_delete": "删除邀请",
"txt_log_action_admin_invite_delete_all": "清空邀请", "txt_log_action_admin_invite_delete_all": "清空邀请",
"txt_log_action_admin_invite_delete_invalid": "删除无效邀请",
"txt_log_action_admin_invite_revoke": "撤销邀请", "txt_log_action_admin_invite_revoke": "撤销邀请",
"txt_log_action_admin_user_delete": "删除用户", "txt_log_action_admin_user_delete": "删除用户",
"txt_log_action_admin_user_status": "修改用户状态", "txt_log_action_admin_user_status": "修改用户状态",
+6
View File
@@ -582,12 +582,17 @@ const zhTW: Record<string, string> = {
"txt_identity_details": "身份詳情", "txt_identity_details": "身份詳情",
"txt_ie_browser": "IE 瀏覽器", "txt_ie_browser": "IE 瀏覽器",
"txt_create_invite_failed": "創建邀請碼失敗", "txt_create_invite_failed": "創建邀請碼失敗",
"txt_delete_invalid": "刪除無效",
"txt_delete_invalid_invites": "刪除無效邀請碼",
"txt_delete_invalid_invites_confirm_message": "確定刪除所有無效邀請碼嗎?仍有效且未過期的邀請碼會保留。",
"txt_delete_invalid_invites_failed": "刪除無效邀請碼失敗",
"txt_delete_invite": "刪除邀請碼", "txt_delete_invite": "刪除邀請碼",
"txt_delete_invite_confirm_message": "確定刪除此邀請碼嗎?刪除後無法復原。", "txt_delete_invite_confirm_message": "確定刪除此邀請碼嗎?刪除後無法復原。",
"txt_delete_invite_failed": "刪除邀請碼失敗", "txt_delete_invite_failed": "刪除邀請碼失敗",
"txt_invite_code_required": "邀請碼(必填)", "txt_invite_code_required": "邀請碼(必填)",
"txt_invite_created": "邀請碼已創建", "txt_invite_created": "邀請碼已創建",
"txt_invite_deleted": "邀請碼已刪除", "txt_invite_deleted": "邀請碼已刪除",
"txt_invalid_invites_deleted": "無效邀請碼已刪除",
"txt_invite_revoked": "邀請碼已撤銷", "txt_invite_revoked": "邀請碼已撤銷",
"txt_revoke_invite_failed": "撤銷邀請碼失敗", "txt_revoke_invite_failed": "撤銷邀請碼失敗",
"txt_invite_validity_hours": "邀請碼有效期(小時)", "txt_invite_validity_hours": "邀請碼有效期(小時)",
@@ -1162,6 +1167,7 @@ const zhTW: Record<string, string> = {
"txt_log_action_admin_invite_create": "建立邀請", "txt_log_action_admin_invite_create": "建立邀請",
"txt_log_action_admin_invite_delete": "刪除邀請", "txt_log_action_admin_invite_delete": "刪除邀請",
"txt_log_action_admin_invite_delete_all": "清空邀請", "txt_log_action_admin_invite_delete_all": "清空邀請",
"txt_log_action_admin_invite_delete_invalid": "刪除無效邀請",
"txt_log_action_admin_invite_revoke": "撤銷邀請", "txt_log_action_admin_invite_revoke": "撤銷邀請",
"txt_log_action_admin_user_delete": "刪除使用者", "txt_log_action_admin_user_delete": "刪除使用者",
"txt_log_action_admin_user_status": "修改使用者狀態", "txt_log_action_admin_user_status": "修改使用者狀態",