mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-01 08:16:44 +00:00
Merge branch 'dev' of https://github.com/fosrl/pangolin into dev
This commit is contained in:
@@ -31,7 +31,7 @@
|
|||||||
[](https://pangolin.net/slack)
|
[](https://pangolin.net/slack)
|
||||||
[](https://hub.docker.com/r/fosrl/pangolin)
|
[](https://hub.docker.com/r/fosrl/pangolin)
|
||||||

|

|
||||||
[](https://www.youtube.com/@fossorial-app)
|
[](https://www.youtube.com/@pangolin-net)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,15 @@ services:
|
|||||||
PARSERS: crowdsecurity/whitelists
|
PARSERS: crowdsecurity/whitelists
|
||||||
ENROLL_TAGS: docker
|
ENROLL_TAGS: docker
|
||||||
healthcheck:
|
healthcheck:
|
||||||
interval: 10s
|
test:
|
||||||
retries: 15
|
- CMD
|
||||||
timeout: 10s
|
- cscli
|
||||||
test: ["CMD", "cscli", "capi", "status"]
|
- lapi
|
||||||
|
- status
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=false" # Disable traefik for crowdsec
|
- "traefik.enable=false" # Disable traefik for crowdsec
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -107,3 +107,12 @@ http:
|
|||||||
loadBalancer:
|
loadBalancer:
|
||||||
servers:
|
servers:
|
||||||
- url: "http://pangolin:3000" # API/WebSocket server
|
- url: "http://pangolin:3000" # API/WebSocket server
|
||||||
|
|
||||||
|
tcp:
|
||||||
|
serversTransports:
|
||||||
|
pp-transport-v1:
|
||||||
|
proxyProtocol:
|
||||||
|
version: 1
|
||||||
|
pp-transport-v2:
|
||||||
|
proxyProtocol:
|
||||||
|
version: 2
|
||||||
|
|||||||
@@ -158,9 +158,9 @@
|
|||||||
"resourceMessageRemove": "Once removed, the resource will no longer be accessible. All targets associated with the resource will also be removed.",
|
"resourceMessageRemove": "Once removed, the resource will no longer be accessible. All targets associated with the resource will also be removed.",
|
||||||
"resourceQuestionRemove": "Are you sure you want to remove the resource from the organization?",
|
"resourceQuestionRemove": "Are you sure you want to remove the resource from the organization?",
|
||||||
"resourceHTTP": "HTTPS Resource",
|
"resourceHTTP": "HTTPS Resource",
|
||||||
"resourceHTTPDescription": "Proxy requests to the app over HTTPS using a subdomain or base domain.",
|
"resourceHTTPDescription": "Proxy requests over HTTPS using a fully qualified domain name.",
|
||||||
"resourceRaw": "Raw TCP/UDP Resource",
|
"resourceRaw": "Raw TCP/UDP Resource",
|
||||||
"resourceRawDescription": "Proxy requests to the app over TCP/UDP using a port number. This only works when sites are connected to nodes.",
|
"resourceRawDescription": "Proxy requests over raw TCP/UDP using a port number.",
|
||||||
"resourceCreate": "Create Resource",
|
"resourceCreate": "Create Resource",
|
||||||
"resourceCreateDescription": "Follow the steps below to create a new resource",
|
"resourceCreateDescription": "Follow the steps below to create a new resource",
|
||||||
"resourceSeeAll": "See All Resources",
|
"resourceSeeAll": "See All Resources",
|
||||||
@@ -946,7 +946,7 @@
|
|||||||
"pincodeAuth": "Authenticator Code",
|
"pincodeAuth": "Authenticator Code",
|
||||||
"pincodeSubmit2": "Submit Code",
|
"pincodeSubmit2": "Submit Code",
|
||||||
"passwordResetSubmit": "Request Reset",
|
"passwordResetSubmit": "Request Reset",
|
||||||
"passwordResetAlreadyHaveCode": "Enter Password Reset Code",
|
"passwordResetAlreadyHaveCode": "Enter Code",
|
||||||
"passwordResetSmtpRequired": "Please contact your administrator",
|
"passwordResetSmtpRequired": "Please contact your administrator",
|
||||||
"passwordResetSmtpRequiredDescription": "A password reset code is required to reset your password. Please contact your administrator for assistance.",
|
"passwordResetSmtpRequiredDescription": "A password reset code is required to reset your password. Please contact your administrator for assistance.",
|
||||||
"passwordBack": "Back to Password",
|
"passwordBack": "Back to Password",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ function createDb() {
|
|||||||
|
|
||||||
export const db = createDb();
|
export const db = createDb();
|
||||||
export default db;
|
export default db;
|
||||||
|
export const driver: "pg" | "sqlite" = "sqlite";
|
||||||
export type Transaction = Parameters<
|
export type Transaction = Parameters<
|
||||||
Parameters<(typeof db)["transaction"]>[0]
|
Parameters<(typeof db)["transaction"]>[0]
|
||||||
>[0];
|
>[0];
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ import response from "@server/lib/response";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo";
|
import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo";
|
||||||
|
|
||||||
|
let primaryDb = db;
|
||||||
|
if (driver == "pg") {
|
||||||
|
primaryDb = db.$primary as typeof db; // select the primary instance in a replicated setup
|
||||||
|
}
|
||||||
|
|
||||||
const queryAccessAuditLogsQuery = z.object({
|
const queryAccessAuditLogsQuery = z.object({
|
||||||
// iso string just validate its a parseable date
|
// iso string just validate its a parseable date
|
||||||
timeStart: z
|
timeStart: z
|
||||||
@@ -74,12 +79,12 @@ async function query(query: Q) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [all] = await db
|
const [all] = await primaryDb
|
||||||
.select({ total: count() })
|
.select({ total: count() })
|
||||||
.from(requestAuditLog)
|
.from(requestAuditLog)
|
||||||
.where(baseConditions);
|
.where(baseConditions);
|
||||||
|
|
||||||
const [blocked] = await db
|
const [blocked] = await primaryDb
|
||||||
.select({ total: count() })
|
.select({ total: count() })
|
||||||
.from(requestAuditLog)
|
.from(requestAuditLog)
|
||||||
.where(and(baseConditions, eq(requestAuditLog.action, false)));
|
.where(and(baseConditions, eq(requestAuditLog.action, false)));
|
||||||
@@ -88,7 +93,9 @@ async function query(query: Q) {
|
|||||||
.mapWith(Number)
|
.mapWith(Number)
|
||||||
.as("total");
|
.as("total");
|
||||||
|
|
||||||
const requestsPerCountry = await db
|
const DISTINCT_LIMIT = 500;
|
||||||
|
|
||||||
|
const requestsPerCountry = await primaryDb
|
||||||
.selectDistinct({
|
.selectDistinct({
|
||||||
code: requestAuditLog.location,
|
code: requestAuditLog.location,
|
||||||
count: totalQ
|
count: totalQ
|
||||||
@@ -96,7 +103,16 @@ async function query(query: Q) {
|
|||||||
.from(requestAuditLog)
|
.from(requestAuditLog)
|
||||||
.where(and(baseConditions, not(isNull(requestAuditLog.location))))
|
.where(and(baseConditions, not(isNull(requestAuditLog.location))))
|
||||||
.groupBy(requestAuditLog.location)
|
.groupBy(requestAuditLog.location)
|
||||||
.orderBy(desc(totalQ));
|
.orderBy(desc(totalQ))
|
||||||
|
.limit(DISTINCT_LIMIT+1);
|
||||||
|
|
||||||
|
if (requestsPerCountry.length > DISTINCT_LIMIT) {
|
||||||
|
// throw an error
|
||||||
|
throw createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
`Too many distinct countries. Please narrow your query.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const groupByDayFunction =
|
const groupByDayFunction =
|
||||||
driver === "pg"
|
driver === "pg"
|
||||||
@@ -106,7 +122,7 @@ async function query(query: Q) {
|
|||||||
const booleanTrue = driver === "pg" ? sql`true` : sql`1`;
|
const booleanTrue = driver === "pg" ? sql`true` : sql`1`;
|
||||||
const booleanFalse = driver === "pg" ? sql`false` : sql`0`;
|
const booleanFalse = driver === "pg" ? sql`false` : sql`0`;
|
||||||
|
|
||||||
const requestsPerDay = await db
|
const requestsPerDay = await primaryDb
|
||||||
.select({
|
.select({
|
||||||
day: groupByDayFunction.as("day"),
|
day: groupByDayFunction.as("day"),
|
||||||
allowedCount:
|
allowedCount:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { db, requestAuditLog, resources } from "@server/db";
|
import { db, driver, requestAuditLog, resources } from "@server/db";
|
||||||
import { registry } from "@server/openApi";
|
import { registry } from "@server/openApi";
|
||||||
import { NextFunction } from "express";
|
import { NextFunction } from "express";
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
@@ -13,6 +13,11 @@ import response from "@server/lib/response";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo";
|
import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo";
|
||||||
|
|
||||||
|
let primaryDb = db;
|
||||||
|
if (driver == "pg") {
|
||||||
|
primaryDb = db.$primary as typeof db; // select the primary instance in a replicated setup
|
||||||
|
}
|
||||||
|
|
||||||
export const queryAccessAuditLogsQuery = z.object({
|
export const queryAccessAuditLogsQuery = z.object({
|
||||||
// iso string just validate its a parseable date
|
// iso string just validate its a parseable date
|
||||||
timeStart: z
|
timeStart: z
|
||||||
@@ -107,7 +112,7 @@ function getWhere(data: Q) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function queryRequest(data: Q) {
|
export function queryRequest(data: Q) {
|
||||||
return db
|
return primaryDb
|
||||||
.select({
|
.select({
|
||||||
id: requestAuditLog.id,
|
id: requestAuditLog.id,
|
||||||
timestamp: requestAuditLog.timestamp,
|
timestamp: requestAuditLog.timestamp,
|
||||||
@@ -143,7 +148,7 @@ export function queryRequest(data: Q) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function countRequestQuery(data: Q) {
|
export function countRequestQuery(data: Q) {
|
||||||
const countQuery = db
|
const countQuery = primaryDb
|
||||||
.select({ count: count() })
|
.select({ count: count() })
|
||||||
.from(requestAuditLog)
|
.from(requestAuditLog)
|
||||||
.where(getWhere(data));
|
.where(getWhere(data));
|
||||||
@@ -173,50 +178,61 @@ async function queryUniqueFilterAttributes(
|
|||||||
eq(requestAuditLog.orgId, orgId)
|
eq(requestAuditLog.orgId, orgId)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get unique actors
|
const DISTINCT_LIMIT = 500;
|
||||||
const uniqueActors = await db
|
|
||||||
.selectDistinct({
|
|
||||||
actor: requestAuditLog.actor
|
|
||||||
})
|
|
||||||
.from(requestAuditLog)
|
|
||||||
.where(baseConditions);
|
|
||||||
|
|
||||||
// Get unique locations
|
// TODO: SOMEONE PLEASE OPTIMIZE THIS!!!!!
|
||||||
const uniqueLocations = await db
|
|
||||||
.selectDistinct({
|
|
||||||
locations: requestAuditLog.location
|
|
||||||
})
|
|
||||||
.from(requestAuditLog)
|
|
||||||
.where(baseConditions);
|
|
||||||
|
|
||||||
// Get unique actors
|
// Run all queries in parallel
|
||||||
const uniqueHosts = await db
|
const [
|
||||||
.selectDistinct({
|
uniqueActors,
|
||||||
hosts: requestAuditLog.host
|
uniqueLocations,
|
||||||
})
|
uniqueHosts,
|
||||||
.from(requestAuditLog)
|
uniquePaths,
|
||||||
.where(baseConditions);
|
uniqueResources
|
||||||
|
] = await Promise.all([
|
||||||
|
primaryDb
|
||||||
|
.selectDistinct({ actor: requestAuditLog.actor })
|
||||||
|
.from(requestAuditLog)
|
||||||
|
.where(baseConditions)
|
||||||
|
.limit(DISTINCT_LIMIT+1),
|
||||||
|
primaryDb
|
||||||
|
.selectDistinct({ locations: requestAuditLog.location })
|
||||||
|
.from(requestAuditLog)
|
||||||
|
.where(baseConditions)
|
||||||
|
.limit(DISTINCT_LIMIT+1),
|
||||||
|
primaryDb
|
||||||
|
.selectDistinct({ hosts: requestAuditLog.host })
|
||||||
|
.from(requestAuditLog)
|
||||||
|
.where(baseConditions)
|
||||||
|
.limit(DISTINCT_LIMIT+1),
|
||||||
|
primaryDb
|
||||||
|
.selectDistinct({ paths: requestAuditLog.path })
|
||||||
|
.from(requestAuditLog)
|
||||||
|
.where(baseConditions)
|
||||||
|
.limit(DISTINCT_LIMIT+1),
|
||||||
|
primaryDb
|
||||||
|
.selectDistinct({
|
||||||
|
id: requestAuditLog.resourceId,
|
||||||
|
name: resources.name
|
||||||
|
})
|
||||||
|
.from(requestAuditLog)
|
||||||
|
.leftJoin(
|
||||||
|
resources,
|
||||||
|
eq(requestAuditLog.resourceId, resources.resourceId)
|
||||||
|
)
|
||||||
|
.where(baseConditions)
|
||||||
|
.limit(DISTINCT_LIMIT+1)
|
||||||
|
]);
|
||||||
|
|
||||||
// Get unique actors
|
if (
|
||||||
const uniquePaths = await db
|
uniqueActors.length > DISTINCT_LIMIT ||
|
||||||
.selectDistinct({
|
uniqueLocations.length > DISTINCT_LIMIT ||
|
||||||
paths: requestAuditLog.path
|
uniqueHosts.length > DISTINCT_LIMIT ||
|
||||||
})
|
uniquePaths.length > DISTINCT_LIMIT ||
|
||||||
.from(requestAuditLog)
|
uniqueResources.length > DISTINCT_LIMIT
|
||||||
.where(baseConditions);
|
) {
|
||||||
|
throw new Error("Too many distinct filter attributes to retrieve. Please refine your time range.");
|
||||||
// Get unique resources with names
|
}
|
||||||
const uniqueResources = await db
|
|
||||||
.selectDistinct({
|
|
||||||
id: requestAuditLog.resourceId,
|
|
||||||
name: resources.name
|
|
||||||
})
|
|
||||||
.from(requestAuditLog)
|
|
||||||
.leftJoin(
|
|
||||||
resources,
|
|
||||||
eq(requestAuditLog.resourceId, resources.resourceId)
|
|
||||||
)
|
|
||||||
.where(baseConditions);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actors: uniqueActors
|
actors: uniqueActors
|
||||||
@@ -295,6 +311,12 @@ export async function queryRequestAuditLogs(
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
// if the message is "Too many distinct filter attributes to retrieve. Please refine your time range.", return a 400 and the message
|
||||||
|
if (error instanceof Error && error.message === "Too many distinct filter attributes to retrieve. Please refine your time range.") {
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.BAD_REQUEST, error.message)
|
||||||
|
);
|
||||||
|
}
|
||||||
return next(
|
return next(
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ export type GetCertificateResponse = {
|
|||||||
status: string; // pending, requested, valid, expired, failed
|
status: string; // pending, requested, valid, expired, failed
|
||||||
expiresAt: string | null;
|
expiresAt: string | null;
|
||||||
lastRenewalAttempt: Date | null;
|
lastRenewalAttempt: Date | null;
|
||||||
createdAt: string;
|
createdAt: number;
|
||||||
updatedAt: string;
|
updatedAt: number;
|
||||||
errorMessage?: string | null;
|
errorMessage?: string | null;
|
||||||
renewalCount: number;
|
renewalCount: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -194,11 +194,23 @@ export async function getOlmToken(
|
|||||||
.where(inArray(exitNodes.exitNodeId, exitNodeIds));
|
.where(inArray(exitNodes.exitNodeId, exitNodeIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map exitNodeId to siteIds
|
||||||
|
const exitNodeIdToSiteIds: Record<number, number[]> = {};
|
||||||
|
for (const { sites: site } of clientSites) {
|
||||||
|
if (site.exitNodeId !== null) {
|
||||||
|
if (!exitNodeIdToSiteIds[site.exitNodeId]) {
|
||||||
|
exitNodeIdToSiteIds[site.exitNodeId] = [];
|
||||||
|
}
|
||||||
|
exitNodeIdToSiteIds[site.exitNodeId].push(site.siteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const exitNodesHpData = allExitNodes.map((exitNode: ExitNode) => {
|
const exitNodesHpData = allExitNodes.map((exitNode: ExitNode) => {
|
||||||
return {
|
return {
|
||||||
publicKey: exitNode.publicKey,
|
publicKey: exitNode.publicKey,
|
||||||
relayPort: config.getRawConfig().gerbil.clients_start_port,
|
relayPort: config.getRawConfig().gerbil.clients_start_port,
|
||||||
endpoint: exitNode.endpoint
|
endpoint: exitNode.endpoint,
|
||||||
|
siteIds: exitNodeIdToSiteIds[exitNode.exitNodeId] ?? []
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -303,6 +303,24 @@ export default function Page() {
|
|||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
|
<div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{t("idpType")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<StrategySelect
|
||||||
|
options={providerTypes}
|
||||||
|
defaultValue={form.getValues("type")}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleProviderChange(
|
||||||
|
value as "oidc" | "google" | "azure"
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
cols={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<SettingsSectionForm>
|
<SettingsSectionForm>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
@@ -331,24 +349,6 @@ export default function Page() {
|
|||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</SettingsSectionForm>
|
</SettingsSectionForm>
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className="mb-2">
|
|
||||||
<span className="text-sm font-medium">
|
|
||||||
{t("idpType")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<StrategySelect
|
|
||||||
options={providerTypes}
|
|
||||||
defaultValue={form.getValues("type")}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleProviderChange(
|
|
||||||
value as "oidc" | "google" | "azure"
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
cols={3}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
|
|||||||
@@ -1312,6 +1312,35 @@ export default function Page() {
|
|||||||
</SettingsSectionTitle>
|
</SettingsSectionTitle>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
|
{resourceTypes.length > 1 && (
|
||||||
|
<>
|
||||||
|
<div className="mb-2">
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{t("type")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<StrategySelect
|
||||||
|
options={resourceTypes}
|
||||||
|
defaultValue="http"
|
||||||
|
onChange={(value) => {
|
||||||
|
baseForm.setValue(
|
||||||
|
"http",
|
||||||
|
value === "http"
|
||||||
|
);
|
||||||
|
// Update method default when switching resource type
|
||||||
|
addTargetForm.setValue(
|
||||||
|
"method",
|
||||||
|
value === "http"
|
||||||
|
? "http"
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
cols={2}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<SettingsSectionForm>
|
<SettingsSectionForm>
|
||||||
<Form {...baseForm}>
|
<Form {...baseForm}>
|
||||||
<form
|
<form
|
||||||
@@ -1348,35 +1377,6 @@ export default function Page() {
|
|||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</SettingsSectionForm>
|
</SettingsSectionForm>
|
||||||
|
|
||||||
{resourceTypes.length > 1 && (
|
|
||||||
<>
|
|
||||||
<div className="mb-2">
|
|
||||||
<span className="text-sm font-medium">
|
|
||||||
{t("type")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<StrategySelect
|
|
||||||
options={resourceTypes}
|
|
||||||
defaultValue="http"
|
|
||||||
onChange={(value) => {
|
|
||||||
baseForm.setValue(
|
|
||||||
"http",
|
|
||||||
value === "http"
|
|
||||||
);
|
|
||||||
// Update method default when switching resource type
|
|
||||||
addTargetForm.setValue(
|
|
||||||
"method",
|
|
||||||
value === "http"
|
|
||||||
? "http"
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
cols={2}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
@@ -1684,7 +1684,7 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center p-4">
|
<div className="text-center py-8 border-2 border-dashed border-muted rounded-lg p-4">
|
||||||
<p className="text-muted-foreground mb-4">
|
<p className="text-muted-foreground mb-4">
|
||||||
{t("targetNoOne")}
|
{t("targetNoOne")}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -674,6 +674,26 @@ WantedBy=default.target`
|
|||||||
</SettingsSectionTitle>
|
</SettingsSectionTitle>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
|
{tunnelTypes.length > 1 && (
|
||||||
|
<>
|
||||||
|
<div className="mb-2">
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{t("type")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<StrategySelect
|
||||||
|
options={tunnelTypes}
|
||||||
|
defaultValue={form.getValues(
|
||||||
|
"method"
|
||||||
|
)}
|
||||||
|
onChange={(value) => {
|
||||||
|
form.setValue("method", value);
|
||||||
|
}}
|
||||||
|
cols={3}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
@@ -748,26 +768,6 @@ WantedBy=default.target`
|
|||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
{tunnelTypes.length > 1 && (
|
|
||||||
<>
|
|
||||||
<div className="mb-2">
|
|
||||||
<span className="text-sm font-medium">
|
|
||||||
{t("type")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<StrategySelect
|
|
||||||
options={tunnelTypes}
|
|
||||||
defaultValue={form.getValues(
|
|
||||||
"method"
|
|
||||||
)}
|
|
||||||
onChange={(value) => {
|
|
||||||
form.setValue("method", value);
|
|
||||||
}}
|
|
||||||
cols={3}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
|
|||||||
@@ -209,22 +209,22 @@ export default function Page() {
|
|||||||
</Form>
|
</Form>
|
||||||
</SettingsSectionForm>
|
</SettingsSectionForm>
|
||||||
|
|
||||||
<div>
|
{/* <div> */}
|
||||||
<div className="mb-2">
|
{/* <div className="mb-2"> */}
|
||||||
<span className="text-sm font-medium">
|
{/* <span className="text-sm font-medium"> */}
|
||||||
{t("idpType")}
|
{/* {t("idpType")} */}
|
||||||
</span>
|
{/* </span> */}
|
||||||
</div>
|
{/* </div> */}
|
||||||
|
{/* */}
|
||||||
<StrategySelect
|
{/* <StrategySelect */}
|
||||||
options={providerTypes}
|
{/* options={providerTypes} */}
|
||||||
defaultValue={form.getValues("type")}
|
{/* defaultValue={form.getValues("type")} */}
|
||||||
onChange={(value) => {
|
{/* onChange={(value) => { */}
|
||||||
form.setValue("type", value as "oidc");
|
{/* form.setValue("type", value as "oidc"); */}
|
||||||
}}
|
{/* }} */}
|
||||||
cols={3}
|
{/* cols={3} */}
|
||||||
/>
|
{/* /> */}
|
||||||
</div>
|
{/* </div> */}
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
|
|||||||
@@ -546,6 +546,7 @@ export default function ResetPasswordForm({
|
|||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
variant="outline"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const email =
|
const email =
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function SettingsSectionForm({
|
|||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={cn("max-w-xl space-y-4", className)}>{children}</div>
|
<div className={cn("md:max-w-1/2 space-y-4", className)}>{children}</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,13 +59,14 @@ export default function CertificateStatus({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldShowRefreshButton = (status: string, updatedAt: string) => {
|
const shouldShowRefreshButton = (status: string, updatedAt: number) => {
|
||||||
return (
|
return (
|
||||||
status === "failed" ||
|
status === "failed" ||
|
||||||
status === "expired" ||
|
status === "expired" ||
|
||||||
(status === "requested" &&
|
(status === "requested" &&
|
||||||
updatedAt &&
|
updatedAt &&
|
||||||
new Date(updatedAt).getTime() < Date.now() - 5 * 60 * 1000)
|
new Date(updatedAt * 1000).getTime() <
|
||||||
|
Date.now() - 5 * 60 * 1000)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user