feat: add agent admin settings access
This commit is contained in:
parent
57b784f542
commit
ea5a189ae0
@ -13,6 +13,12 @@ export default async function AdminLayout({
|
||||
getNavLinks(),
|
||||
getTutorialVideos(),
|
||||
]);
|
||||
const settingsHref =
|
||||
session?.user?.type === "admin"
|
||||
? "/settings"
|
||||
: session?.user?.type === "agent_admin"
|
||||
? "/settings/agent-workflow"
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col bg-background">
|
||||
@ -20,7 +26,8 @@ export default async function AdminLayout({
|
||||
siteName={siteSettings.siteName}
|
||||
siteLogo={siteSettings.siteLogo || undefined}
|
||||
siteLogoDark={siteSettings.siteLogoDark || undefined}
|
||||
isAdmin={session?.user?.type === "admin"}
|
||||
isAdmin={session?.user?.type === "admin" || session?.user?.type === "agent_admin"}
|
||||
settingsHref={settingsHref}
|
||||
customLinks={navLinks.map((link) => ({
|
||||
id: link.id,
|
||||
name: link.name,
|
||||
@ -47,4 +54,3 @@ export default async function AdminLayout({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -128,7 +128,7 @@ type User = {
|
||||
email: string;
|
||||
name: string | null;
|
||||
department: string | null;
|
||||
role: "guest" | "regular" | "admin";
|
||||
role: "guest" | "regular" | "admin" | "agent_admin";
|
||||
isHighVip: boolean;
|
||||
isActive: boolean;
|
||||
monthlyImageLimit: number | null;
|
||||
@ -141,6 +141,7 @@ const ROLE_LABELS: Record<User["role"], string> = {
|
||||
guest: "访客用户",
|
||||
regular: "普通用户",
|
||||
admin: "管理员",
|
||||
agent_admin: "智能体管理员",
|
||||
};
|
||||
|
||||
type ToolConfig = {
|
||||
@ -3172,6 +3173,7 @@ export function AdminSettingsClient() {
|
||||
<SelectItem value="guest">访客</SelectItem>
|
||||
<SelectItem value="regular">普通</SelectItem>
|
||||
<SelectItem value="admin">管理员</SelectItem>
|
||||
<SelectItem value="agent_admin">智能体管理员</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
|
||||
@ -119,7 +119,15 @@ function createWorkflowStepDraft(
|
||||
};
|
||||
}
|
||||
|
||||
export function AgentWorkflowSettingsClient() {
|
||||
interface AgentWorkflowSettingsClientProps {
|
||||
backHref?: string;
|
||||
backLabel?: string;
|
||||
}
|
||||
|
||||
export function AgentWorkflowSettingsClient({
|
||||
backHref = "/settings",
|
||||
backLabel = "返回设置",
|
||||
}: AgentWorkflowSettingsClientProps = {}) {
|
||||
const { data, isLoading, mutate } = useSWR<{
|
||||
agentWorkflowUiConfig?: AgentWorkflowUiConfig;
|
||||
agentWorkflowMaterialApiConfig?: AgentWorkflowMaterialApiConfig;
|
||||
@ -403,9 +411,9 @@ export function AgentWorkflowSettingsClient() {
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button asChild variant="outline">
|
||||
<Link href="/settings">
|
||||
<Link href={backHref}>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
返回设置
|
||||
{backLabel}
|
||||
</Link>
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={isSaving}>
|
||||
|
||||
@ -1,15 +1,19 @@
|
||||
import { requireAdmin } from "@/lib/middleware/admin";
|
||||
import { requireAgentWorkflowAdmin } from "@/lib/middleware/admin";
|
||||
import { redirect } from "next/navigation";
|
||||
import { AgentWorkflowSettingsClient } from "./agent-workflow-settings-client";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AgentWorkflowSettingsPage() {
|
||||
try {
|
||||
await requireAdmin();
|
||||
} catch {
|
||||
const session = await requireAgentWorkflowAdmin().catch(() => null);
|
||||
if (!session) {
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
return <AgentWorkflowSettingsClient />;
|
||||
return (
|
||||
<AgentWorkflowSettingsClient
|
||||
backHref={session.user.type === "admin" ? "/settings" : "/"}
|
||||
backLabel={session.user.type === "admin" ? "返回设置" : "返回首页"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import { requireAdmin } from "@/lib/middleware/admin";
|
||||
import { redirect } from "next/navigation";
|
||||
import { AdminSettingsClient } from "./admin-settings-client";
|
||||
@ -6,6 +7,11 @@ import { AdminSettingsClient } from "./admin-settings-client";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminSettingsPage() {
|
||||
const session = await auth().catch(() => null);
|
||||
if (session?.user?.type === "agent_admin") {
|
||||
redirect("/settings/agent-workflow");
|
||||
}
|
||||
|
||||
try {
|
||||
await requireAdmin();
|
||||
} catch {
|
||||
@ -14,4 +20,3 @@ export default async function AdminSettingsPage() {
|
||||
|
||||
return <AdminSettingsClient />;
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import { db } from "@/lib/db";
|
||||
import { loginToken, user as userTable } from "@/lib/db/schema";
|
||||
import { eq, and, gt } from "drizzle-orm";
|
||||
|
||||
export type UserType = "guest" | "regular" | "admin";
|
||||
export type UserType = "guest" | "regular" | "admin" | "agent_admin";
|
||||
|
||||
declare module "next-auth" {
|
||||
interface Session extends DefaultSession {
|
||||
|
||||
@ -40,6 +40,12 @@ async function SidebarWrapper({ children }: { children: React.ReactNode }) {
|
||||
const isCollapsed = cookieStore.get("sidebar_state")?.value !== "true";
|
||||
const homeVariant =
|
||||
homeVariantSetting?.value === "lovart" ? "lovart" : "classic";
|
||||
const settingsHref =
|
||||
session?.user?.type === "admin"
|
||||
? "/settings"
|
||||
: session?.user?.type === "agent_admin"
|
||||
? "/settings/agent-workflow"
|
||||
: null;
|
||||
|
||||
return (
|
||||
<ChatShell
|
||||
@ -49,7 +55,8 @@ async function SidebarWrapper({ children }: { children: React.ReactNode }) {
|
||||
siteName={siteSettings.siteName}
|
||||
siteLogo={siteSettings.siteLogo || undefined}
|
||||
siteLogoDark={siteSettings.siteLogoDark || undefined}
|
||||
isAdmin={session?.user?.type === "admin"}
|
||||
isAdmin={session?.user?.type === "admin" || session?.user?.type === "agent_admin"}
|
||||
settingsHref={settingsHref}
|
||||
customLinks={navLinks.map((link) => ({
|
||||
id: link.id,
|
||||
name: link.name,
|
||||
|
||||
@ -10,7 +10,7 @@ import { ChatSDKError } from "@/lib/errors";
|
||||
|
||||
const updateUserSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
role: z.enum(["guest", "regular", "admin"]).optional(),
|
||||
role: z.enum(["guest", "regular", "admin", "agent_admin"]).optional(),
|
||||
isHighVip: z.boolean().optional(),
|
||||
isActive: z.boolean().optional(),
|
||||
email: z.string().email().optional(),
|
||||
|
||||
@ -4,6 +4,7 @@ import { db } from "@/lib/db";
|
||||
import { siteSettings, systemSettings } from "@/lib/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import { isAgentWorkflowAdminType, isSiteAdminType } from "@/lib/middleware/admin";
|
||||
import {
|
||||
defaultMembershipConfig,
|
||||
defaultMembershipUserOverrides,
|
||||
@ -209,7 +210,8 @@ function normalizeAgentCategoryTreeConfig(value: unknown): AgentCategoryTreeConf
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await auth().catch(() => null);
|
||||
const isAdmin = session?.user?.type === "admin";
|
||||
const isAdmin = isSiteAdminType(session?.user?.type);
|
||||
const canManageAgentWorkflow = isAgentWorkflowAdminType(session?.user?.type);
|
||||
const [
|
||||
settings,
|
||||
notificationSetting,
|
||||
@ -405,7 +407,7 @@ export async function GET() {
|
||||
const agentWorkflowUiConfig = normalizeAgentWorkflowUiConfig(
|
||||
agentWorkflowUiConfigSetting?.value
|
||||
);
|
||||
const agentWorkflowMaterialApiConfig = isAdmin
|
||||
const agentWorkflowMaterialApiConfig = canManageAgentWorkflow
|
||||
? normalizeAgentWorkflowMaterialApiConfig(agentWorkflowMaterialApiConfigSetting?.value)
|
||||
: normalizeAgentWorkflowMaterialApiConfigForPublic(
|
||||
agentWorkflowMaterialApiConfigSetting?.value
|
||||
@ -669,7 +671,7 @@ const updateSchema = z.object({
|
||||
.optional(),
|
||||
});
|
||||
|
||||
// PUT /api/site-settings - 更新网站设置(仅管理员)
|
||||
// PUT /api/site-settings - 更新网站设置(管理员 / 智能体管理员)
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth();
|
||||
@ -678,11 +680,25 @@ export async function PUT(request: NextRequest) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
if (session.user.type !== "admin") {
|
||||
const isAdmin = isSiteAdminType(session.user.type);
|
||||
const canManageAgentWorkflow = isAgentWorkflowAdminType(session.user.type);
|
||||
|
||||
if (!canManageAgentWorkflow) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
if (!isAdmin) {
|
||||
const allowedKeys = new Set([
|
||||
"agentWorkflowUiConfig",
|
||||
"agentWorkflowMaterialApiConfig",
|
||||
"agentWorkflowBrandModelConfig",
|
||||
]);
|
||||
const hasRestrictedKeys = Object.keys(body || {}).some((key) => !allowedKeys.has(key));
|
||||
if (hasRestrictedKeys) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
}
|
||||
const data = updateSchema.parse(body);
|
||||
const {
|
||||
homeVariant,
|
||||
|
||||
@ -32,6 +32,7 @@ type ChatShellProps = {
|
||||
siteLogo?: string;
|
||||
siteLogoDark?: string;
|
||||
isAdmin: boolean;
|
||||
settingsHref?: string | null;
|
||||
customLinks: CustomLink[];
|
||||
toolbox1Name: string;
|
||||
toolbox1Enabled: boolean;
|
||||
@ -52,6 +53,7 @@ export function ChatShell({
|
||||
siteLogo,
|
||||
siteLogoDark,
|
||||
isAdmin,
|
||||
settingsHref = null,
|
||||
customLinks,
|
||||
toolbox1Name,
|
||||
toolbox1Enabled,
|
||||
@ -76,6 +78,7 @@ export function ChatShell({
|
||||
siteLogo={siteLogo}
|
||||
siteLogoDark={siteLogoDark}
|
||||
isAdmin={isAdmin}
|
||||
settingsHref={settingsHref}
|
||||
customLinks={customLinks}
|
||||
toolbox1Name={toolbox1Name}
|
||||
toolbox1Enabled={toolbox1Enabled}
|
||||
|
||||
@ -1599,13 +1599,15 @@ export function LovartHome({
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
) : null}
|
||||
{user?.type === "admin" ? (
|
||||
{user?.type === "admin" || user?.type === "agent_admin" ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full hover:bg-black/5"
|
||||
onClick={() => router.push("/settings")}
|
||||
onClick={() =>
|
||||
router.push(user?.type === "agent_admin" ? "/settings/agent-workflow" : "/settings")
|
||||
}
|
||||
title="系统设置"
|
||||
>
|
||||
<Settings className="h-[18px] w-[18px]" />
|
||||
|
||||
@ -48,6 +48,7 @@ type TopNavbarProps = {
|
||||
siteLogo?: string;
|
||||
siteLogoDark?: string;
|
||||
isAdmin?: boolean;
|
||||
settingsHref?: string | null;
|
||||
customLinks?: CustomLink[];
|
||||
toolbox1Name?: string;
|
||||
toolbox1Enabled?: boolean;
|
||||
@ -64,6 +65,7 @@ export function TopNavbar({
|
||||
siteLogo,
|
||||
siteLogoDark,
|
||||
isAdmin = false,
|
||||
settingsHref = null,
|
||||
customLinks = [],
|
||||
toolbox1Name = "工具箱",
|
||||
toolbox1Enabled = true,
|
||||
@ -490,16 +492,16 @@ export function TopNavbar({
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* 系统设置(仅管理员可见)*/}
|
||||
{isAdmin && (
|
||||
{/* 系统设置(管理员 / 智能体管理员可见)*/}
|
||||
{(isAdmin || settingsHref) && (
|
||||
<div className="pb-4">
|
||||
<a
|
||||
href="/settings"
|
||||
href={settingsHref || "/settings"}
|
||||
className={navItemBaseClass}
|
||||
title="系统设置"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.location.href = "/settings";
|
||||
window.location.href = settingsHref || "/settings";
|
||||
}}
|
||||
>
|
||||
<Settings className="h-5 w-5" />
|
||||
|
||||
@ -1587,7 +1587,7 @@ export async function updateUser({
|
||||
}: {
|
||||
id: string;
|
||||
email?: string;
|
||||
role?: "guest" | "regular" | "admin";
|
||||
role?: "guest" | "regular" | "admin" | "agent_admin";
|
||||
isHighVip?: boolean;
|
||||
isActive?: boolean;
|
||||
monthlyImageLimit?: number | null;
|
||||
|
||||
@ -21,7 +21,7 @@ export const user = pgTable("User", {
|
||||
name: varchar("name", { length: 100 }), // 用户姓名(用于显示)
|
||||
department: varchar("department", { length: 256 }), // 部门信息
|
||||
dingtalkUserId: varchar("dingtalkUserId", { length: 128 }), // 钉钉用户ID
|
||||
role: varchar("role", { enum: ["guest", "regular", "admin"] })
|
||||
role: varchar("role", { enum: ["guest", "regular", "admin", "agent_admin"] })
|
||||
.notNull()
|
||||
.default("regular"),
|
||||
isHighVip: boolean("isHighVip").notNull().default(false),
|
||||
|
||||
@ -1,6 +1,14 @@
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import { ChatSDKError } from "@/lib/errors";
|
||||
|
||||
export function isSiteAdminType(type?: string | null) {
|
||||
return type === "admin";
|
||||
}
|
||||
|
||||
export function isAgentWorkflowAdminType(type?: string | null) {
|
||||
return type === "admin" || type === "agent_admin";
|
||||
}
|
||||
|
||||
export async function requireAdmin() {
|
||||
const session = await auth();
|
||||
|
||||
@ -8,10 +16,23 @@ export async function requireAdmin() {
|
||||
throw new ChatSDKError("unauthorized:admin");
|
||||
}
|
||||
|
||||
if (session.user.type !== "admin") {
|
||||
if (!isSiteAdminType(session.user.type)) {
|
||||
throw new ChatSDKError("forbidden:admin");
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function requireAgentWorkflowAdmin() {
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
throw new ChatSDKError("unauthorized:admin");
|
||||
}
|
||||
|
||||
if (!isAgentWorkflowAdminType(session.user.type)) {
|
||||
throw new ChatSDKError("forbidden:admin");
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user