feat: add sqlserver admin connection settings
This commit is contained in:
parent
cfb4a0e329
commit
817309ade9
@ -29,6 +29,10 @@ import {
|
||||
DETAIL_AGENT_LOCAL_MOONSHOT_SETTING_KEY,
|
||||
normalizeDetailAgentLocalMoonshotConfig,
|
||||
} from "@/lib/ai/detail-agent-model-config";
|
||||
import {
|
||||
defaultThirdPartySqlServerConfig,
|
||||
THIRD_PARTY_SQLSERVER_CONFIG_KEY,
|
||||
} from "@/lib/sqlserver/config";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
@ -65,6 +69,7 @@ import {
|
||||
Loader2,
|
||||
Check,
|
||||
Paintbrush,
|
||||
Database,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
Dialog,
|
||||
@ -1031,6 +1036,10 @@ export function AdminSettingsClient() {
|
||||
displayName: "官方 Kimi K2.5",
|
||||
isEnabled: true,
|
||||
});
|
||||
const [thirdPartySqlServerConfig, setThirdPartySqlServerConfig] = useState({
|
||||
...defaultThirdPartySqlServerConfig,
|
||||
});
|
||||
const [isTestingThirdPartySqlServer, setIsTestingThirdPartySqlServer] = useState(false);
|
||||
|
||||
// 加载抠图API配置
|
||||
const { data: rmbgConfigData } = useSWR("/api/admin/settings?key=rmbg_api_config", fetcher);
|
||||
@ -1184,6 +1193,39 @@ export function AdminSettingsClient() {
|
||||
}
|
||||
}, [layerSplitConfigData]);
|
||||
|
||||
const { data: thirdPartySqlServerConfigData } = useSWR(
|
||||
`/api/admin/settings?key=${THIRD_PARTY_SQLSERVER_CONFIG_KEY}`,
|
||||
fetcher
|
||||
);
|
||||
useEffect(() => {
|
||||
if (thirdPartySqlServerConfigData?.setting?.value) {
|
||||
const config = thirdPartySqlServerConfigData.setting.value as {
|
||||
enabled?: boolean;
|
||||
host?: string;
|
||||
port?: number;
|
||||
database?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
encrypt?: boolean;
|
||||
trustServerCertificate?: boolean;
|
||||
connectionTimeoutMs?: number;
|
||||
requestTimeoutMs?: number;
|
||||
};
|
||||
setThirdPartySqlServerConfig({
|
||||
enabled: config.enabled !== false,
|
||||
host: config.host || "",
|
||||
port: Math.max(1, Math.min(65535, Number(config.port) || 1433)),
|
||||
database: config.database || "",
|
||||
username: config.username || "",
|
||||
password: config.password || "",
|
||||
encrypt: config.encrypt === true,
|
||||
trustServerCertificate: config.trustServerCertificate !== false,
|
||||
connectionTimeoutMs: Math.max(1000, Math.min(120000, Number(config.connectionTimeoutMs) || 15000)),
|
||||
requestTimeoutMs: Math.max(1000, Math.min(120000, Number(config.requestTimeoutMs) || 30000)),
|
||||
});
|
||||
}
|
||||
}, [thirdPartySqlServerConfigData]);
|
||||
|
||||
// 加载实时画布API配置
|
||||
const { data: realtimeCanvasConfigData } = useSWR(
|
||||
"/api/admin/settings?key=realtime_canvas_config",
|
||||
@ -2337,6 +2379,108 @@ export function AdminSettingsClient() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveThirdPartySqlServerConfig = async () => {
|
||||
try {
|
||||
if (thirdPartySqlServerConfig.enabled) {
|
||||
if (!thirdPartySqlServerConfig.host.trim()) {
|
||||
toast.error("请填写 SQL Server 主机地址");
|
||||
return;
|
||||
}
|
||||
if (!thirdPartySqlServerConfig.database.trim()) {
|
||||
toast.error("请填写数据库名称");
|
||||
return;
|
||||
}
|
||||
if (!thirdPartySqlServerConfig.username.trim()) {
|
||||
toast.error("请填写数据库账号");
|
||||
return;
|
||||
}
|
||||
if (!thirdPartySqlServerConfig.password.trim()) {
|
||||
toast.error("请填写数据库密码");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch("/api/admin/settings", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
key: THIRD_PARTY_SQLSERVER_CONFIG_KEY,
|
||||
value: {
|
||||
enabled: thirdPartySqlServerConfig.enabled,
|
||||
host: thirdPartySqlServerConfig.host.trim(),
|
||||
port: Math.max(1, Math.min(65535, Number(thirdPartySqlServerConfig.port) || 1433)),
|
||||
database: thirdPartySqlServerConfig.database.trim(),
|
||||
username: thirdPartySqlServerConfig.username.trim(),
|
||||
password: thirdPartySqlServerConfig.password.trim(),
|
||||
encrypt: thirdPartySqlServerConfig.encrypt,
|
||||
trustServerCertificate: thirdPartySqlServerConfig.trustServerCertificate,
|
||||
connectionTimeoutMs: Math.max(
|
||||
1000,
|
||||
Math.min(120000, Number(thirdPartySqlServerConfig.connectionTimeoutMs) || 15000)
|
||||
),
|
||||
requestTimeoutMs: Math.max(
|
||||
1000,
|
||||
Math.min(120000, Number(thirdPartySqlServerConfig.requestTimeoutMs) || 30000)
|
||||
),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = await response.json().catch(() => null);
|
||||
if (!response.ok) {
|
||||
throw new Error(payload?.cause || payload?.message || payload?.error || "保存失败");
|
||||
}
|
||||
|
||||
toast.success("SQL Server 配置保存成功");
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "保存 SQL Server 配置失败");
|
||||
}
|
||||
};
|
||||
|
||||
const handleTestThirdPartySqlServerConfig = async () => {
|
||||
try {
|
||||
setIsTestingThirdPartySqlServer(true);
|
||||
|
||||
const response = await fetch("/api/admin/sqlserver/test", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
config: {
|
||||
enabled: thirdPartySqlServerConfig.enabled,
|
||||
host: thirdPartySqlServerConfig.host.trim(),
|
||||
port: Math.max(1, Math.min(65535, Number(thirdPartySqlServerConfig.port) || 1433)),
|
||||
database: thirdPartySqlServerConfig.database.trim(),
|
||||
username: thirdPartySqlServerConfig.username.trim(),
|
||||
password: thirdPartySqlServerConfig.password.trim(),
|
||||
encrypt: thirdPartySqlServerConfig.encrypt,
|
||||
trustServerCertificate: thirdPartySqlServerConfig.trustServerCertificate,
|
||||
connectionTimeoutMs: Math.max(
|
||||
1000,
|
||||
Math.min(120000, Number(thirdPartySqlServerConfig.connectionTimeoutMs) || 15000)
|
||||
),
|
||||
requestTimeoutMs: Math.max(
|
||||
1000,
|
||||
Math.min(120000, Number(thirdPartySqlServerConfig.requestTimeoutMs) || 30000)
|
||||
),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = await response.json().catch(() => null);
|
||||
if (!response.ok) {
|
||||
throw new Error(payload?.cause || payload?.message || payload?.error || "连接测试失败");
|
||||
}
|
||||
|
||||
toast.success(
|
||||
`连接成功:${payload?.serverName || thirdPartySqlServerConfig.host} / ${payload?.databaseName || thirdPartySqlServerConfig.database}(${payload?.latencyMs || 0}ms)`
|
||||
);
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "SQL Server 连接测试失败");
|
||||
} finally {
|
||||
setIsTestingThirdPartySqlServer(false);
|
||||
}
|
||||
};
|
||||
|
||||
// --- 实时画布API配置逻辑 ---
|
||||
const handleSaveRealtimeCanvasConfig = async () => {
|
||||
try {
|
||||
@ -9450,6 +9594,205 @@ export function AdminSettingsClient() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-xl">
|
||||
<Database className="h-5 w-5" />
|
||||
第三方 SQL Server 数据库连接
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
配置后可供后续业务功能直接访问外部 SQL Server 数据库。当前仅提供配置保存与连通性测试。
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6 pt-6">
|
||||
<div className="flex items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base">启用第三方数据库连接</Label>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
关闭后保留配置但不作为后续功能默认外部数据源。
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={thirdPartySqlServerConfig.enabled}
|
||||
onCheckedChange={(checked) =>
|
||||
setThirdPartySqlServerConfig((prev) => ({ ...prev, enabled: checked }))
|
||||
}
|
||||
/>
|
||||
<span className="text-sm font-medium text-muted-foreground min-w-[3rem]">
|
||||
{thirdPartySqlServerConfig.enabled ? "已开启" : "已关闭"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sqlserverHost">主机地址</Label>
|
||||
<Input
|
||||
id="sqlserverHost"
|
||||
value={thirdPartySqlServerConfig.host}
|
||||
onChange={(e) =>
|
||||
setThirdPartySqlServerConfig((prev) => ({ ...prev, host: e.target.value }))
|
||||
}
|
||||
placeholder="218.93.191.16"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sqlserverPort">端口</Label>
|
||||
<Input
|
||||
id="sqlserverPort"
|
||||
type="number"
|
||||
min={1}
|
||||
max={65535}
|
||||
value={thirdPartySqlServerConfig.port}
|
||||
onChange={(e) =>
|
||||
setThirdPartySqlServerConfig((prev) => ({
|
||||
...prev,
|
||||
port: Math.max(1, Math.min(65535, Number(e.target.value) || 1433)),
|
||||
}))
|
||||
}
|
||||
placeholder="1433"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sqlserverDatabase">数据库名称</Label>
|
||||
<Input
|
||||
id="sqlserverDatabase"
|
||||
value={thirdPartySqlServerConfig.database}
|
||||
onChange={(e) =>
|
||||
setThirdPartySqlServerConfig((prev) => ({ ...prev, database: e.target.value }))
|
||||
}
|
||||
placeholder="JSDataMiddlePlatform"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sqlserverUsername">账号</Label>
|
||||
<Input
|
||||
id="sqlserverUsername"
|
||||
value={thirdPartySqlServerConfig.username}
|
||||
onChange={(e) =>
|
||||
setThirdPartySqlServerConfig((prev) => ({ ...prev, username: e.target.value }))
|
||||
}
|
||||
placeholder="nanting"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sqlserverPassword">密码</Label>
|
||||
<Input
|
||||
id="sqlserverPassword"
|
||||
type="password"
|
||||
value={thirdPartySqlServerConfig.password}
|
||||
onChange={(e) =>
|
||||
setThirdPartySqlServerConfig((prev) => ({ ...prev, password: e.target.value }))
|
||||
}
|
||||
placeholder="请输入 SQL Server 密码"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sqlserverConnectionTimeout">连接超时(毫秒)</Label>
|
||||
<Input
|
||||
id="sqlserverConnectionTimeout"
|
||||
type="number"
|
||||
min={1000}
|
||||
max={120000}
|
||||
step={1000}
|
||||
value={thirdPartySqlServerConfig.connectionTimeoutMs}
|
||||
onChange={(e) =>
|
||||
setThirdPartySqlServerConfig((prev) => ({
|
||||
...prev,
|
||||
connectionTimeoutMs: Math.max(
|
||||
1000,
|
||||
Math.min(120000, Number(e.target.value) || 15000)
|
||||
),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sqlserverRequestTimeout">请求超时(毫秒)</Label>
|
||||
<Input
|
||||
id="sqlserverRequestTimeout"
|
||||
type="number"
|
||||
min={1000}
|
||||
max={120000}
|
||||
step={1000}
|
||||
value={thirdPartySqlServerConfig.requestTimeoutMs}
|
||||
onChange={(e) =>
|
||||
setThirdPartySqlServerConfig((prev) => ({
|
||||
...prev,
|
||||
requestTimeoutMs: Math.max(
|
||||
1000,
|
||||
Math.min(120000, Number(e.target.value) || 30000)
|
||||
),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="flex items-center justify-between rounded-lg border p-3">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-sm">启用加密</Label>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
现代云数据库通常需要开启;传统内网 SQL Server 可关闭。
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={thirdPartySqlServerConfig.encrypt}
|
||||
onCheckedChange={(checked) =>
|
||||
setThirdPartySqlServerConfig((prev) => ({ ...prev, encrypt: checked }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between rounded-lg border p-3">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-sm">信任服务器证书</Label>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
对自签名证书或传统 SQL Server 更友好。
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={thirdPartySqlServerConfig.trustServerCertificate}
|
||||
onCheckedChange={(checked) =>
|
||||
setThirdPartySqlServerConfig((prev) => ({
|
||||
...prev,
|
||||
trustServerCertificate: checked,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4 space-y-1 text-xs text-blue-800">
|
||||
<p>• 当前支持 SQL Server 标准 TCP 连接。</p>
|
||||
<p>• 配置会保存到系统设置,供后续外部数据查询/同步功能复用。</p>
|
||||
<p>• 测试连接会尝试建立连接并执行基础查询,不会修改外部数据库数据。</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap justify-end gap-3">
|
||||
<Button variant="outline" onClick={handleTestThirdPartySqlServerConfig} disabled={isTestingThirdPartySqlServer}>
|
||||
{isTestingThirdPartySqlServer ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Database className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
测试连接
|
||||
</Button>
|
||||
<Button onClick={handleSaveThirdPartySqlServerConfig}>
|
||||
<Save className="mr-2 h-4 w-4" /> 保存 SQL Server 配置
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 实时画布API配置 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
||||
@ -13,6 +13,10 @@ import {
|
||||
resolveRealtimeProviders,
|
||||
} from "@/lib/realtime/config";
|
||||
import { DETAIL_AGENT_LOCAL_MOONSHOT_SETTING_KEY } from "@/lib/ai/detail-agent-model-config";
|
||||
import {
|
||||
normalizeThirdPartySqlServerConfig,
|
||||
THIRD_PARTY_SQLSERVER_CONFIG_KEY,
|
||||
} from "@/lib/sqlserver/config";
|
||||
|
||||
const settingSchema = z.object({
|
||||
key: z.string(),
|
||||
@ -166,6 +170,37 @@ export async function POST(request: Request) {
|
||||
nextValue = normalizedValue;
|
||||
}
|
||||
|
||||
if (key === THIRD_PARTY_SQLSERVER_CONFIG_KEY) {
|
||||
const existingSetting = await getSystemSetting({
|
||||
key: THIRD_PARTY_SQLSERVER_CONFIG_KEY,
|
||||
}).catch(() => null);
|
||||
const existingValue =
|
||||
existingSetting?.value && typeof existingSetting.value === "object"
|
||||
? (existingSetting.value as Record<string, unknown>)
|
||||
: {};
|
||||
const normalizedValue = normalizeThirdPartySqlServerConfig(
|
||||
value,
|
||||
String(existingValue.password || "").trim(),
|
||||
);
|
||||
|
||||
if (normalizedValue.enabled) {
|
||||
if (!normalizedValue.host) {
|
||||
return new ChatSDKError("bad_request:api", "请填写 SQL Server 主机地址").toResponse();
|
||||
}
|
||||
if (!normalizedValue.database) {
|
||||
return new ChatSDKError("bad_request:api", "请填写数据库名称").toResponse();
|
||||
}
|
||||
if (!normalizedValue.username) {
|
||||
return new ChatSDKError("bad_request:api", "请填写数据库账号").toResponse();
|
||||
}
|
||||
if (!normalizedValue.password) {
|
||||
return new ChatSDKError("bad_request:api", "请填写数据库密码").toResponse();
|
||||
}
|
||||
}
|
||||
|
||||
nextValue = normalizedValue;
|
||||
}
|
||||
|
||||
const setting = await upsertSystemSetting({
|
||||
key,
|
||||
value: nextValue,
|
||||
|
||||
50
app/api/admin/sqlserver/test/route.ts
Normal file
50
app/api/admin/sqlserver/test/route.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { z } from "zod";
|
||||
import { requireAdmin } from "@/lib/middleware/admin";
|
||||
import { getSystemSetting } from "@/lib/db/queries";
|
||||
import { ChatSDKError } from "@/lib/errors";
|
||||
import {
|
||||
THIRD_PARTY_SQLSERVER_CONFIG_KEY,
|
||||
thirdPartySqlServerConfigSchema,
|
||||
} from "@/lib/sqlserver/config";
|
||||
import { testSqlServerConnection } from "@/lib/sqlserver/client";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const requestSchema = z.object({
|
||||
config: thirdPartySqlServerConfigSchema.optional(),
|
||||
});
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
await requireAdmin();
|
||||
|
||||
const body = requestSchema.parse(await request.json().catch(() => ({})));
|
||||
const savedSetting = !body.config
|
||||
? await getSystemSetting({ key: THIRD_PARTY_SQLSERVER_CONFIG_KEY }).catch(() => null)
|
||||
: null;
|
||||
const config = body.config ?? savedSetting?.value ?? {};
|
||||
|
||||
const result = await testSqlServerConnection(config);
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
...result,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof ChatSDKError) {
|
||||
return error.toResponse();
|
||||
}
|
||||
|
||||
if (error instanceof z.ZodError) {
|
||||
return new ChatSDKError("bad_request:api", error.message).toResponse();
|
||||
}
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
code: "bad_request:api",
|
||||
message: error instanceof Error ? error.message : "SQL Server 连接测试失败",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
66
lib/sqlserver/client.ts
Normal file
66
lib/sqlserver/client.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import "server-only";
|
||||
|
||||
import sql from "mssql";
|
||||
import {
|
||||
normalizeThirdPartySqlServerConfig,
|
||||
type NormalizedThirdPartySqlServerConfig,
|
||||
} from "@/lib/sqlserver/config";
|
||||
|
||||
function toSqlServerConnectionConfig(config: NormalizedThirdPartySqlServerConfig): sql.config {
|
||||
return {
|
||||
server: config.host,
|
||||
port: config.port,
|
||||
user: config.username,
|
||||
password: config.password,
|
||||
database: config.database,
|
||||
options: {
|
||||
encrypt: config.encrypt,
|
||||
trustServerCertificate: config.trustServerCertificate,
|
||||
},
|
||||
connectionTimeout: config.connectionTimeoutMs,
|
||||
requestTimeout: config.requestTimeoutMs,
|
||||
pool: {
|
||||
max: 1,
|
||||
min: 0,
|
||||
idleTimeoutMillis: 5000,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function testSqlServerConnection(configValue: unknown) {
|
||||
const config = normalizeThirdPartySqlServerConfig(configValue);
|
||||
|
||||
if (!config.host) {
|
||||
throw new Error("请填写 SQL Server 主机地址");
|
||||
}
|
||||
if (!config.database) {
|
||||
throw new Error("请填写数据库名称");
|
||||
}
|
||||
if (!config.username) {
|
||||
throw new Error("请填写数据库账号");
|
||||
}
|
||||
if (!config.password) {
|
||||
throw new Error("请填写数据库密码");
|
||||
}
|
||||
|
||||
const startedAt = Date.now();
|
||||
const pool = new sql.ConnectionPool(toSqlServerConnectionConfig(config));
|
||||
|
||||
try {
|
||||
await pool.connect();
|
||||
const result = await pool
|
||||
.request()
|
||||
.query("SELECT @@SERVERNAME AS serverName, DB_NAME() AS databaseName, @@VERSION AS version");
|
||||
|
||||
const row = result.recordset?.[0] || {};
|
||||
|
||||
return {
|
||||
latencyMs: Date.now() - startedAt,
|
||||
serverName: String(row.serverName || config.host),
|
||||
databaseName: String(row.databaseName || config.database),
|
||||
version: String(row.version || "").trim(),
|
||||
};
|
||||
} finally {
|
||||
await pool.close().catch(() => undefined);
|
||||
}
|
||||
}
|
||||
66
lib/sqlserver/config.ts
Normal file
66
lib/sqlserver/config.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const THIRD_PARTY_SQLSERVER_CONFIG_KEY = "third_party_sqlserver_config";
|
||||
|
||||
export const thirdPartySqlServerConfigSchema = z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
host: z.string().trim().optional(),
|
||||
port: z.coerce.number().int().min(1).max(65535).optional(),
|
||||
database: z.string().trim().optional(),
|
||||
username: z.string().trim().optional(),
|
||||
password: z.string().optional(),
|
||||
encrypt: z.boolean().optional(),
|
||||
trustServerCertificate: z.boolean().optional(),
|
||||
connectionTimeoutMs: z.coerce.number().int().min(1000).max(120000).optional(),
|
||||
requestTimeoutMs: z.coerce.number().int().min(1000).max(120000).optional(),
|
||||
});
|
||||
|
||||
export type ThirdPartySqlServerConfig = z.infer<typeof thirdPartySqlServerConfigSchema>;
|
||||
|
||||
export type NormalizedThirdPartySqlServerConfig = {
|
||||
enabled: boolean;
|
||||
host: string;
|
||||
port: number;
|
||||
database: string;
|
||||
username: string;
|
||||
password: string;
|
||||
encrypt: boolean;
|
||||
trustServerCertificate: boolean;
|
||||
connectionTimeoutMs: number;
|
||||
requestTimeoutMs: number;
|
||||
};
|
||||
|
||||
export const defaultThirdPartySqlServerConfig: NormalizedThirdPartySqlServerConfig = {
|
||||
enabled: true,
|
||||
host: "",
|
||||
port: 1433,
|
||||
database: "",
|
||||
username: "",
|
||||
password: "",
|
||||
encrypt: false,
|
||||
trustServerCertificate: true,
|
||||
connectionTimeoutMs: 15000,
|
||||
requestTimeoutMs: 30000,
|
||||
};
|
||||
|
||||
export function normalizeThirdPartySqlServerConfig(
|
||||
value: unknown,
|
||||
existingPassword = "",
|
||||
): NormalizedThirdPartySqlServerConfig {
|
||||
const parsed = thirdPartySqlServerConfigSchema.parse(value || {});
|
||||
|
||||
return {
|
||||
enabled: parsed.enabled !== false,
|
||||
host: String(parsed.host || "").trim(),
|
||||
port: Number(parsed.port) || defaultThirdPartySqlServerConfig.port,
|
||||
database: String(parsed.database || "").trim(),
|
||||
username: String(parsed.username || "").trim(),
|
||||
password: String(parsed.password || "").trim() || String(existingPassword || "").trim(),
|
||||
encrypt: parsed.encrypt === true,
|
||||
trustServerCertificate: parsed.trustServerCertificate !== false,
|
||||
connectionTimeoutMs:
|
||||
Number(parsed.connectionTimeoutMs) || defaultThirdPartySqlServerConfig.connectionTimeoutMs,
|
||||
requestTimeoutMs:
|
||||
Number(parsed.requestTimeoutMs) || defaultThirdPartySqlServerConfig.requestTimeoutMs,
|
||||
};
|
||||
}
|
||||
@ -75,6 +75,7 @@
|
||||
"katex": "^0.16.25",
|
||||
"lucide-react": "^0.446.0",
|
||||
"matter-js": "^0.20.0",
|
||||
"mssql": "^12.2.1",
|
||||
"nanoid": "^5.0.8",
|
||||
"next": "^16.0.9",
|
||||
"next-auth": "^5.0.0-beta.30",
|
||||
@ -121,6 +122,7 @@
|
||||
"@tailwindcss/postcss": "^4.1.13",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@types/d3-scale": "^4.0.8",
|
||||
"@types/mssql": "^9.1.9",
|
||||
"@types/node": "^22.8.6",
|
||||
"@types/papaparse": "^5.3.15",
|
||||
"@types/pdf-parse": "^1.1.4",
|
||||
|
||||
583
pnpm-lock.yaml
generated
583
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user