mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-02 01:09:03 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 774a0730b9 |
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM node:24.18.1-alpine
|
||||
FROM node:26.8.1-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ experimental:
|
||||
version: "{{.BadgerVersion}}"
|
||||
crowdsec: # CrowdSec plugin configuration added
|
||||
moduleName: "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||
version: "v1.7.1"
|
||||
version: "v1.4.4"
|
||||
|
||||
log:
|
||||
level: "INFO"
|
||||
|
||||
+1
-8
@@ -1176,10 +1176,6 @@
|
||||
"idpJmespathAboutDescriptionLink": "Learn more about JMESPath",
|
||||
"idpJmespathLabel": "Identifier Path",
|
||||
"idpJmespathLabelDescription": "The path to the user identifier in the ID token",
|
||||
"idpIdentifierChangeTitle": "Identifier Path Change Warning",
|
||||
"idpIdentifierChangeDescription": "You are about to change the identifier path. This will affect how existing users are mapped. Users who previously signed in through this identity provider may no longer be recognized as the same users.",
|
||||
"idpIdentifierChangeConfirmMessage": "I confirm",
|
||||
"idpIdentifierChangeWarningText": "This will affect how existing users are mapped",
|
||||
"idpJmespathEmailPathOptional": "Email Path (Optional)",
|
||||
"idpJmespathEmailPathOptionalDescription": "The path to the user's email in the ID token",
|
||||
"idpJmespathNamePathOptional": "Name Path (Optional)",
|
||||
@@ -1577,8 +1573,6 @@
|
||||
"search": "Search…",
|
||||
"searchPlaceholder": "Search...",
|
||||
"emptySearchOptions": "No options found",
|
||||
"ipFilterSearchPlaceholder": "Enter an IP address…",
|
||||
"ipFilterEmptyMessage": "Enter an IP address to filter by",
|
||||
"create": "Create",
|
||||
"orgs": "Organizations",
|
||||
"loginError": "An unexpected error occurred. Please try again.",
|
||||
@@ -2602,7 +2596,6 @@
|
||||
"createDomainType": "Type:",
|
||||
"createDomainName": "Name:",
|
||||
"createDomainValue": "Value:",
|
||||
"multiSelectFilterCount": "{count} selected",
|
||||
"createDomainCnameRecords": "CNAME Records",
|
||||
"createDomainARecords": "A Records",
|
||||
"createDomainRecordNumber": "Record {number}",
|
||||
@@ -3000,7 +2993,7 @@
|
||||
"remoteExitNodeNetworkingSubnetsPlaceholder": "Add a CIDR range (e.g. 10.0.0.0/8)",
|
||||
"remoteExitNodeNetworkingSubnetsLoadError": "Failed to load subnets",
|
||||
"remoteExitNodeNetworkingLabelsTitle": "Preference Labels",
|
||||
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will prefer to connect through this remote exit node.",
|
||||
"remoteExitNodeNetworkingLabelsDescription": "Sites with these labels will be enforced to connect through this remote exit node.",
|
||||
"remoteExitNodeNetworkingLabelsButtonText": "Select labels...",
|
||||
"remoteExitNodeNetworkingLabelsSearchPlaceholder": "Search labels...",
|
||||
"remoteExitNodeNetworkingLabelsLoadError": "Failed to load labels",
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { and, asc, eq, or } from "drizzle-orm";
|
||||
import { Transaction, User, userOrgs, users } from "@server/db";
|
||||
|
||||
export async function findOrgUserByIdentifier(
|
||||
trx: Transaction,
|
||||
orgId: string,
|
||||
identifier: string
|
||||
): Promise<User | null> {
|
||||
const [match] = await trx
|
||||
.select()
|
||||
.from(users)
|
||||
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
|
||||
.where(
|
||||
and(
|
||||
or(eq(users.username, identifier), eq(users.email, identifier)),
|
||||
eq(userOrgs.orgId, orgId)
|
||||
)
|
||||
)
|
||||
.orderBy(asc(users.dateCreated), asc(users.userId))
|
||||
.limit(1);
|
||||
|
||||
return match?.user ?? null;
|
||||
}
|
||||
|
||||
export async function resolveOrgUserIds(
|
||||
trx: Transaction,
|
||||
orgId: string,
|
||||
identifiers: string[]
|
||||
): Promise<string[]> {
|
||||
const userIds = new Set<string>();
|
||||
for (const identifier of identifiers) {
|
||||
const user = await findOrgUserByIdentifier(trx, orgId, identifier);
|
||||
if (user) {
|
||||
userIds.add(user.userId);
|
||||
}
|
||||
}
|
||||
return [...userIds];
|
||||
}
|
||||
@@ -11,14 +11,15 @@ import {
|
||||
siteNetworks,
|
||||
siteResources,
|
||||
Transaction,
|
||||
userOrgs,
|
||||
users,
|
||||
userSiteResources,
|
||||
networks
|
||||
} from "@server/db";
|
||||
import { sites } from "@server/db";
|
||||
import { eq, and, ne, inArray, isNotNull } from "drizzle-orm";
|
||||
import { eq, and, ne, inArray, or, isNotNull } from "drizzle-orm";
|
||||
import { Config } from "./types";
|
||||
import { getOrCreateLabelIds, syncSiteResourceLabels } from "./labels";
|
||||
import { resolveOrgUserIds } from "./findOrgUser";
|
||||
import logger from "@server/logger";
|
||||
import { defaultRoleAllowedActions } from "@server/routers/role/createRole";
|
||||
import { getNextAvailableAliasAddress } from "../ip";
|
||||
@@ -388,22 +389,28 @@ export async function updatePrivateResources(
|
||||
.where(eq(userSiteResources.siteResourceId, siteResourceId));
|
||||
|
||||
if (resourceData.users.length > 0) {
|
||||
const userIds = await resolveOrgUserIds(
|
||||
trx,
|
||||
orgId,
|
||||
resourceData.users
|
||||
);
|
||||
// get userIds from username
|
||||
const usersToUpdate = await trx
|
||||
.select()
|
||||
.from(users)
|
||||
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
|
||||
.where(
|
||||
and(
|
||||
or(
|
||||
inArray(users.username, resourceData.users),
|
||||
inArray(users.email, resourceData.users)
|
||||
),
|
||||
eq(userOrgs.orgId, orgId)
|
||||
)
|
||||
);
|
||||
|
||||
if (userIds.length > 0) {
|
||||
await trx
|
||||
.insert(userSiteResources)
|
||||
.values(
|
||||
userIds.map((userId) => ({
|
||||
userId,
|
||||
siteResourceId
|
||||
}))
|
||||
);
|
||||
}
|
||||
const userIds = usersToUpdate.map((user) => user.user.userId);
|
||||
|
||||
await trx
|
||||
.insert(userSiteResources)
|
||||
.values(
|
||||
userIds.map((userId) => ({ userId, siteResourceId }))
|
||||
);
|
||||
}
|
||||
|
||||
// Get all admin role IDs for this org to exclude from deletion
|
||||
@@ -714,22 +721,28 @@ export async function updatePrivateResources(
|
||||
}
|
||||
|
||||
if (resourceData.users.length > 0) {
|
||||
const userIds = await resolveOrgUserIds(
|
||||
trx,
|
||||
orgId,
|
||||
resourceData.users
|
||||
);
|
||||
// get userIds from username
|
||||
const usersToUpdate = await trx
|
||||
.select()
|
||||
.from(users)
|
||||
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
|
||||
.where(
|
||||
and(
|
||||
or(
|
||||
inArray(users.username, resourceData.users),
|
||||
inArray(users.email, resourceData.users)
|
||||
),
|
||||
eq(userOrgs.orgId, orgId)
|
||||
)
|
||||
);
|
||||
|
||||
if (userIds.length > 0) {
|
||||
await trx
|
||||
.insert(userSiteResources)
|
||||
.values(
|
||||
userIds.map((userId) => ({
|
||||
userId,
|
||||
siteResourceId
|
||||
}))
|
||||
);
|
||||
}
|
||||
const userIds = usersToUpdate.map((user) => user.user.userId);
|
||||
|
||||
await trx
|
||||
.insert(userSiteResources)
|
||||
.values(
|
||||
userIds.map((userId) => ({ userId, siteResourceId }))
|
||||
);
|
||||
}
|
||||
|
||||
if (resourceData.machines.length > 0) {
|
||||
|
||||
@@ -46,12 +46,11 @@ import { encrypt } from "@server/lib/crypto";
|
||||
import logger from "@server/logger";
|
||||
import { defaultRoleAllowedActions } from "@server/routers/role/createRole";
|
||||
import { pickPort } from "@server/routers/target/helpers";
|
||||
import { and, asc, eq, isNotNull, ne } from "drizzle-orm";
|
||||
import { and, asc, eq, isNotNull, ne, or } from "drizzle-orm";
|
||||
import { tierMatrix } from "../billing/tierMatrix";
|
||||
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
|
||||
import { Config, isTargetsOnlyResource, TargetData } from "./types";
|
||||
import { getOrCreateLabelIds, syncResourceLabels } from "./labels";
|
||||
import { findOrgUserByIdentifier } from "./findOrgUser";
|
||||
import { LimitId } from "../billing";
|
||||
import { usageService } from "../billing/usageService";
|
||||
import { syncInferenceAiConfig } from "./aiProviders";
|
||||
@@ -1564,19 +1563,29 @@ async function syncUserResources(
|
||||
.where(eq(userResources.resourceId, resourceId));
|
||||
|
||||
for (const username of ssoUsers) {
|
||||
const user = await findOrgUserByIdentifier(trx, orgId, username);
|
||||
const [user] = await trx
|
||||
.select()
|
||||
.from(users)
|
||||
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
|
||||
.where(
|
||||
and(
|
||||
or(eq(users.username, username), eq(users.email, username)),
|
||||
eq(userOrgs.orgId, orgId)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!user) {
|
||||
throw new Error(`User not found: ${username} in org ${orgId}`);
|
||||
}
|
||||
|
||||
const existingUserResource = existingUserResources.find(
|
||||
(rr) => rr.userId === user.userId
|
||||
(rr) => rr.userId === user.user.userId
|
||||
);
|
||||
|
||||
if (!existingUserResource) {
|
||||
await trx.insert(userResources).values({
|
||||
userId: user.userId,
|
||||
userId: user.user.userId,
|
||||
resourceId: resourceId
|
||||
});
|
||||
}
|
||||
@@ -1946,19 +1955,29 @@ async function syncUserPolicies(
|
||||
.where(eq(userPolicies.resourcePolicyId, policyId));
|
||||
|
||||
for (const username of ssoUsers) {
|
||||
const user = await findOrgUserByIdentifier(trx, orgId, username);
|
||||
const [user] = await trx
|
||||
.select()
|
||||
.from(users)
|
||||
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
|
||||
.where(
|
||||
and(
|
||||
or(eq(users.username, username), eq(users.email, username)),
|
||||
eq(userOrgs.orgId, orgId)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!user) {
|
||||
throw new Error(`User not found: ${username} in org ${orgId}`);
|
||||
}
|
||||
|
||||
const existingUserPolicy = existingUserPoliciesList.find(
|
||||
(up) => up.userId === user.userId
|
||||
(up) => up.userId === user.user.userId
|
||||
);
|
||||
|
||||
if (!existingUserPolicy) {
|
||||
await trx.insert(userPolicies).values({
|
||||
userId: user.userId,
|
||||
userId: user.user.userId,
|
||||
resourcePolicyId: policyId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
userPolicies,
|
||||
users
|
||||
} from "@server/db";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { eq, and, or } from "drizzle-orm";
|
||||
import { Config, ResourcePolicyData } from "./types";
|
||||
import logger from "@server/logger";
|
||||
import { getUniqueResourcePolicyName } from "@server/db/names";
|
||||
@@ -22,7 +22,6 @@ import { idpExistsForOrg } from "@server/lib/idp/idpExistsForOrg";
|
||||
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
|
||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
||||
import { tierMatrix } from "../billing/tierMatrix";
|
||||
import { findOrgUserByIdentifier } from "./findOrgUser";
|
||||
|
||||
export type ResourcePoliciesResults = {
|
||||
resourcePolicyId: number;
|
||||
@@ -467,7 +466,17 @@ async function syncUserPolicies(
|
||||
.where(eq(userPolicies.resourcePolicyId, policyId));
|
||||
|
||||
for (const username of ssoUsers) {
|
||||
const user = await findOrgUserByIdentifier(trx, orgId, username);
|
||||
const [user] = await trx
|
||||
.select()
|
||||
.from(users)
|
||||
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
|
||||
.where(
|
||||
and(
|
||||
or(eq(users.username, username), eq(users.email, username)),
|
||||
eq(userOrgs.orgId, orgId)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!user) {
|
||||
logger.warn(
|
||||
@@ -477,12 +486,12 @@ async function syncUserPolicies(
|
||||
}
|
||||
|
||||
const alreadyExists = existingUserPolicies.some(
|
||||
(up) => up.userId === user.userId
|
||||
(up) => up.userId === user.user.userId
|
||||
);
|
||||
|
||||
if (!alreadyExists) {
|
||||
await trx.insert(userPolicies).values({
|
||||
userId: user.userId,
|
||||
userId: user.user.userId,
|
||||
resourcePolicyId: policyId
|
||||
});
|
||||
}
|
||||
@@ -527,7 +536,17 @@ async function addUserPolicies(
|
||||
trx: Transaction
|
||||
) {
|
||||
for (const username of ssoUsers) {
|
||||
const user = await findOrgUserByIdentifier(trx, orgId, username);
|
||||
const [user] = await trx
|
||||
.select()
|
||||
.from(users)
|
||||
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
|
||||
.where(
|
||||
and(
|
||||
or(eq(users.username, username), eq(users.email, username)),
|
||||
eq(userOrgs.orgId, orgId)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!user) {
|
||||
logger.warn(
|
||||
@@ -537,7 +556,7 @@ async function addUserPolicies(
|
||||
}
|
||||
|
||||
await trx.insert(userPolicies).values({
|
||||
userId: user.userId,
|
||||
userId: user.user.userId,
|
||||
resourcePolicyId: policyId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -88,27 +88,7 @@ export const queryAccessAuditLogsQuery = z.object({
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.int().nonnegative()),
|
||||
ip: z
|
||||
.preprocess((val) => {
|
||||
if (val === undefined || val === null || val === "") {
|
||||
return undefined;
|
||||
}
|
||||
if (Array.isArray(val)) {
|
||||
return val;
|
||||
}
|
||||
// the array is returned as this
|
||||
if (typeof val === "string") {
|
||||
return val.split(",");
|
||||
}
|
||||
return undefined;
|
||||
}, z.array(z.string()))
|
||||
.optional()
|
||||
.catch([])
|
||||
.openapi({
|
||||
type: "array",
|
||||
description: "Filter by IP adresses"
|
||||
})
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
export const queryAccessAuditLogsParams = z.object({
|
||||
@@ -154,9 +134,6 @@ function getWhere(data: Q) {
|
||||
data.type ? eq(accessAuditLog.type, data.type) : undefined,
|
||||
data.action !== undefined
|
||||
? eq(accessAuditLog.action, data.action)
|
||||
: undefined,
|
||||
data.ip && data.ip.length > 0
|
||||
? inArray(accessAuditLog.ip, data.ip)
|
||||
: undefined
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@ import {
|
||||
handleRemoteExitNodePingMessage
|
||||
} from "#private/routers/remoteExitNode";
|
||||
import { MessageHandler } from "@server/routers/ws";
|
||||
import { handleConnectionLogMessage } from "#private/routers/newt";
|
||||
import {
|
||||
handleConnectionLogMessage,
|
||||
} from "#private/routers/newt";
|
||||
|
||||
export const messageHandlers: Record<string, MessageHandler> = {
|
||||
"remoteExitNode/register": handleRemoteExitNodeRegisterMessage,
|
||||
"remoteExitNode/ping": handleRemoteExitNodePingMessage,
|
||||
"newt/access-log": handleConnectionLogMessage
|
||||
"newt/access-log": handleConnectionLogMessage,
|
||||
};
|
||||
|
||||
@@ -81,27 +81,7 @@ export const queryAccessAuditLogsQuery = z.strictObject({
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.int().nonnegative()),
|
||||
ip: z
|
||||
.preprocess((val) => {
|
||||
if (val === undefined || val === null || val === "") {
|
||||
return undefined;
|
||||
}
|
||||
if (Array.isArray(val)) {
|
||||
return val;
|
||||
}
|
||||
// the array is returned as this
|
||||
if (typeof val === "string") {
|
||||
return val.split(",");
|
||||
}
|
||||
return undefined;
|
||||
}, z.array(z.string()))
|
||||
.optional()
|
||||
.catch([])
|
||||
.openapi({
|
||||
type: "array",
|
||||
description: "Filter by IP adresses"
|
||||
})
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
export const queryRequestAuditLogsParams = z.object({
|
||||
@@ -146,9 +126,6 @@ function getWhere(data: Q) {
|
||||
data.path ? eq(requestAuditLog.path, data.path) : undefined,
|
||||
data.action !== undefined
|
||||
? eq(requestAuditLog.action, data.action)
|
||||
: undefined,
|
||||
data.ip && data.ip.length > 0
|
||||
? inArray(requestAuditLog.ip, data.ip)
|
||||
: undefined
|
||||
);
|
||||
}
|
||||
|
||||
@@ -533,23 +533,18 @@ export async function startAuthentication(
|
||||
|
||||
// If email is provided, get security keys for that specific user
|
||||
if (email) {
|
||||
const matchingUsers = await db
|
||||
const [user] = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(
|
||||
and(
|
||||
eq(users.email, email.toLowerCase()),
|
||||
eq(users.type, UserType.Internal)
|
||||
)
|
||||
);
|
||||
.where(eq(users.email, email))
|
||||
.limit(1);
|
||||
|
||||
if (matchingUsers.length !== 1) {
|
||||
if (!user || user.type !== UserType.Internal) {
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid credentials")
|
||||
);
|
||||
}
|
||||
|
||||
const user = matchingUsers[0];
|
||||
userId = user.userId;
|
||||
|
||||
const userSecurityKeys = await db
|
||||
|
||||
@@ -42,62 +42,54 @@ export async function setServerAdmin(
|
||||
|
||||
const { email, password, setupToken } = parsedBody.data;
|
||||
|
||||
// Validate setup token
|
||||
const [validToken] = await db
|
||||
.select()
|
||||
.from(setupTokens)
|
||||
.where(
|
||||
and(
|
||||
eq(setupTokens.token, setupToken),
|
||||
eq(setupTokens.used, false)
|
||||
)
|
||||
);
|
||||
|
||||
if (!validToken) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Invalid or expired setup token"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.serverAdmin, true));
|
||||
|
||||
if (existing) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Server admin already exists"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const passwordHash = await hashPassword(password);
|
||||
const userId = generateId(15);
|
||||
|
||||
await db.transaction(async (trx) => {
|
||||
const consumed = await trx
|
||||
// Mark the token as used
|
||||
await trx
|
||||
.update(setupTokens)
|
||||
.set({
|
||||
used: true,
|
||||
dateUsed: moment().toISOString()
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(setupTokens.token, setupToken),
|
||||
eq(setupTokens.used, false)
|
||||
)
|
||||
)
|
||||
.returning({ tokenId: setupTokens.tokenId });
|
||||
|
||||
if (!consumed.length) {
|
||||
throw createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Invalid setup token"
|
||||
);
|
||||
}
|
||||
|
||||
const [existingAdmin] = await trx
|
||||
.select({ userId: users.userId })
|
||||
.from(users)
|
||||
.where(eq(users.serverAdmin, true))
|
||||
.limit(1);
|
||||
|
||||
if (existingAdmin) {
|
||||
throw createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Server admin already exists"
|
||||
);
|
||||
}
|
||||
|
||||
const [existingUser] = await trx
|
||||
.select({ userId: users.userId })
|
||||
.from(users)
|
||||
.where(
|
||||
and(
|
||||
eq(users.email, email),
|
||||
eq(users.type, UserType.Internal)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (existingUser) {
|
||||
throw createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"A user with that email address already exists"
|
||||
);
|
||||
}
|
||||
.where(eq(setupTokens.tokenId, validToken.tokenId));
|
||||
|
||||
// Create the server admin user
|
||||
await trx.insert(users).values({
|
||||
userId: userId,
|
||||
email: email,
|
||||
@@ -119,9 +111,6 @@ export async function setServerAdmin(
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (e) {
|
||||
if (createHttpError.isHttpError(e)) {
|
||||
return next(e);
|
||||
}
|
||||
logger.error(e);
|
||||
return next(
|
||||
createHttpError(
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function validateSetupToken(
|
||||
return response<ValidateSetupTokenResponse>(res, {
|
||||
data: {
|
||||
valid: false,
|
||||
message: "Invalid setup token"
|
||||
message: "Invalid or expired setup token"
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
|
||||
@@ -46,7 +46,6 @@ import { AxiosResponse } from "axios";
|
||||
import { ListRolesResponse } from "@server/routers/role";
|
||||
import AutoProvisionConfigWidget from "@app/components/AutoProvisionConfigWidget";
|
||||
import IdpAutoProvisionUsersDescription from "@app/components/IdpAutoProvisionUsersDescription";
|
||||
import IdpIdentifierChangeDialog from "@app/components/IdpIdentifierChangeDialog";
|
||||
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import {
|
||||
@@ -76,12 +75,6 @@ export default function GeneralPage() {
|
||||
>([createMappingBuilderRule()]);
|
||||
const [rawRoleExpression, setRawRoleExpression] = useState("");
|
||||
const [variant, setVariant] = useState<"oidc" | "google" | "azure">("oidc");
|
||||
const [originalIdentifierPath, setOriginalIdentifierPath] = useState("");
|
||||
const [identifierConfirmOpen, setIdentifierConfirmOpen] = useState(false);
|
||||
const [pendingPayload, setPendingPayload] = useState<Record<
|
||||
string,
|
||||
unknown
|
||||
> | null>(null);
|
||||
|
||||
const dashboardRedirectUrl = `${env.app.dashboardUrl}/auth/idp/${idpId}/oidc/callback`;
|
||||
const [redirectUrl, setRedirectUrl] = useState(
|
||||
@@ -191,9 +184,6 @@ export default function GeneralPage() {
|
||||
const data = res.data.data;
|
||||
const roleMapping = data.idpOrg.roleMapping;
|
||||
const idpVariant = data.idpOidcConfig?.variant || "oidc";
|
||||
setOriginalIdentifierPath(
|
||||
data.idpOidcConfig?.identifierPath ?? "sub"
|
||||
);
|
||||
setRedirectUrl(res.data.data.redirectUrl);
|
||||
|
||||
// Set the variant
|
||||
@@ -388,56 +378,18 @@ export default function GeneralPage() {
|
||||
};
|
||||
}
|
||||
|
||||
const nextIdentifierPath =
|
||||
variant === "oidc"
|
||||
? (data as OidcFormValues).identifierPath
|
||||
: undefined;
|
||||
const res = await api.post(
|
||||
`/org/${orgId}/idp/${idpId}/oidc`,
|
||||
payload
|
||||
);
|
||||
|
||||
if (
|
||||
typeof nextIdentifierPath === "string" &&
|
||||
nextIdentifierPath !== originalIdentifierPath
|
||||
) {
|
||||
setPendingPayload(payload);
|
||||
setIdentifierConfirmOpen(true);
|
||||
return;
|
||||
if (res.status === 200) {
|
||||
toast({
|
||||
title: t("success"),
|
||||
description: t("idpUpdatedDescription")
|
||||
});
|
||||
router.refresh();
|
||||
}
|
||||
|
||||
await persistIdp(payload);
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: t("error"),
|
||||
description: formatAxiosError(e),
|
||||
variant: "destructive"
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function persistIdp(payload: Record<string, unknown>) {
|
||||
const res = await api.post(`/org/${orgId}/idp/${idpId}/oidc`, payload);
|
||||
|
||||
if (res.status === 200) {
|
||||
if (typeof payload.identifierPath === "string") {
|
||||
setOriginalIdentifierPath(payload.identifierPath);
|
||||
}
|
||||
toast({
|
||||
title: t("success"),
|
||||
description: t("idpUpdatedDescription")
|
||||
});
|
||||
router.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmIdentifierChange() {
|
||||
if (!pendingPayload) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
await persistIdp(pendingPayload);
|
||||
setPendingPayload(null);
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: t("error"),
|
||||
@@ -455,16 +407,6 @@ export default function GeneralPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<IdpIdentifierChangeDialog
|
||||
open={identifierConfirmOpen}
|
||||
setOpen={(open) => {
|
||||
setIdentifierConfirmOpen(open);
|
||||
if (!open) {
|
||||
setPendingPayload(null);
|
||||
}
|
||||
}}
|
||||
onConfirm={confirmIdentifierChange}
|
||||
/>
|
||||
<SettingsContainer>
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
|
||||
@@ -12,7 +12,6 @@ import { DateTimeValue } from "@app/components/DateTimePicker";
|
||||
import { ArrowUpRight, Key, User } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { ColumnFilterButton } from "@app/components/ColumnFilterButton";
|
||||
import { ColumnMultiFilterButton } from "@app/components/ColumnMultiFilterButton";
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import { build } from "@server/build";
|
||||
import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo";
|
||||
@@ -27,7 +26,6 @@ import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import { logQueries } from "@app/lib/queries";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { QueryAccessAuditLogResponse } from "@server/routers/auditLogs/types";
|
||||
import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji";
|
||||
|
||||
export default function GeneralPage() {
|
||||
const router = useRouter();
|
||||
@@ -47,14 +45,12 @@ export default function GeneralPage() {
|
||||
resourceId?: string;
|
||||
location?: string;
|
||||
actor?: string;
|
||||
ip?: string[];
|
||||
}>({
|
||||
action: searchParams.get("action") || undefined,
|
||||
type: searchParams.get("type") || undefined,
|
||||
resourceId: searchParams.get("resourceId") || undefined,
|
||||
location: searchParams.get("location") || undefined,
|
||||
actor: searchParams.get("actor") || undefined,
|
||||
ip: searchParams.getAll("ip") || undefined
|
||||
actor: searchParams.get("actor") || undefined
|
||||
});
|
||||
|
||||
const [currentPage, setCurrentPage] = useState<number>(0);
|
||||
@@ -180,7 +176,7 @@ export default function GeneralPage() {
|
||||
|
||||
const handleFilterChange = (
|
||||
filterType: keyof typeof filters,
|
||||
value: string | string[] | undefined
|
||||
value: string | undefined
|
||||
) => {
|
||||
const newFilters = { ...filters, [filterType]: value };
|
||||
setFilters(newFilters);
|
||||
@@ -198,13 +194,10 @@ export default function GeneralPage() {
|
||||
) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
Object.entries(newFilters).forEach(([key, value]) => {
|
||||
params.delete(key);
|
||||
if (typeof value === "string") {
|
||||
if (value) {
|
||||
params.set(key, value);
|
||||
} else if (typeof value !== "undefined" && "length" in value) {
|
||||
for (const element of value) {
|
||||
params.append(key, element);
|
||||
}
|
||||
} else {
|
||||
params.delete(key);
|
||||
}
|
||||
});
|
||||
router.replace(`?${params.toString()}`, { scroll: false });
|
||||
@@ -212,7 +205,6 @@ export default function GeneralPage() {
|
||||
|
||||
const exportData = async () => {
|
||||
try {
|
||||
const { ip, ...restFilters } = filters;
|
||||
const params: any = {
|
||||
timeStart: dateRange.startDate?.date
|
||||
? new Date(dateRange.startDate.date).toISOString()
|
||||
@@ -220,20 +212,13 @@ export default function GeneralPage() {
|
||||
timeEnd: dateRange.endDate?.date
|
||||
? new Date(dateRange.endDate.date).toISOString()
|
||||
: undefined,
|
||||
...restFilters
|
||||
...filters
|
||||
};
|
||||
|
||||
// axios serializes arrays as `ip[]=…`, which express's query
|
||||
// parser does not read back as `ip`, so pass them in the URL
|
||||
const sp = new URLSearchParams((ip ?? []).map((ip) => ["ip", ip]));
|
||||
|
||||
const response = await api.get(
|
||||
`/org/${orgId}/logs/access/export?${sp.toString()}`,
|
||||
{
|
||||
responseType: "blob",
|
||||
params
|
||||
}
|
||||
);
|
||||
const response = await api.get(`/org/${orgId}/logs/access/export`, {
|
||||
responseType: "blob",
|
||||
params
|
||||
});
|
||||
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||
const link = document.createElement("a");
|
||||
@@ -312,24 +297,7 @@ export default function GeneralPage() {
|
||||
},
|
||||
{
|
||||
accessorKey: "ip",
|
||||
header: () => (
|
||||
<span className="px-2">
|
||||
<ColumnMultiFilterButton
|
||||
options={(filters.ip ?? []).map((ip) => ({
|
||||
label: ip,
|
||||
value: ip
|
||||
}))}
|
||||
label={t("ip")}
|
||||
allowArbitraryValues
|
||||
searchPlaceholder={t("ipFilterSearchPlaceholder")}
|
||||
emptyMessage={t("ipFilterEmptyMessage")}
|
||||
selectedValues={filters.ip ?? []}
|
||||
onSelectedValuesChange={(value) =>
|
||||
handleFilterChange("ip", value)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
),
|
||||
header: () => <span className="px-2">{t("ip")}</span>,
|
||||
cell: ({ row }) => {
|
||||
return row.original.ip ? (
|
||||
row.original.ip
|
||||
@@ -347,7 +315,7 @@ export default function GeneralPage() {
|
||||
options={filterAttributes.locations.map(
|
||||
(location) => ({
|
||||
value: location,
|
||||
label: `${location} ${countryCodeToFlagEmoji(location)}`
|
||||
label: location
|
||||
})
|
||||
)}
|
||||
label={t("location")}
|
||||
@@ -366,8 +334,7 @@ export default function GeneralPage() {
|
||||
<span className="flex items-center gap-1">
|
||||
{row.original.location ? (
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{row.original.location}{" "}
|
||||
{countryCodeToFlagEmoji(row.original.location)}
|
||||
{row.original.location}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground text-xs">
|
||||
|
||||
@@ -23,8 +23,6 @@ import { useMemo, useState, useTransition } from "react";
|
||||
import { useStoredPageSize } from "@app/hooks/useStoredPageSize";
|
||||
import type { QueryRequestAuditLogResponse } from "@server/routers/auditLogs/types";
|
||||
import { ColumnFilterButton } from "@app/components/ColumnFilterButton";
|
||||
import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji";
|
||||
import { ColumnMultiFilterButton } from "@app/components/ColumnMultiFilterButton";
|
||||
|
||||
export default function GeneralPage() {
|
||||
const router = useRouter();
|
||||
@@ -49,7 +47,6 @@ export default function GeneralPage() {
|
||||
method?: string;
|
||||
reason?: string;
|
||||
path?: string;
|
||||
ip?: string[];
|
||||
}>({
|
||||
action: searchParams.get("action") || undefined,
|
||||
host: searchParams.get("host") || undefined,
|
||||
@@ -58,8 +55,7 @@ export default function GeneralPage() {
|
||||
actor: searchParams.get("actor") || undefined,
|
||||
method: searchParams.get("method") || undefined,
|
||||
reason: searchParams.get("reason") || undefined,
|
||||
path: searchParams.get("path") || undefined,
|
||||
ip: searchParams.getAll("ip") || undefined
|
||||
path: searchParams.get("path") || undefined
|
||||
});
|
||||
|
||||
const getDefaultDateRange = () => {
|
||||
@@ -183,7 +179,7 @@ export default function GeneralPage() {
|
||||
|
||||
const handleFilterChange = (
|
||||
filterType: keyof typeof filters,
|
||||
value: string | string[] | undefined
|
||||
value: string | undefined
|
||||
) => {
|
||||
const newFilters = { ...filters, [filterType]: value };
|
||||
setFilters(newFilters);
|
||||
@@ -201,13 +197,10 @@ export default function GeneralPage() {
|
||||
) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
Object.entries(newFilters).forEach(([key, value]) => {
|
||||
params.delete(key);
|
||||
if (typeof value === "string") {
|
||||
if (value) {
|
||||
params.set(key, value);
|
||||
} else if (typeof value !== "undefined" && "length" in value) {
|
||||
for (const element of value) {
|
||||
params.append(key, element);
|
||||
}
|
||||
} else {
|
||||
params.delete(key);
|
||||
}
|
||||
});
|
||||
router.replace(`?${params.toString()}`, { scroll: false });
|
||||
@@ -216,7 +209,6 @@ export default function GeneralPage() {
|
||||
const exportData = async () => {
|
||||
try {
|
||||
// Prepare query params for export
|
||||
const { ip, ...restFilters } = filters;
|
||||
const params: any = {
|
||||
timeStart: dateRange.startDate?.date
|
||||
? new Date(dateRange.startDate.date).toISOString()
|
||||
@@ -224,15 +216,11 @@ export default function GeneralPage() {
|
||||
timeEnd: dateRange.endDate?.date
|
||||
? new Date(dateRange.endDate.date).toISOString()
|
||||
: undefined,
|
||||
...restFilters
|
||||
...filters
|
||||
};
|
||||
|
||||
// axios serializes arrays as `ip[]=…`, which express's query
|
||||
// parser does not read back as `ip`, so pass them in the URL
|
||||
const sp = new URLSearchParams((ip ?? []).map((ip) => ["ip", ip]));
|
||||
|
||||
const response = await api.get(
|
||||
`/org/${orgId}/logs/request/export?${sp.toString()}`,
|
||||
`/org/${orgId}/logs/request/export`,
|
||||
{
|
||||
responseType: "blob",
|
||||
params
|
||||
@@ -363,24 +351,7 @@ export default function GeneralPage() {
|
||||
},
|
||||
{
|
||||
accessorKey: "ip",
|
||||
header: ({ column }) => (
|
||||
<span className="px-2">
|
||||
<ColumnMultiFilterButton
|
||||
options={(filters.ip ?? []).map((ip) => ({
|
||||
label: ip,
|
||||
value: ip
|
||||
}))}
|
||||
label={t("ip")}
|
||||
allowArbitraryValues
|
||||
searchPlaceholder={t("ipFilterSearchPlaceholder")}
|
||||
emptyMessage={t("ipFilterEmptyMessage")}
|
||||
selectedValues={filters.ip ?? []}
|
||||
onSelectedValuesChange={(value) =>
|
||||
handleFilterChange("ip", value)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
),
|
||||
header: ({ column }) => <span className="px-2">{t("ip")}</span>,
|
||||
cell: ({ row }) => {
|
||||
return row.original.ip ? (
|
||||
row.original.ip
|
||||
@@ -398,7 +369,7 @@ export default function GeneralPage() {
|
||||
options={filterAttributes.locations.map(
|
||||
(location) => ({
|
||||
value: location,
|
||||
label: `${location} ${countryCodeToFlagEmoji(location)}`
|
||||
label: location
|
||||
})
|
||||
)}
|
||||
selectedValue={filters.location}
|
||||
@@ -418,8 +389,7 @@ export default function GeneralPage() {
|
||||
<span className="flex items-center gap-1">
|
||||
{row.original.location ? (
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{row.original.location}{" "}
|
||||
{countryCodeToFlagEmoji(row.original.location)}
|
||||
{row.original.location}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground text-xs">
|
||||
|
||||
@@ -41,7 +41,6 @@ import {
|
||||
} from "@app/components/InfoSection";
|
||||
import CopyToClipboard from "@app/components/CopyToClipboard";
|
||||
import IdpTypeBadge from "@app/components/IdpTypeBadge";
|
||||
import IdpIdentifierChangeDialog from "@app/components/IdpIdentifierChangeDialog";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function GeneralPage() {
|
||||
@@ -52,12 +51,6 @@ export default function GeneralPage() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [initialLoading, setInitialLoading] = useState(true);
|
||||
const [variant, setVariant] = useState<"oidc" | "google" | "azure">("oidc");
|
||||
const [originalIdentifierPath, setOriginalIdentifierPath] = useState("");
|
||||
const [identifierConfirmOpen, setIdentifierConfirmOpen] = useState(false);
|
||||
const [pendingPayload, setPendingPayload] = useState<Record<
|
||||
string,
|
||||
unknown
|
||||
> | null>(null);
|
||||
|
||||
const redirectUrl = `${env.app.dashboardUrl}/auth/idp/${idpId}/oidc/callback`;
|
||||
const t = useTranslations();
|
||||
@@ -148,9 +141,6 @@ export default function GeneralPage() {
|
||||
| "google"
|
||||
| "azure") || "oidc";
|
||||
setVariant(idpVariant);
|
||||
setOriginalIdentifierPath(
|
||||
data.idpOidcConfig?.identifierPath ?? "sub"
|
||||
);
|
||||
|
||||
let tenantId = "";
|
||||
if (idpVariant === "azure" && data.idpOidcConfig?.authUrl) {
|
||||
@@ -268,56 +258,15 @@ export default function GeneralPage() {
|
||||
};
|
||||
}
|
||||
|
||||
const nextIdentifierPath =
|
||||
variant === "oidc"
|
||||
? (data as OidcFormValues).identifierPath
|
||||
: undefined;
|
||||
const res = await api.post(`/idp/${idpId}/oidc`, payload);
|
||||
|
||||
if (
|
||||
typeof nextIdentifierPath === "string" &&
|
||||
nextIdentifierPath !== originalIdentifierPath
|
||||
) {
|
||||
setPendingPayload(payload);
|
||||
setIdentifierConfirmOpen(true);
|
||||
return;
|
||||
if (res.status === 200) {
|
||||
toast({
|
||||
title: t("success"),
|
||||
description: t("idpUpdatedDescription")
|
||||
});
|
||||
router.refresh();
|
||||
}
|
||||
|
||||
await persistIdp(payload);
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: t("error"),
|
||||
description: formatAxiosError(e),
|
||||
variant: "destructive"
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function persistIdp(payload: Record<string, unknown>) {
|
||||
const res = await api.post(`/idp/${idpId}/oidc`, payload);
|
||||
|
||||
if (res.status === 200) {
|
||||
if (typeof payload.identifierPath === "string") {
|
||||
setOriginalIdentifierPath(payload.identifierPath);
|
||||
}
|
||||
toast({
|
||||
title: t("success"),
|
||||
description: t("idpUpdatedDescription")
|
||||
});
|
||||
router.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmIdentifierChange() {
|
||||
if (!pendingPayload) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
await persistIdp(pendingPayload);
|
||||
setPendingPayload(null);
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: t("error"),
|
||||
@@ -335,16 +284,6 @@ export default function GeneralPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<IdpIdentifierChangeDialog
|
||||
open={identifierConfirmOpen}
|
||||
setOpen={(open) => {
|
||||
setIdentifierConfirmOpen(open);
|
||||
if (!open) {
|
||||
setPendingPayload(null);
|
||||
}
|
||||
}}
|
||||
onConfirm={confirmIdentifierChange}
|
||||
/>
|
||||
<SettingsContainer>
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
|
||||
@@ -21,7 +21,7 @@ import { useTranslations } from "next-intl";
|
||||
|
||||
interface FilterOption {
|
||||
value: string;
|
||||
label: React.ReactNode;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface ColumnFilterButtonProps {
|
||||
@@ -32,7 +32,6 @@ interface ColumnFilterButtonProps {
|
||||
emptyMessage?: string;
|
||||
className?: string;
|
||||
label: string;
|
||||
allowArbitraryValues?: boolean;
|
||||
}
|
||||
|
||||
export function ColumnFilterButton({
|
||||
@@ -42,8 +41,7 @@ export function ColumnFilterButton({
|
||||
searchPlaceholder = "Search...",
|
||||
emptyMessage = "No options found",
|
||||
className,
|
||||
label,
|
||||
allowArbitraryValues
|
||||
label
|
||||
}: ColumnFilterButtonProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
@@ -103,7 +101,7 @@ export function ColumnFilterButton({
|
||||
{options.map((option) => (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
value={option.label}
|
||||
onSelect={() => {
|
||||
onValueChange(
|
||||
selectedValue === option.value
|
||||
|
||||
@@ -35,7 +35,6 @@ type ColumnMultiFilterButtonProps = {
|
||||
emptyMessage?: string;
|
||||
className?: string;
|
||||
label: string;
|
||||
allowArbitraryValues?: boolean;
|
||||
};
|
||||
|
||||
export function ColumnMultiFilterButton({
|
||||
@@ -45,26 +44,11 @@ export function ColumnMultiFilterButton({
|
||||
searchPlaceholder = "Search...",
|
||||
emptyMessage = "No options found",
|
||||
className,
|
||||
label,
|
||||
allowArbitraryValues
|
||||
label
|
||||
}: ColumnMultiFilterButtonProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const t = useTranslations();
|
||||
|
||||
const visibleOptions = useMemo<FilterOption[]>(() => {
|
||||
const newOptions = [...options];
|
||||
|
||||
if (allowArbitraryValues && searchQuery.trim().length > 0) {
|
||||
newOptions.push({
|
||||
label: searchQuery,
|
||||
value: searchQuery
|
||||
});
|
||||
}
|
||||
|
||||
return newOptions;
|
||||
}, [options, allowArbitraryValues, searchQuery]);
|
||||
|
||||
const selectedSet = useMemo(
|
||||
() => new Set(selectedValues),
|
||||
[selectedValues]
|
||||
@@ -80,7 +64,7 @@ export function ColumnMultiFilterButton({
|
||||
selectedValues[0]
|
||||
);
|
||||
}
|
||||
return t("multiSelectFilterCount", {
|
||||
return t("accessUsersRoleFilterCount", {
|
||||
count: selectedValues.length
|
||||
});
|
||||
}, [selectedValues, options, t]);
|
||||
@@ -124,11 +108,7 @@ export function ColumnMultiFilterButton({
|
||||
align="start"
|
||||
>
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder={searchPlaceholder}
|
||||
value={searchQuery}
|
||||
onValueChange={setSearchQuery}
|
||||
/>
|
||||
<CommandInput placeholder={searchPlaceholder} />
|
||||
<CommandList>
|
||||
<CommandEmpty>{emptyMessage}</CommandEmpty>
|
||||
<CommandGroup>
|
||||
@@ -143,7 +123,7 @@ export function ColumnMultiFilterButton({
|
||||
{t("accessFilterClear")}
|
||||
</CommandItem>
|
||||
)}
|
||||
{visibleOptions.map((option) => (
|
||||
{options.map((option) => (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
value={option.label}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type IdpIdentifierChangeDialogProps = {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
onConfirm: () => Promise<void>;
|
||||
};
|
||||
|
||||
export default function IdpIdentifierChangeDialog({
|
||||
open,
|
||||
setOpen,
|
||||
onConfirm
|
||||
}: IdpIdentifierChangeDialogProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<ConfirmDeleteDialog
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
dialog={
|
||||
<div className="space-y-2">
|
||||
<p>{t("idpIdentifierChangeDescription")}</p>
|
||||
</div>
|
||||
}
|
||||
buttonText={t("saveGeneralSettings")}
|
||||
onConfirm={onConfirm}
|
||||
string={t("idpIdentifierChangeConfirmMessage")}
|
||||
title={t("idpIdentifierChangeTitle")}
|
||||
warningText={t("idpIdentifierChangeWarningText")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+22
-30
@@ -1,8 +1,3 @@
|
||||
import {
|
||||
getAiBudgetScopeListPath,
|
||||
type AiBudgetScope
|
||||
} from "@app/lib/aiBudgetScope";
|
||||
import type { AiProviderType } from "@app/lib/aiProviderDefaults";
|
||||
import type { LauncherQueryFilters } from "@app/lib/launcherSearchParams";
|
||||
import { buildLauncherSearchParams } from "@app/lib/launcherSearchParams";
|
||||
import { build } from "@server/build";
|
||||
@@ -10,21 +5,15 @@ import {
|
||||
StatusHistoryResponse,
|
||||
type BatchedStatusHistoryResponse
|
||||
} from "@server/lib/statusHistory";
|
||||
import type { ListAiBudgetsByScopeResponse } from "@server/routers/aiBudget/types";
|
||||
import type {
|
||||
ListAiModelsResponse,
|
||||
ListAiProvidersResponse,
|
||||
ListCatalogModelsResponse
|
||||
} from "@server/routers/aiProvider/types";
|
||||
import type { ListAlertRulesResponse } from "@server/routers/alertRule/types";
|
||||
import type {
|
||||
QueryRequestAnalyticsResponse,
|
||||
QueryAiUsageFilterOptionsResponse,
|
||||
QueryAiUsageOverviewResponse,
|
||||
QueryAiUsageProvidersResponse,
|
||||
QueryAiUsageResourcesResponse,
|
||||
QueryAiUsageUsersRolesResponse,
|
||||
QueryAiUsageVirtualApiKeysResponse,
|
||||
QueryRequestAnalyticsResponse
|
||||
QueryAiUsageVirtualApiKeysResponse
|
||||
} from "@server/routers/auditLogs";
|
||||
import type {
|
||||
QueryAccessAuditLogResponse,
|
||||
@@ -45,7 +34,6 @@ import type {
|
||||
import type { GetDomainResponse } from "@server/routers/domain/getDomain";
|
||||
import { ListHealthChecksResponse } from "@server/routers/healthChecks/types";
|
||||
import type { ListOrgLabelsResponse } from "@server/routers/labels/types";
|
||||
import type { ListLauncherAiModelsResponse } from "@server/routers/launcher/listLauncherAiModels";
|
||||
import type {
|
||||
LauncherResource,
|
||||
ListLauncherGroupsResponse,
|
||||
@@ -55,8 +43,9 @@ import type {
|
||||
ListLauncherSitesResponse,
|
||||
ListLauncherViewsResponse
|
||||
} from "@server/routers/launcher/types";
|
||||
import type { ListLauncherAiModelsResponse } from "@server/routers/launcher/listLauncherAiModels";
|
||||
import type { ListMyVirtualApiKeysResponse } from "@server/routers/virtualApiKey/types";
|
||||
import type { GetResourcePolicyResponse } from "@server/routers/policy";
|
||||
import type { ListRemoteExitNodesResponse } from "@server/routers/remoteExitNode/types";
|
||||
import type {
|
||||
GetResourcePoliciesResponse,
|
||||
GetResourceWhitelistResponse,
|
||||
@@ -70,6 +59,7 @@ import type {
|
||||
import type { GetResourceResponse } from "@server/routers/resource/getResource";
|
||||
import type { GetResourceAuthInfoResponse } from "@server/routers/resource/getResourceAuthInfo";
|
||||
import type { ListResourcePoliciesResponse } from "@server/routers/resource/types";
|
||||
import type { ListRemoteExitNodesResponse } from "@server/routers/remoteExitNode/types";
|
||||
import type { ListRolesResponse } from "@server/routers/role";
|
||||
import type { ListSitesResponse } from "@server/routers/site";
|
||||
import type {
|
||||
@@ -81,8 +71,18 @@ import type {
|
||||
} from "@server/routers/siteResource";
|
||||
import type { GetSiteResourceResponse } from "@server/routers/siteResource/getSiteResource";
|
||||
import type { ListTargetsResponse } from "@server/routers/target";
|
||||
import type {
|
||||
ListAiModelsResponse,
|
||||
ListAiProvidersResponse,
|
||||
ListCatalogModelsResponse
|
||||
} from "@server/routers/aiProvider/types";
|
||||
import type { AiProviderType } from "@app/lib/aiProviderDefaults";
|
||||
import type { ListAiBudgetsByScopeResponse } from "@server/routers/aiBudget/types";
|
||||
import {
|
||||
getAiBudgetScopeListPath,
|
||||
type AiBudgetScope
|
||||
} from "@app/lib/aiBudgetScope";
|
||||
import type { ListUsersResponse } from "@server/routers/user";
|
||||
import type { ListMyVirtualApiKeysResponse } from "@server/routers/virtualApiKey/types";
|
||||
import type ResponseT from "@server/types/Response";
|
||||
import {
|
||||
infiniteQueryOptions,
|
||||
@@ -1000,8 +1000,7 @@ export const httpLogsFiltersSchema = z.object({
|
||||
actor: z.string().optional().catch(undefined),
|
||||
method: z.string().optional().catch(undefined),
|
||||
reason: z.string().optional().catch(undefined),
|
||||
path: z.string().optional().catch(undefined),
|
||||
ip: z.array(z.string()).optional().catch(undefined)
|
||||
path: z.string().optional().catch(undefined)
|
||||
});
|
||||
|
||||
export type HttpLogFilters = z.output<typeof httpLogsFiltersSchema>;
|
||||
@@ -1027,8 +1026,7 @@ export const accessLogsFiltersSchema = z.object({
|
||||
action: z.string().optional().catch(undefined),
|
||||
location: z.string().optional().catch(undefined),
|
||||
actor: z.string().optional().catch(undefined),
|
||||
type: z.string().optional().catch(undefined),
|
||||
ip: z.array(z.string()).optional().catch(undefined)
|
||||
type: z.string().optional().catch(undefined)
|
||||
});
|
||||
|
||||
export type AccessLogFilters = z.output<typeof accessLogsFiltersSchema>;
|
||||
@@ -1141,13 +1139,10 @@ export const logQueries = {
|
||||
queryOptions({
|
||||
queryKey: ["REQUEST_LOGS", orgId, "ALL", filters] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
const { page, pageSize, ip, ...rest } = filters;
|
||||
const sp = new URLSearchParams(
|
||||
(ip ?? []).map((ip) => ["ip", ip])
|
||||
);
|
||||
const { page, pageSize, ...rest } = filters;
|
||||
const res = await meta!.api.get<
|
||||
AxiosResponse<QueryRequestAuditLogResponse>
|
||||
>(`/org/${orgId}/logs/request?${sp.toString()}`, {
|
||||
>(`/org/${orgId}/logs/request`, {
|
||||
params: {
|
||||
...rest,
|
||||
limit: pageSize,
|
||||
@@ -1169,13 +1164,10 @@ export const logQueries = {
|
||||
queryOptions({
|
||||
queryKey: ["ACCESS_LOGS", orgId, "ALL", filters] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
const { page, pageSize, ip, ...rest } = filters;
|
||||
const sp = new URLSearchParams(
|
||||
(ip ?? []).map((ip) => ["ip", ip])
|
||||
);
|
||||
const { page, pageSize, ...rest } = filters;
|
||||
const res = await meta!.api.get<
|
||||
AxiosResponse<QueryAccessAuditLogResponse>
|
||||
>(`/org/${orgId}/logs/access?${sp.toString()}`, {
|
||||
>(`/org/${orgId}/logs/access`, {
|
||||
params: {
|
||||
...rest,
|
||||
limit: pageSize,
|
||||
|
||||
Reference in New Issue
Block a user