Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions messages/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,8 @@
"proxyErrorInvalidHeader": "Invalid custom Host Header value. Use domain name format, or save empty to unset custom Host Header.",
"proxyErrorTls": "Invalid TLS Server Name. Use domain name format, or save empty to remove the TLS Server Name.",
"proxyEnableSSL": "Enable SSL (https)",
"target": "Target",
"configureTarget": "Configure Targets",
"targetErrorFetch": "Failed to fetch targets",
"targetErrorFetchDescription": "An error occurred while fetching targets",
"siteErrorFetch": "Failed to fetch resource",
Expand Down
268 changes: 131 additions & 137 deletions src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx

Large diffs are not rendered by default.

141 changes: 70 additions & 71 deletions src/app/[orgId]/settings/resources/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ import { DomainRow } from "../../../../../components/DomainsTable";
import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@app/components/ui/tooltip";
import { PathMatchDisplay, PathMatchModal, PathRewriteDisplay, PathRewriteModal } from "@app/components/PathMatchRenameModal";
import { TargetModal } from "@app/components/TargetModal";
import { TargetDisplay } from "@app/components/TargetDisplay";


const baseResourceFormSchema = z.object({
Expand Down Expand Up @@ -810,87 +812,84 @@ export default function Page() {
);
}
},
...(baseForm.watch("http")
? [
{
accessorKey: "method",
header: t("method"),
cell: ({ row }: { row: Row<LocalTarget> }) => (
<Select
defaultValue={row.original.method ?? ""}
onValueChange={(value) =>
{
accessorKey: "target",
header: t("target"),
cell: ({ row }) => {
const hasTarget = !!(row.original.ip || row.original.port || row.original.method);

return hasTarget ? (
<div className="flex items-center gap-1">
<TargetModal
value={{
method: row.original.method,
ip: row.original.ip,
port: row.original.port
}}
onChange={(config) =>
updateTarget(row.original.targetId, {
...row.original,
method: value
...config
})
}
>
<SelectTrigger>
{row.original.method}
</SelectTrigger>
<SelectContent>
<SelectItem value="http">http</SelectItem>
<SelectItem value="https">https</SelectItem>
<SelectItem value="h2c">h2c</SelectItem>
</SelectContent>
</Select>
)
}
]
: []),
{
accessorKey: "ip",
header: t("targetAddr"),
cell: ({ row }) => (
<Input
defaultValue={row.original.ip}
className="min-w-[150px]"
onBlur={(e) => {
const input = e.target.value.trim();
const hasProtocol = /^(https?|h2c):\/\//.test(input);
const hasPort = /:\d+(?:\/|$)/.test(input);

if (hasProtocol || hasPort) {
const parsed = parseHostTarget(input);
if (parsed) {
updateTarget(row.original.targetId, {
...row.original,
method: hasProtocol ? parsed.protocol : row.original.method,
ip: parsed.host,
port: hasPort ? parsed.port : row.original.port
});
} else {
showMethod={baseForm.watch("http")}
trigger={
<Button
variant="outline"
className="flex items-center gap-2 max-w-md text-left cursor-pointer"
>
<TargetDisplay
value={{
method: row.original.method,
ip: row.original.ip,
port: row.original.port
}}
showMethod={baseForm.watch("http")}
/>
</Button>
}
/>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
onClick={(e) => {
e.stopPropagation();
updateTarget(row.original.targetId, {
...row.original,
ip: input
method: null,
ip: "",
port: undefined
});
}
} else {
}}
>
×
</Button>
<MoveRight className="mr-2 h-4 w-4" />
</div>
) : (
<TargetModal
value={{
method: row.original.method,
ip: row.original.ip,
port: row.original.port
}}
onChange={(config) =>
updateTarget(row.original.targetId, {
...row.original,
ip: input
});
...config
})
}
}}
/>
)
},
{
accessorKey: "port",
header: t("targetPort"),
cell: ({ row }) => (
<Input
type="number"
defaultValue={row.original.port}
className="min-w-[100px]"
onBlur={(e) =>
updateTarget(row.original.targetId, {
...row.original,
port: parseInt(e.target.value, 10)
})
}
/>
)
showMethod={baseForm.watch("http")}
trigger={
<Button variant="outline">
<Plus className="h-4 w-4 mr-2" />
{t("configureTarget")}
</Button>
}
/>
);
}
},
{
accessorKey: "rewritePath",
Expand Down
8 changes: 7 additions & 1 deletion src/components/AdminIdpDataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import { useTranslations } from "next-intl";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
onRefresh?: () => void;
isRefreshing?: boolean;
}

export function IdpDataTable<TData, TValue>({
columns,
data
data,
onRefresh,
isRefreshing
}: DataTableProps<TData, TValue>) {
const router = useRouter();
const t = useTranslations();
Expand All @@ -29,6 +33,8 @@ export function IdpDataTable<TData, TValue>({
onAdd={() => {
router.push("/admin/idp/create");
}}
onRefresh={onRefresh}
isRefreshing={isRefreshing}
/>
);
}
25 changes: 24 additions & 1 deletion src/components/AdminIdpTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,26 @@ export default function IdpTable({ idps }: Props) {
const [selectedIdp, setSelectedIdp] = useState<IdpRow | null>(null);
const api = createApiClient(useEnvContext());
const router = useRouter();
const [isRefreshing, setIsRefreshing] = useState(false);
const t = useTranslations();

const refreshData = async () => {
console.log("Data refreshed");
setIsRefreshing(true);
try {
await new Promise((resolve) => setTimeout(resolve, 200));
router.refresh();
} catch (error) {
toast({
title: t("error"),
description: t("refreshError"),
variant: "destructive"
});
} finally {
setIsRefreshing(false);
}
};

const deleteIdp = async (idpId: number) => {
try {
await api.delete(`/idp/${idpId}`);
Expand Down Expand Up @@ -194,7 +212,12 @@ export default function IdpTable({ idps }: Props) {
/>
)}

<IdpDataTable columns={columns} data={idps} />
<IdpDataTable
columns={columns}
data={idps}
onRefresh={refreshData}
isRefreshing={isRefreshing}
/>
</>
);
}
8 changes: 7 additions & 1 deletion src/components/AdminUsersDataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import { useTranslations } from "next-intl";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
onRefresh?: () => void;
isRefreshing?: boolean;
}

export function UsersDataTable<TData, TValue>({
columns,
data
data,
onRefresh,
isRefreshing
}: DataTableProps<TData, TValue>) {

const t = useTranslations();
Expand All @@ -26,6 +30,8 @@ export function UsersDataTable<TData, TValue>({
title={t('userServer')}
searchPlaceholder={t('userSearch')}
searchColumn="email"
onRefresh={onRefresh}
isRefreshing={isRefreshing}
/>
);
}
28 changes: 26 additions & 2 deletions src/components/AdminUsersTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,25 @@ export default function UsersTable({ users }: Props) {

const api = createApiClient(useEnvContext());

const [isRefreshing, setIsRefreshing] = useState(false);

const refreshData = async () => {
console.log("Data refreshed");
setIsRefreshing(true);
try {
await new Promise((resolve) => setTimeout(resolve, 200));
router.refresh();
} catch (error) {
toast({
title: t("error"),
description: t("refreshError"),
variant: "destructive"
});
} finally {
setIsRefreshing(false);
}
};

const deleteUser = (id: string) => {
api.delete(`/user/${id}`)
.catch((e) => {
Expand Down Expand Up @@ -168,7 +187,7 @@ export default function UsersTable({ users }: Props) {
<div className="flex flex-row items-center gap-2">
<span>
{userRow.twoFactorEnabled ||
userRow.twoFactorSetupRequested ? (
userRow.twoFactorSetupRequested ? (
<span className="text-green-500">
{t("enabled")}
</span>
Expand Down Expand Up @@ -263,7 +282,12 @@ export default function UsersTable({ users }: Props) {
/>
)}

<UsersDataTable columns={columns} data={rows} />
<UsersDataTable
columns={columns}
data={rows}
onRefresh={refreshData}
isRefreshing={isRefreshing}
/>
</>
);
}
10 changes: 8 additions & 2 deletions src/components/ApiKeysDataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,20 @@ interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
addApiKey?: () => void;
onRefresh?: () => void;
isRefreshing?: boolean;
}

export function ApiKeysDataTable<TData, TValue>({
addApiKey,
columns,
data
data,
onRefresh,
isRefreshing
}: DataTableProps<TData, TValue>) {

const t = useTranslations();

return (
<DataTable
columns={columns}
Expand All @@ -53,6 +57,8 @@ export function ApiKeysDataTable<TData, TValue>({
searchColumn="name"
onAdd={addApiKey}
addButtonText={t('apiKeysAdd')}
onRefresh={onRefresh}
isRefreshing={isRefreshing}
/>
);
}
21 changes: 21 additions & 0 deletions src/components/ApiKeysTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,25 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {

const t = useTranslations();

const [isRefreshing, setIsRefreshing] = useState(false);

const refreshData = async () => {
console.log("Data refreshed");
setIsRefreshing(true);
try {
await new Promise((resolve) => setTimeout(resolve, 200));
router.refresh();
} catch (error) {
toast({
title: t("error"),
description: t("refreshError"),
variant: "destructive"
});
} finally {
setIsRefreshing(false);
}
};

const deleteSite = (apiKeyId: string) => {
api.delete(`/api-key/${apiKeyId}`)
.catch((e) => {
Expand Down Expand Up @@ -186,6 +205,8 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
addApiKey={() => {
router.push(`/admin/api-keys/create`);
}}
onRefresh={refreshData}
isRefreshing={isRefreshing}
/>
</>
);
Expand Down
Loading