feat: 管理后台接真实接口并加 Playwright e2e
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Vitest 组件测用:把 `@/api/admin` 指到假数据(不连真实后端)。
|
||||
* 生产与 Playwright e2e 走真实 `admin.ts`。
|
||||
*/
|
||||
import { ApiError } from "./http";
|
||||
import { mockApi } from "./mock";
|
||||
import { message } from "@/utils/notify";
|
||||
import type {
|
||||
EndpointCreateRequest,
|
||||
EndpointListQuery,
|
||||
EndpointPatchRequest,
|
||||
GroupFailed,
|
||||
MeInfo,
|
||||
Overview,
|
||||
RegistrationUpdateRequest,
|
||||
} from "./types";
|
||||
|
||||
export { ApiError };
|
||||
export type * from "./types";
|
||||
|
||||
async function run<T>(fn: () => Promise<T>, silent = false): Promise<T> {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (e) {
|
||||
if (!silent && e instanceof ApiError) {
|
||||
message.error(e.message);
|
||||
} else if (!silent && e instanceof Error) {
|
||||
message.error(e.message);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export function login(username: string, password: string) {
|
||||
return run(() => mockApi.login(username, password), true);
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
return run(() => mockApi.logout());
|
||||
}
|
||||
|
||||
export function fetchMe(silent = false) {
|
||||
return run(() => mockApi.me(), silent);
|
||||
}
|
||||
|
||||
export function changePassword(oldPassword: string, newPassword: string) {
|
||||
return run(() => mockApi.changePassword(oldPassword, newPassword));
|
||||
}
|
||||
|
||||
export function fetchOverview(): Promise<Overview> {
|
||||
return run(() => mockApi.overview());
|
||||
}
|
||||
|
||||
export function listEndpoints(q?: EndpointListQuery) {
|
||||
return run(() => mockApi.listEndpoints(q));
|
||||
}
|
||||
|
||||
export function createEndpoint(body: EndpointCreateRequest) {
|
||||
return run(() => mockApi.createEndpoint(body));
|
||||
}
|
||||
|
||||
export function importEndpoints(csvText: string) {
|
||||
return run(() => mockApi.importEndpoints(csvText), true);
|
||||
}
|
||||
|
||||
export function batchEndpoints(ids: string[], action: "disable" | "enable" | "delete") {
|
||||
return run(() => mockApi.batchEndpoints(ids, action));
|
||||
}
|
||||
|
||||
export function getEndpoint(id: string) {
|
||||
return run(() => mockApi.getEndpoint(id));
|
||||
}
|
||||
|
||||
export function patchEndpoint(id: string, body: EndpointPatchRequest) {
|
||||
return run(() => mockApi.patchEndpoint(id, body));
|
||||
}
|
||||
|
||||
export function deleteEndpoint(id: string) {
|
||||
return run(() => mockApi.deleteEndpoint(id));
|
||||
}
|
||||
|
||||
export function kickEndpoint(id: string) {
|
||||
return run(() => mockApi.kickEndpoint(id));
|
||||
}
|
||||
|
||||
export function resetLoginPassword(id: string, loginPassword?: string) {
|
||||
return run(() => mockApi.resetLoginPassword(id, loginPassword));
|
||||
}
|
||||
|
||||
export function setTalkPassword(id: string, talkPassword: string) {
|
||||
return run(() => mockApi.setTalkPassword(id, talkPassword));
|
||||
}
|
||||
|
||||
export function unlockEndpoint(id: string) {
|
||||
return run(() => mockApi.unlockEndpoint(id));
|
||||
}
|
||||
|
||||
export function getRegistration() {
|
||||
return run(() => mockApi.getRegistration());
|
||||
}
|
||||
|
||||
export function updateRegistration(body: RegistrationUpdateRequest) {
|
||||
return run(() => mockApi.updateRegistration(body));
|
||||
}
|
||||
|
||||
export function listTokens() {
|
||||
return run(() => mockApi.listTokens());
|
||||
}
|
||||
|
||||
export function createToken(name: string) {
|
||||
return run(() => mockApi.createToken(name));
|
||||
}
|
||||
|
||||
export function patchToken(id: string, body: { name?: string; enabled?: boolean }) {
|
||||
return run(() => mockApi.patchToken(id, body));
|
||||
}
|
||||
|
||||
export function deleteToken(id: string) {
|
||||
return run(() => mockApi.deleteToken(id));
|
||||
}
|
||||
|
||||
export function listGroups(query?: string, cursor?: string, limit?: number) {
|
||||
return run(() => mockApi.listGroups(query, cursor, limit));
|
||||
}
|
||||
|
||||
export function createGroup(body: {
|
||||
id?: string;
|
||||
name: string;
|
||||
owner_id: string;
|
||||
member_ids?: string[];
|
||||
}): Promise<{ id: string; name: string; owner_id: string; failed: GroupFailed[] }> {
|
||||
return run(() => mockApi.createGroup(body));
|
||||
}
|
||||
|
||||
export function getGroup(id: string, cursor?: string, limit?: number) {
|
||||
return run(() => mockApi.getGroup(id, cursor, limit));
|
||||
}
|
||||
|
||||
export function renameGroup(id: string, name: string) {
|
||||
return run(() => mockApi.renameGroup(id, name));
|
||||
}
|
||||
|
||||
export function deleteGroup(id: string) {
|
||||
return run(() => mockApi.deleteGroup(id));
|
||||
}
|
||||
|
||||
export function addGroupMembers(id: string, memberIds: string[]) {
|
||||
return run(() => mockApi.addGroupMembers(id, memberIds));
|
||||
}
|
||||
|
||||
export function removeGroupMember(id: string, endpointId: string) {
|
||||
return run(() => mockApi.removeGroupMember(id, endpointId));
|
||||
}
|
||||
|
||||
export function transferGroup(id: string, endpointId: string) {
|
||||
return run(() => mockApi.transferGroup(id, endpointId));
|
||||
}
|
||||
|
||||
export function listMessages(q: {
|
||||
cursor?: string;
|
||||
limit?: number;
|
||||
sender_id?: string;
|
||||
endpoint_id?: string;
|
||||
group_id?: string;
|
||||
state?: string;
|
||||
}) {
|
||||
return run(() => mockApi.listMessages(q));
|
||||
}
|
||||
|
||||
export function getMessage(seq: number) {
|
||||
return run(() => mockApi.getMessage(seq));
|
||||
}
|
||||
|
||||
export function getSettings() {
|
||||
return run(() => mockApi.getSettings());
|
||||
}
|
||||
|
||||
export type { MeInfo };
|
||||
+210
-42
@@ -1,8 +1,7 @@
|
||||
/**
|
||||
* 管理接口函数集中处。当前走假数据(mock);W4 改为 requestAdmin 即可,页面不用改。
|
||||
* 管理接口:真实请求 `/api/admin`(开发时由 Vite 代理到本机 nixmsg)。
|
||||
*/
|
||||
import { ApiError } from "./http";
|
||||
import { mockApi } from "./mock";
|
||||
import { ApiError, requestAdmin } from "./http";
|
||||
import { message } from "@/utils/notify";
|
||||
import type {
|
||||
ApiToken,
|
||||
@@ -43,97 +42,212 @@ async function run<T>(fn: () => Promise<T>, silent = false): Promise<T> {
|
||||
}
|
||||
}
|
||||
|
||||
function buildQuery(params: Record<string, string | number | boolean | undefined | null>): string {
|
||||
const sp = new URLSearchParams();
|
||||
for (const [k, v] of Object.entries(params)) {
|
||||
if (v === undefined || v === null || v === "") continue;
|
||||
sp.set(k, String(v));
|
||||
}
|
||||
const q = sp.toString();
|
||||
return q ? `?${q}` : "";
|
||||
}
|
||||
|
||||
export function login(username: string, password: string) {
|
||||
// 登录页自行展示错误,避免与 message 重复
|
||||
return run(() => mockApi.login(username, password), true);
|
||||
return run(
|
||||
() =>
|
||||
requestAdmin<{ username: string }>("/api/admin/login", {
|
||||
method: "POST",
|
||||
body: { username, password },
|
||||
silent: true,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
return run(() => mockApi.logout());
|
||||
return run(() => requestAdmin<Record<string, never>>("/api/admin/logout", { method: "POST" }));
|
||||
}
|
||||
|
||||
export function fetchMe(silent = false) {
|
||||
return run(() => mockApi.me(), silent);
|
||||
return run(() => requestAdmin<MeInfo>("/api/admin/me", { silent }), silent);
|
||||
}
|
||||
|
||||
export function changePassword(oldPassword: string, newPassword: string) {
|
||||
return run(() => mockApi.changePassword(oldPassword, newPassword));
|
||||
return run(() =>
|
||||
requestAdmin<Record<string, never>>("/api/admin/password", {
|
||||
method: "POST",
|
||||
body: { old_password: oldPassword, new_password: newPassword },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function fetchOverview(): Promise<Overview> {
|
||||
return run(() => mockApi.overview());
|
||||
return run(() => requestAdmin<Overview>("/api/admin/overview"));
|
||||
}
|
||||
|
||||
export function listEndpoints(q?: EndpointListQuery): Promise<PageResult<Endpoint>> {
|
||||
return run(() => mockApi.listEndpoints(q));
|
||||
export function listEndpoints(q: EndpointListQuery = {}): Promise<PageResult<Endpoint>> {
|
||||
return run(() =>
|
||||
requestAdmin<PageResult<Endpoint>>(
|
||||
`/api/admin/endpoints${buildQuery({
|
||||
cursor: q.cursor,
|
||||
limit: q.limit,
|
||||
source: q.source || undefined,
|
||||
online: q.online === "" || q.online === undefined ? undefined : q.online,
|
||||
enabled: q.enabled === "" || q.enabled === undefined ? undefined : q.enabled,
|
||||
query: q.query,
|
||||
})}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function createEndpoint(body: EndpointCreateRequest): Promise<EndpointCreateResult> {
|
||||
return run(() => mockApi.createEndpoint(body));
|
||||
return run(() =>
|
||||
requestAdmin<EndpointCreateResult>("/api/admin/endpoints", { method: "POST", body }),
|
||||
);
|
||||
}
|
||||
|
||||
export function importEndpoints(csvText: string): Promise<{ items: ImportItem[] }> {
|
||||
return run(() => mockApi.importEndpoints(csvText), true);
|
||||
return run(
|
||||
() =>
|
||||
requestAdmin<{ items: ImportItem[] }>("/api/admin/endpoints/import", {
|
||||
method: "POST",
|
||||
rawBody: csvText,
|
||||
contentType: "text/csv",
|
||||
silent: true,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
export function batchEndpoints(ids: string[], action: "disable" | "enable" | "delete"): Promise<BatchResult> {
|
||||
return run(() => mockApi.batchEndpoints(ids, action));
|
||||
export function batchEndpoints(
|
||||
ids: string[],
|
||||
action: "disable" | "enable" | "delete",
|
||||
): Promise<BatchResult> {
|
||||
return run(() =>
|
||||
requestAdmin<BatchResult>("/api/admin/endpoints/batch", {
|
||||
method: "POST",
|
||||
body: { ids, action },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function getEndpoint(id: string): Promise<Endpoint> {
|
||||
return run(() => mockApi.getEndpoint(id));
|
||||
return run(() => requestAdmin<Endpoint>(`/api/admin/endpoints/${encodeURIComponent(id)}`));
|
||||
}
|
||||
|
||||
export function patchEndpoint(id: string, body: EndpointPatchRequest): Promise<Endpoint> {
|
||||
return run(() => mockApi.patchEndpoint(id, body));
|
||||
return run(() =>
|
||||
requestAdmin<Endpoint>(`/api/admin/endpoints/${encodeURIComponent(id)}`, {
|
||||
method: "PATCH",
|
||||
body,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function deleteEndpoint(id: string) {
|
||||
return run(() => mockApi.deleteEndpoint(id));
|
||||
return run(() =>
|
||||
requestAdmin<Record<string, never>>(`/api/admin/endpoints/${encodeURIComponent(id)}`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function kickEndpoint(id: string) {
|
||||
return run(() => mockApi.kickEndpoint(id));
|
||||
return run(() =>
|
||||
requestAdmin<{ kicked: boolean }>(`/api/admin/endpoints/${encodeURIComponent(id)}/kick`, {
|
||||
method: "POST",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function resetLoginPassword(id: string, loginPassword?: string) {
|
||||
return run(() => mockApi.resetLoginPassword(id, loginPassword));
|
||||
return run(() =>
|
||||
requestAdmin<{ login_password: string }>(
|
||||
`/api/admin/endpoints/${encodeURIComponent(id)}/reset-login-password`,
|
||||
{
|
||||
method: "POST",
|
||||
body: loginPassword ? { login_password: loginPassword } : {},
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function setTalkPassword(id: string, talkPassword: string) {
|
||||
return run(() => mockApi.setTalkPassword(id, talkPassword));
|
||||
return run(() =>
|
||||
requestAdmin<{ talk_password_set: boolean }>(
|
||||
`/api/admin/endpoints/${encodeURIComponent(id)}/talk-password`,
|
||||
{
|
||||
method: "PUT",
|
||||
body: { talk_password: talkPassword },
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function unlockEndpoint(id: string) {
|
||||
return run(() => mockApi.unlockEndpoint(id));
|
||||
return run(() =>
|
||||
requestAdmin<Record<string, never>>(`/api/admin/endpoints/${encodeURIComponent(id)}/unlock`, {
|
||||
method: "POST",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function getRegistration(): Promise<RegistrationSettings> {
|
||||
return run(() => mockApi.getRegistration());
|
||||
return run(() => requestAdmin<RegistrationSettings>("/api/admin/registration"));
|
||||
}
|
||||
|
||||
export function updateRegistration(body: RegistrationUpdateRequest): Promise<RegistrationSettings> {
|
||||
return run(() => mockApi.updateRegistration(body));
|
||||
return run(() =>
|
||||
requestAdmin<RegistrationSettings>("/api/admin/registration", {
|
||||
method: "PUT",
|
||||
body,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function listTokens(): Promise<PageResult<ApiToken>> {
|
||||
return run(() => mockApi.listTokens());
|
||||
return run(() => requestAdmin<PageResult<ApiToken>>("/api/admin/tokens"));
|
||||
}
|
||||
|
||||
export function createToken(name: string): Promise<ApiTokenCreateResult> {
|
||||
return run(() => mockApi.createToken(name));
|
||||
return run(() =>
|
||||
requestAdmin<ApiTokenCreateResult>("/api/admin/tokens", {
|
||||
method: "POST",
|
||||
body: { name },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function patchToken(id: number, body: { name?: string; enabled?: boolean }): Promise<ApiToken> {
|
||||
return run(() => mockApi.patchToken(id, body));
|
||||
export function patchToken(
|
||||
id: string,
|
||||
body: { name?: string; enabled?: boolean },
|
||||
): Promise<ApiToken> {
|
||||
return run(() =>
|
||||
requestAdmin<ApiToken>(`/api/admin/tokens/${encodeURIComponent(id)}`, {
|
||||
method: "PATCH",
|
||||
body,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function deleteToken(id: number) {
|
||||
return run(() => mockApi.deleteToken(id));
|
||||
export function deleteToken(id: string) {
|
||||
return run(() =>
|
||||
requestAdmin<Record<string, never>>(`/api/admin/tokens/${encodeURIComponent(id)}`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function listGroups(query?: string, cursor?: string, limit?: number): Promise<PageResult<GroupSummary>> {
|
||||
return run(() => mockApi.listGroups(query, cursor, limit));
|
||||
export function listGroups(
|
||||
query?: string,
|
||||
cursor?: string,
|
||||
limit?: number,
|
||||
): Promise<PageResult<GroupSummary>> {
|
||||
return run(() =>
|
||||
requestAdmin<PageResult<GroupSummary>>(
|
||||
`/api/admin/groups${buildQuery({ query, cursor, limit })}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function createGroup(body: {
|
||||
@@ -142,31 +256,74 @@ export function createGroup(body: {
|
||||
owner_id: string;
|
||||
member_ids?: string[];
|
||||
}): Promise<{ id: string; name: string; owner_id: string; failed: GroupFailed[] }> {
|
||||
return run(() => mockApi.createGroup(body));
|
||||
// A3:后台建群不接受自定义 id;有值才传,否则省略字段
|
||||
const payload: Record<string, unknown> = {
|
||||
name: body.name,
|
||||
owner_id: body.owner_id,
|
||||
};
|
||||
if (body.member_ids?.length) payload.member_ids = body.member_ids;
|
||||
if (body.id?.trim()) payload.id = body.id.trim();
|
||||
return run(() =>
|
||||
requestAdmin<{ id: string; name: string; owner_id: string; failed: GroupFailed[] }>(
|
||||
"/api/admin/groups",
|
||||
{ method: "POST", body: payload },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function getGroup(id: string, cursor?: string, limit?: number): Promise<GroupDetail> {
|
||||
return run(() => mockApi.getGroup(id, cursor, limit));
|
||||
return run(() =>
|
||||
requestAdmin<GroupDetail>(
|
||||
`/api/admin/groups/${encodeURIComponent(id)}${buildQuery({ cursor, limit })}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function renameGroup(id: string, name: string): Promise<GroupSummary> {
|
||||
return run(() => mockApi.renameGroup(id, name));
|
||||
return run(() =>
|
||||
requestAdmin<GroupSummary>(`/api/admin/groups/${encodeURIComponent(id)}`, {
|
||||
method: "PATCH",
|
||||
body: { name },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function deleteGroup(id: string) {
|
||||
return run(() => mockApi.deleteGroup(id));
|
||||
return run(() =>
|
||||
requestAdmin<Record<string, never>>(`/api/admin/groups/${encodeURIComponent(id)}`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function addGroupMembers(id: string, memberIds: string[]) {
|
||||
return run(() => mockApi.addGroupMembers(id, memberIds));
|
||||
return run(() =>
|
||||
requestAdmin<{ failed: GroupFailed[] }>(
|
||||
`/api/admin/groups/${encodeURIComponent(id)}/members`,
|
||||
{
|
||||
method: "POST",
|
||||
body: { member_ids: memberIds },
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function removeGroupMember(id: string, endpointId: string) {
|
||||
return run(() => mockApi.removeGroupMember(id, endpointId));
|
||||
return run(() =>
|
||||
requestAdmin<Record<string, never>>(
|
||||
`/api/admin/groups/${encodeURIComponent(id)}/members/${encodeURIComponent(endpointId)}`,
|
||||
{ method: "DELETE" },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function transferGroup(id: string, endpointId: string) {
|
||||
return run(() => mockApi.transferGroup(id, endpointId));
|
||||
return run(() =>
|
||||
requestAdmin<{ owner_id: string }>(`/api/admin/groups/${encodeURIComponent(id)}/transfer`, {
|
||||
method: "POST",
|
||||
body: { endpoint_id: endpointId },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function listMessages(q: {
|
||||
@@ -177,15 +334,26 @@ export function listMessages(q: {
|
||||
group_id?: string;
|
||||
state?: string;
|
||||
}): Promise<PageResult<MessageSummary>> {
|
||||
return run(() => mockApi.listMessages(q));
|
||||
return run(() =>
|
||||
requestAdmin<PageResult<MessageSummary>>(
|
||||
`/api/admin/messages${buildQuery({
|
||||
cursor: q.cursor,
|
||||
limit: q.limit,
|
||||
sender_id: q.sender_id,
|
||||
endpoint_id: q.endpoint_id,
|
||||
group_id: q.group_id,
|
||||
state: q.state,
|
||||
})}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function getMessage(seq: number): Promise<MessageDetail> {
|
||||
return run(() => mockApi.getMessage(seq));
|
||||
return run(() => requestAdmin<MessageDetail>(`/api/admin/messages/${seq}`));
|
||||
}
|
||||
|
||||
export function getSettings(): Promise<RuntimeSettings> {
|
||||
return run(() => mockApi.getSettings());
|
||||
return run(() => requestAdmin<RuntimeSettings>("/api/admin/settings"));
|
||||
}
|
||||
|
||||
export type { MeInfo };
|
||||
|
||||
+4
-4
@@ -98,7 +98,7 @@ const registration: RegistrationSettings = {
|
||||
|
||||
const tokens: ApiToken[] = [
|
||||
{
|
||||
id: 1,
|
||||
id: "1",
|
||||
name: "ops",
|
||||
enabled: true,
|
||||
created_at_ms: now() - 86_400_000,
|
||||
@@ -557,7 +557,7 @@ export const mockApi = {
|
||||
async createToken(name: string): Promise<ApiTokenCreateResult> {
|
||||
requireSession();
|
||||
const item: ApiToken = {
|
||||
id: nextTokenId++,
|
||||
id: String(nextTokenId++),
|
||||
name,
|
||||
enabled: true,
|
||||
created_at_ms: now(),
|
||||
@@ -572,7 +572,7 @@ export const mockApi = {
|
||||
};
|
||||
},
|
||||
|
||||
async patchToken(id: number, body: { name?: string; enabled?: boolean }): Promise<ApiToken> {
|
||||
async patchToken(id: string, body: { name?: string; enabled?: boolean }): Promise<ApiToken> {
|
||||
requireSession();
|
||||
const t = tokens.find((x) => x.id === id);
|
||||
if (!t) {
|
||||
@@ -583,7 +583,7 @@ export const mockApi = {
|
||||
return clone(t);
|
||||
},
|
||||
|
||||
async deleteToken(id: number): Promise<Record<string, never>> {
|
||||
async deleteToken(id: string): Promise<Record<string, never>> {
|
||||
requireSession();
|
||||
const idx = tokens.findIndex((x) => x.id === id);
|
||||
if (idx < 0) {
|
||||
|
||||
@@ -34,6 +34,8 @@ export interface Overview {
|
||||
endpoints_total: number;
|
||||
endpoints_online: number;
|
||||
endpoints_disabled: number;
|
||||
/** A3 补充:自助注册端数量;契约示例未列,前端可选展示 */
|
||||
endpoints_self?: number;
|
||||
groups_total: number;
|
||||
messages_pending: number;
|
||||
messages_scheduled: number;
|
||||
@@ -118,7 +120,8 @@ export interface RegistrationUpdateRequest {
|
||||
}
|
||||
|
||||
export interface ApiToken {
|
||||
id: number;
|
||||
/** 后端为 TEXT 十六进制字符串(A1 偏差);契约示例曾写数字 */
|
||||
id: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
created_at_ms: number;
|
||||
@@ -126,7 +129,7 @@ export interface ApiToken {
|
||||
}
|
||||
|
||||
export interface ApiTokenCreateResult {
|
||||
id: number;
|
||||
id: string;
|
||||
name: string;
|
||||
token: string;
|
||||
created_at_ms: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { config, mount, flushPromises } from "@vue/test-utils";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
NConfigProvider,
|
||||
NDialogProvider,
|
||||
@@ -12,6 +12,8 @@ import { defineComponent, h, nextTick } from "vue";
|
||||
import EndpointsView from "./EndpointsView.vue";
|
||||
import { mockApi } from "@/api/mock";
|
||||
|
||||
vi.mock("@/api/admin", async () => import("@/api/admin-mock"));
|
||||
|
||||
config.global.stubs = { teleport: true };
|
||||
|
||||
function wrap(Comp: object) {
|
||||
|
||||
@@ -230,6 +230,7 @@ async function onTransfer(endpointId: string) {
|
||||
<template #actions>
|
||||
<n-button
|
||||
type="primary"
|
||||
data-testid="group-create-open"
|
||||
@click="
|
||||
Object.assign(createForm, { id: '', name: '', owner_id: '', member_ids: '' });
|
||||
createOpen = true;
|
||||
@@ -267,23 +268,31 @@ async function onTransfer(endpointId: string) {
|
||||
<n-scrollbar style="max-height: 320px">
|
||||
<n-form label-placement="left" label-width="90" :show-feedback="false">
|
||||
<n-form-item label="编号">
|
||||
<n-input v-model:value="createForm.id" placeholder="留空则生成" />
|
||||
<n-input v-model:value="createForm.id" placeholder="留空则生成" data-testid="group-id" />
|
||||
</n-form-item>
|
||||
<n-form-item label="名称">
|
||||
<n-input v-model:value="createForm.name" />
|
||||
<n-input v-model:value="createForm.name" data-testid="group-name" />
|
||||
</n-form-item>
|
||||
<n-form-item label="群主编号">
|
||||
<n-input v-model:value="createForm.owner_id" />
|
||||
<n-input v-model:value="createForm.owner_id" data-testid="group-owner" />
|
||||
</n-form-item>
|
||||
<n-form-item label="成员" label-placement="top">
|
||||
<n-input v-model:value="createForm.member_ids" type="textarea" :rows="3" placeholder="多个编号用逗号或空格分隔" />
|
||||
<n-input
|
||||
v-model:value="createForm.member_ids"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="多个编号用逗号或空格分隔"
|
||||
data-testid="group-members"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-scrollbar>
|
||||
<template #footer>
|
||||
<n-space justify="end">
|
||||
<n-button @click="createOpen = false">取消</n-button>
|
||||
<n-button type="primary" :loading="createLoading" @click="submitCreate">创建</n-button>
|
||||
<n-button type="primary" :loading="createLoading" data-testid="group-create-submit" @click="submitCreate">
|
||||
创建
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { config, mount, flushPromises } from "@vue/test-utils";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { createMemoryHistory, createRouter } from "vue-router";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
NConfigProvider,
|
||||
NDialogProvider,
|
||||
@@ -14,6 +14,8 @@ import LoginView from "./LoginView.vue";
|
||||
import { mockApi } from "@/api/mock";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
vi.mock("@/api/admin", async () => import("@/api/admin-mock"));
|
||||
|
||||
config.global.stubs = { teleport: true };
|
||||
|
||||
function wrap(child: object) {
|
||||
|
||||
@@ -52,9 +52,6 @@ async function onSubmit() {
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-form>
|
||||
<n-text depth="3" style="display: block; margin-top: 12px; font-size: 12px">
|
||||
假数据模式:用户名 admin,密码 adminpassword
|
||||
</n-text>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -71,16 +71,23 @@ async function generateCode() {
|
||||
开放注册
|
||||
<HelpTip>开启后需同时设置安全码,客户端注册时携带。</HelpTip>
|
||||
</template>
|
||||
<n-switch :value="data.enabled" :loading="saving" @update:value="onToggle" />
|
||||
<n-switch
|
||||
:value="data.enabled"
|
||||
:loading="saving"
|
||||
data-testid="reg-enabled"
|
||||
@update:value="onToggle"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="安全码">
|
||||
<n-input v-model:value="codeDraft" placeholder="8–64 字符" />
|
||||
<n-input v-model:value="codeDraft" placeholder="8–64 字符" data-testid="reg-code" />
|
||||
</n-form-item>
|
||||
<n-form-item label="更新时间">
|
||||
<n-text>{{ formatLocalMs(data.updated_at_ms) }}</n-text>
|
||||
</n-form-item>
|
||||
<n-space>
|
||||
<n-button type="primary" :loading="saving" @click="saveCode">保存安全码</n-button>
|
||||
<n-button type="primary" :loading="saving" data-testid="reg-save-code" @click="saveCode">
|
||||
保存安全码
|
||||
</n-button>
|
||||
<n-button :loading="saving" @click="generateCode">生成安全码</n-button>
|
||||
</n-space>
|
||||
</n-form>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { config, mount, flushPromises } from "@vue/test-utils";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
NConfigProvider,
|
||||
NDialogProvider,
|
||||
@@ -12,6 +12,8 @@ import { defineComponent, h, nextTick } from "vue";
|
||||
import TokensView from "./TokensView.vue";
|
||||
import { mockApi } from "@/api/mock";
|
||||
|
||||
vi.mock("@/api/admin", async () => import("@/api/admin-mock"));
|
||||
|
||||
config.global.stubs = { teleport: true };
|
||||
|
||||
function wrap(Comp: object) {
|
||||
|
||||
@@ -40,7 +40,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
const columns = computed<DataTableColumns<ApiToken>>(() => [
|
||||
{ title: "ID", key: "id", width: 60 },
|
||||
{ title: "ID", key: "id", width: 140, ellipsis: { tooltip: true } },
|
||||
{ title: "名称", key: "name", width: 160 },
|
||||
{
|
||||
title: "启用",
|
||||
|
||||
Reference in New Issue
Block a user