mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-09-23 23:10:34 +00:00
API lib cleanup, 404 hosts WIP
This commit is contained in:
@@ -1,13 +1,10 @@
|
||||
import * as api from "./base";
|
||||
import type { AccessList } from "./models";
|
||||
|
||||
export async function createAccessList(item: AccessList, abortController?: AbortController): Promise<AccessList> {
|
||||
return await api.post(
|
||||
{
|
||||
url: "/nginx/access-lists",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function createAccessList(item: AccessList): Promise<AccessList> {
|
||||
return await api.post({
|
||||
url: "/nginx/access-lists",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
});
|
||||
}
|
||||
|
@@ -1,13 +1,10 @@
|
||||
import * as api from "./base";
|
||||
import type { Certificate } from "./models";
|
||||
|
||||
export async function createCertificate(item: Certificate, abortController?: AbortController): Promise<Certificate> {
|
||||
return await api.post(
|
||||
{
|
||||
url: "/nginx/certificates",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function createCertificate(item: Certificate): Promise<Certificate> {
|
||||
return await api.post({
|
||||
url: "/nginx/certificates",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
});
|
||||
}
|
||||
|
@@ -1,13 +1,10 @@
|
||||
import * as api from "./base";
|
||||
import type { DeadHost } from "./models";
|
||||
|
||||
export async function createDeadHost(item: DeadHost, abortController?: AbortController): Promise<DeadHost> {
|
||||
return await api.post(
|
||||
{
|
||||
url: "/nginx/dead-hosts",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function createDeadHost(item: DeadHost): Promise<DeadHost> {
|
||||
return await api.post({
|
||||
url: "/nginx/dead-hosts",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
});
|
||||
}
|
||||
|
@@ -1,13 +1,10 @@
|
||||
import * as api from "./base";
|
||||
import type { ProxyHost } from "./models";
|
||||
|
||||
export async function createProxyHost(item: ProxyHost, abortController?: AbortController): Promise<ProxyHost> {
|
||||
return await api.post(
|
||||
{
|
||||
url: "/nginx/proxy-hosts",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function createProxyHost(item: ProxyHost): Promise<ProxyHost> {
|
||||
return await api.post({
|
||||
url: "/nginx/proxy-hosts",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
});
|
||||
}
|
||||
|
@@ -1,16 +1,10 @@
|
||||
import * as api from "./base";
|
||||
import type { RedirectionHost } from "./models";
|
||||
|
||||
export async function createRedirectionHost(
|
||||
item: RedirectionHost,
|
||||
abortController?: AbortController,
|
||||
): Promise<RedirectionHost> {
|
||||
return await api.post(
|
||||
{
|
||||
url: "/nginx/redirection-hosts",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function createRedirectionHost(item: RedirectionHost): Promise<RedirectionHost> {
|
||||
return await api.post({
|
||||
url: "/nginx/redirection-hosts",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
});
|
||||
}
|
||||
|
@@ -1,13 +1,10 @@
|
||||
import * as api from "./base";
|
||||
import type { Stream } from "./models";
|
||||
|
||||
export async function createStream(item: Stream, abortController?: AbortController): Promise<Stream> {
|
||||
return await api.post(
|
||||
{
|
||||
url: "/nginx/streams",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function createStream(item: Stream): Promise<Stream> {
|
||||
return await api.post({
|
||||
url: "/nginx/streams",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
});
|
||||
}
|
||||
|
@@ -15,14 +15,11 @@ export interface NewUser {
|
||||
roles?: string[];
|
||||
}
|
||||
|
||||
export async function createUser(item: NewUser, noAuth?: boolean, abortController?: AbortController): Promise<User> {
|
||||
return await api.post(
|
||||
{
|
||||
url: "/users",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
noAuth,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function createUser(item: NewUser, noAuth?: boolean): Promise<User> {
|
||||
return await api.post({
|
||||
url: "/users",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
noAuth,
|
||||
});
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
import * as api from "./base";
|
||||
|
||||
export async function deleteAccessList(id: number, abortController?: AbortController): Promise<boolean> {
|
||||
return await api.del(
|
||||
{
|
||||
url: `/nginx/access-lists/${id}`,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function deleteAccessList(id: number): Promise<boolean> {
|
||||
return await api.del({
|
||||
url: `/nginx/access-lists/${id}`,
|
||||
});
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
import * as api from "./base";
|
||||
|
||||
export async function deleteCertificate(id: number, abortController?: AbortController): Promise<boolean> {
|
||||
return await api.del(
|
||||
{
|
||||
url: `/nginx/certificates/${id}`,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function deleteCertificate(id: number): Promise<boolean> {
|
||||
return await api.del({
|
||||
url: `/nginx/certificates/${id}`,
|
||||
});
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
import * as api from "./base";
|
||||
|
||||
export async function deleteDeadHost(id: number, abortController?: AbortController): Promise<boolean> {
|
||||
return await api.del(
|
||||
{
|
||||
url: `/nginx/dead-hosts/${id}`,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function deleteDeadHost(id: number): Promise<boolean> {
|
||||
return await api.del({
|
||||
url: `/nginx/dead-hosts/${id}`,
|
||||
});
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
import * as api from "./base";
|
||||
|
||||
export async function deleteProxyHost(id: number, abortController?: AbortController): Promise<boolean> {
|
||||
return await api.del(
|
||||
{
|
||||
url: `/nginx/proxy-hosts/${id}`,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function deleteProxyHost(id: number): Promise<boolean> {
|
||||
return await api.del({
|
||||
url: `/nginx/proxy-hosts/${id}`,
|
||||
});
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
import * as api from "./base";
|
||||
|
||||
export async function deleteRedirectionHost(id: number, abortController?: AbortController): Promise<boolean> {
|
||||
return await api.del(
|
||||
{
|
||||
url: `/nginx/redirection-hosts/${id}`,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function deleteRedirectionHost(id: number): Promise<boolean> {
|
||||
return await api.del({
|
||||
url: `/nginx/redirection-hosts/${id}`,
|
||||
});
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
import * as api from "./base";
|
||||
|
||||
export async function deleteStream(id: number, abortController?: AbortController): Promise<boolean> {
|
||||
return await api.del(
|
||||
{
|
||||
url: `/nginx/streams/${id}`,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function deleteStream(id: number): Promise<boolean> {
|
||||
return await api.del({
|
||||
url: `/nginx/streams/${id}`,
|
||||
});
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
import * as api from "./base";
|
||||
|
||||
export async function deleteUser(id: number, abortController?: AbortController): Promise<boolean> {
|
||||
return await api.del(
|
||||
{
|
||||
url: `/users/${id}`,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function deleteUser(id: number): Promise<boolean> {
|
||||
return await api.del({
|
||||
url: `/users/${id}`,
|
||||
});
|
||||
}
|
||||
|
@@ -1,11 +1,8 @@
|
||||
import * as api from "./base";
|
||||
import type { Binary } from "./responseTypes";
|
||||
|
||||
export async function downloadCertificate(id: number, abortController?: AbortController): Promise<Binary> {
|
||||
return await api.get(
|
||||
{
|
||||
url: `/nginx/certificates/${id}/download`,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function downloadCertificate(id: number): Promise<Binary> {
|
||||
return await api.get({
|
||||
url: `/nginx/certificates/${id}/download`,
|
||||
});
|
||||
}
|
||||
|
6
frontend/src/api/backend/expansions.ts
Normal file
6
frontend/src/api/backend/expansions.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export type AccessListExpansion = "owner" | "items" | "clients";
|
||||
export type AuditLogExpansion = "user";
|
||||
export type CertificateExpansion = "owner" | "proxy_hosts" | "redirection_hosts" | "dead_hosts";
|
||||
export type HostExpansion = "owner" | "certificate";
|
||||
export type ProxyHostExpansion = "owner" | "access_list" | "certificate";
|
||||
export type UserExpansion = "permissions";
|
@@ -1,11 +1,13 @@
|
||||
import * as api from "./base";
|
||||
import type { AccessListExpansion } from "./expansions";
|
||||
import type { AccessList } from "./models";
|
||||
|
||||
export async function getAccessList(id: number, abortController?: AbortController): Promise<AccessList> {
|
||||
return await api.get(
|
||||
{
|
||||
url: `/nginx/access-lists/${id}`,
|
||||
export async function getAccessList(id: number, expand?: AccessListExpansion[], params = {}): Promise<AccessList> {
|
||||
return await api.get({
|
||||
url: `/nginx/access-lists/${id}`,
|
||||
params: {
|
||||
expand: expand?.join(","),
|
||||
...params,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import * as api from "./base";
|
||||
import type { AccessListExpansion } from "./expansions";
|
||||
import type { AccessList } from "./models";
|
||||
|
||||
export type AccessListExpansion = "owner" | "items" | "clients";
|
||||
|
||||
export async function getAccessLists(expand?: AccessListExpansion[], params = {}): Promise<AccessList[]> {
|
||||
return await api.get({
|
||||
url: "/nginx/access-lists",
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import * as api from "./base";
|
||||
import type { AuditLogExpansion } from "./getAuditLogs";
|
||||
import type { AuditLogExpansion } from "./expansions";
|
||||
import type { AuditLog } from "./models";
|
||||
|
||||
export async function getAuditLog(id: number, expand?: AuditLogExpansion[], params = {}): Promise<AuditLog> {
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import * as api from "./base";
|
||||
import type { AuditLogExpansion } from "./expansions";
|
||||
import type { AuditLog } from "./models";
|
||||
|
||||
export type AuditLogExpansion = "user";
|
||||
|
||||
export async function getAuditLogs(expand?: AuditLogExpansion[], params = {}): Promise<AuditLog[]> {
|
||||
return await api.get({
|
||||
url: "/audit-log",
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import * as api from "./base";
|
||||
import type { CertificateExpansion } from "./expansions";
|
||||
import type { Certificate } from "./models";
|
||||
|
||||
export async function getCertificate(id: number, abortController?: AbortController): Promise<Certificate> {
|
||||
return await api.get(
|
||||
{
|
||||
url: `/nginx/certificates/${id}`,
|
||||
export async function getCertificate(id: number, expand?: CertificateExpansion[], params = {}): Promise<Certificate> {
|
||||
return await api.get({
|
||||
url: `/nginx/certificates/${id}`,
|
||||
params: {
|
||||
expand: expand?.join(","),
|
||||
...params,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import * as api from "./base";
|
||||
import type { CertificateExpansion } from "./expansions";
|
||||
import type { Certificate } from "./models";
|
||||
|
||||
export type CertificateExpansion = "owner" | "proxy_hosts" | "redirection_hosts" | "dead_hosts";
|
||||
|
||||
export async function getCertificates(expand?: CertificateExpansion[], params = {}): Promise<Certificate[]> {
|
||||
return await api.get({
|
||||
url: "/nginx/certificates",
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import * as api from "./base";
|
||||
import type { HostExpansion } from "./expansions";
|
||||
import type { DeadHost } from "./models";
|
||||
|
||||
export async function getDeadHost(id: number, abortController?: AbortController): Promise<DeadHost> {
|
||||
return await api.get(
|
||||
{
|
||||
url: `/nginx/dead-hosts/${id}`,
|
||||
export async function getDeadHost(id: number, expand?: HostExpansion[], params = {}): Promise<DeadHost> {
|
||||
return await api.get({
|
||||
url: `/nginx/dead-hosts/${id}`,
|
||||
params: {
|
||||
expand: expand?.join(","),
|
||||
...params,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import * as api from "./base";
|
||||
import type { HostExpansion } from "./expansions";
|
||||
import type { DeadHost } from "./models";
|
||||
|
||||
export type DeadHostExpansion = "owner" | "certificate";
|
||||
|
||||
export async function getDeadHosts(expand?: DeadHostExpansion[], params = {}): Promise<DeadHost[]> {
|
||||
export async function getDeadHosts(expand?: HostExpansion[], params = {}): Promise<DeadHost[]> {
|
||||
return await api.get({
|
||||
url: "/nginx/dead-hosts",
|
||||
params: {
|
||||
|
@@ -1,11 +1,8 @@
|
||||
import * as api from "./base";
|
||||
import type { HealthResponse } from "./responseTypes";
|
||||
|
||||
export async function getHealth(abortController?: AbortController): Promise<HealthResponse> {
|
||||
return await api.get(
|
||||
{
|
||||
url: "/",
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function getHealth(): Promise<HealthResponse> {
|
||||
return await api.get({
|
||||
url: "/",
|
||||
});
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
import * as api from "./base";
|
||||
|
||||
export async function getHostsReport(abortController?: AbortController): Promise<Record<string, number>> {
|
||||
return await api.get(
|
||||
{
|
||||
url: "/reports/hosts",
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function getHostsReport(): Promise<Record<string, number>> {
|
||||
return await api.get({
|
||||
url: "/reports/hosts",
|
||||
});
|
||||
}
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import * as api from "./base";
|
||||
import type { ProxyHostExpansion } from "./expansions";
|
||||
import type { ProxyHost } from "./models";
|
||||
|
||||
export async function getProxyHost(id: number, abortController?: AbortController): Promise<ProxyHost> {
|
||||
return await api.get(
|
||||
{
|
||||
url: `/nginx/proxy-hosts/${id}`,
|
||||
export async function getProxyHost(id: number, expand?: ProxyHostExpansion[], params = {}): Promise<ProxyHost> {
|
||||
return await api.get({
|
||||
url: `/nginx/proxy-hosts/${id}`,
|
||||
params: {
|
||||
expand: expand?.join(","),
|
||||
...params,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import * as api from "./base";
|
||||
import type { ProxyHostExpansion } from "./expansions";
|
||||
import type { ProxyHost } from "./models";
|
||||
|
||||
export type ProxyHostExpansion = "owner" | "access_list" | "certificate";
|
||||
|
||||
export async function getProxyHosts(expand?: ProxyHostExpansion[], params = {}): Promise<ProxyHost[]> {
|
||||
return await api.get({
|
||||
url: "/nginx/proxy-hosts",
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import * as api from "./base";
|
||||
import type { HostExpansion } from "./expansions";
|
||||
import type { ProxyHost } from "./models";
|
||||
|
||||
export async function getRedirectionHost(id: number, abortController?: AbortController): Promise<ProxyHost> {
|
||||
return await api.get(
|
||||
{
|
||||
url: `/nginx/redirection-hosts/${id}`,
|
||||
export async function getRedirectionHost(id: number, expand?: HostExpansion[], params = {}): Promise<ProxyHost> {
|
||||
return await api.get({
|
||||
url: `/nginx/redirection-hosts/${id}`,
|
||||
params: {
|
||||
expand: expand?.join(","),
|
||||
...params,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@@ -1,11 +1,8 @@
|
||||
import * as api from "./base";
|
||||
import type { HostExpansion } from "./expansions";
|
||||
import type { RedirectionHost } from "./models";
|
||||
|
||||
export type RedirectionHostExpansion = "owner" | "certificate";
|
||||
export async function getRedirectionHosts(
|
||||
expand?: RedirectionHostExpansion[],
|
||||
params = {},
|
||||
): Promise<RedirectionHost[]> {
|
||||
export async function getRedirectionHosts(expand?: HostExpansion[], params = {}): Promise<RedirectionHost[]> {
|
||||
return await api.get({
|
||||
url: "/nginx/redirection-hosts",
|
||||
params: {
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import * as api from "./base";
|
||||
import type { Setting } from "./models";
|
||||
|
||||
export async function getSetting(id: string, abortController?: AbortController): Promise<Setting> {
|
||||
return await api.get(
|
||||
{
|
||||
url: `/settings/${id}`,
|
||||
export async function getSetting(id: string, expand?: string[], params = {}): Promise<Setting> {
|
||||
return await api.get({
|
||||
url: `/settings/${id}`,
|
||||
params: {
|
||||
expand: expand?.join(","),
|
||||
...params,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import * as api from "./base";
|
||||
import type { HostExpansion } from "./expansions";
|
||||
import type { Stream } from "./models";
|
||||
|
||||
export async function getStream(id: number, abortController?: AbortController): Promise<Stream> {
|
||||
return await api.get(
|
||||
{
|
||||
url: `/nginx/streams/${id}`,
|
||||
export async function getStream(id: number, expand?: HostExpansion[], params = {}): Promise<Stream> {
|
||||
return await api.get({
|
||||
url: `/nginx/streams/${id}`,
|
||||
params: {
|
||||
expand: expand?.join(","),
|
||||
...params,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import * as api from "./base";
|
||||
import type { HostExpansion } from "./expansions";
|
||||
import type { Stream } from "./models";
|
||||
|
||||
export type StreamExpansion = "owner" | "certificate";
|
||||
|
||||
export async function getStreams(expand?: StreamExpansion[], params = {}): Promise<Stream[]> {
|
||||
export async function getStreams(expand?: HostExpansion[], params = {}): Promise<Stream[]> {
|
||||
return await api.get({
|
||||
url: "/nginx/streams",
|
||||
params: {
|
||||
|
@@ -1,19 +1,9 @@
|
||||
import * as api from "./base";
|
||||
import type { TokenResponse } from "./responseTypes";
|
||||
|
||||
interface Options {
|
||||
payload: {
|
||||
identity: string;
|
||||
secret: string;
|
||||
};
|
||||
}
|
||||
|
||||
export async function getToken({ payload }: Options, abortController?: AbortController): Promise<TokenResponse> {
|
||||
return await api.post(
|
||||
{
|
||||
url: "/tokens",
|
||||
data: payload,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function getToken(identity: string, secret: string): Promise<TokenResponse> {
|
||||
return await api.post({
|
||||
url: "/tokens",
|
||||
data: { identity, secret },
|
||||
});
|
||||
}
|
||||
|
@@ -1,10 +1,14 @@
|
||||
import * as api from "./base";
|
||||
import type { UserExpansion } from "./expansions";
|
||||
import type { User } from "./models";
|
||||
|
||||
export async function getUser(id: number | string = "me", params = {}): Promise<User> {
|
||||
export async function getUser(id: number | string = "me", expand?: UserExpansion[], params = {}): Promise<User> {
|
||||
const userId = id ? id : "me";
|
||||
return await api.get({
|
||||
url: `/users/${userId}`,
|
||||
params,
|
||||
params: {
|
||||
expand: expand?.join(","),
|
||||
...params,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import * as api from "./base";
|
||||
import type { UserExpansion } from "./expansions";
|
||||
import type { User } from "./models";
|
||||
|
||||
export type UserExpansion = "permissions";
|
||||
|
||||
export async function getUsers(expand?: UserExpansion[], params = {}): Promise<User[]> {
|
||||
return await api.get({
|
||||
url: "/users",
|
||||
|
@@ -13,6 +13,7 @@ export * from "./deleteRedirectionHost";
|
||||
export * from "./deleteStream";
|
||||
export * from "./deleteUser";
|
||||
export * from "./downloadCertificate";
|
||||
export * from "./expansions";
|
||||
export * from "./getAccessList";
|
||||
export * from "./getAccessLists";
|
||||
export * from "./getAuditLog";
|
||||
|
@@ -1,11 +1,8 @@
|
||||
import * as api from "./base";
|
||||
import type { TokenResponse } from "./responseTypes";
|
||||
|
||||
export async function refreshToken(abortController?: AbortController): Promise<TokenResponse> {
|
||||
return await api.get(
|
||||
{
|
||||
url: "/tokens",
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function refreshToken(): Promise<TokenResponse> {
|
||||
return await api.get({
|
||||
url: "/tokens",
|
||||
});
|
||||
}
|
||||
|
@@ -1,11 +1,8 @@
|
||||
import * as api from "./base";
|
||||
import type { Certificate } from "./models";
|
||||
|
||||
export async function renewCertificate(id: number, abortController?: AbortController): Promise<Certificate> {
|
||||
return await api.post(
|
||||
{
|
||||
url: `/nginx/certificates/${id}/renew`,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function renewCertificate(id: number): Promise<Certificate> {
|
||||
return await api.post({
|
||||
url: `/nginx/certificates/${id}/renew`,
|
||||
});
|
||||
}
|
||||
|
@@ -1,17 +1,10 @@
|
||||
import * as api from "./base";
|
||||
import type { UserPermissions } from "./models";
|
||||
|
||||
export async function setPermissions(
|
||||
userId: number,
|
||||
data: UserPermissions,
|
||||
abortController?: AbortController,
|
||||
): Promise<boolean> {
|
||||
export async function setPermissions(userId: number, data: UserPermissions): Promise<boolean> {
|
||||
// Remove readonly fields
|
||||
return await api.put(
|
||||
{
|
||||
url: `/users/${userId}/permissions`,
|
||||
data,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
return await api.put({
|
||||
url: `/users/${userId}/permissions`,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
@@ -1,16 +1,10 @@
|
||||
import * as api from "./base";
|
||||
|
||||
export async function testHttpCertificate(
|
||||
domains: string[],
|
||||
abortController?: AbortController,
|
||||
): Promise<Record<string, string>> {
|
||||
return await api.get(
|
||||
{
|
||||
url: "/nginx/certificates/test-http",
|
||||
params: {
|
||||
domains: domains.join(","),
|
||||
},
|
||||
export async function testHttpCertificate(domains: string[]): Promise<Record<string, string>> {
|
||||
return await api.get({
|
||||
url: "/nginx/certificates/test-http",
|
||||
params: {
|
||||
domains: domains.join(","),
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@@ -1,14 +1,7 @@
|
||||
import * as api from "./base";
|
||||
|
||||
export async function toggleDeadHost(
|
||||
id: number,
|
||||
enabled: boolean,
|
||||
abortController?: AbortController,
|
||||
): Promise<boolean> {
|
||||
return await api.post(
|
||||
{
|
||||
url: `/nginx/dead-hosts/${id}/${enabled ? "enable" : "disable"}`,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function toggleDeadHost(id: number, enabled: boolean): Promise<boolean> {
|
||||
return await api.post({
|
||||
url: `/nginx/dead-hosts/${id}/${enabled ? "enable" : "disable"}`,
|
||||
});
|
||||
}
|
||||
|
@@ -1,14 +1,7 @@
|
||||
import * as api from "./base";
|
||||
|
||||
export async function toggleProxyHost(
|
||||
id: number,
|
||||
enabled: boolean,
|
||||
abortController?: AbortController,
|
||||
): Promise<boolean> {
|
||||
return await api.post(
|
||||
{
|
||||
url: `/nginx/proxy-hosts/${id}/${enabled ? "enable" : "disable"}`,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function toggleProxyHost(id: number, enabled: boolean): Promise<boolean> {
|
||||
return await api.post({
|
||||
url: `/nginx/proxy-hosts/${id}/${enabled ? "enable" : "disable"}`,
|
||||
});
|
||||
}
|
||||
|
@@ -1,14 +1,7 @@
|
||||
import * as api from "./base";
|
||||
|
||||
export async function toggleRedirectionHost(
|
||||
id: number,
|
||||
enabled: boolean,
|
||||
abortController?: AbortController,
|
||||
): Promise<boolean> {
|
||||
return await api.post(
|
||||
{
|
||||
url: `/nginx/redirection-hosts/${id}/${enabled ? "enable" : "disable"}`,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function toggleRedirectionHost(id: number, enabled: boolean): Promise<boolean> {
|
||||
return await api.post({
|
||||
url: `/nginx/redirection-hosts/${id}/${enabled ? "enable" : "disable"}`,
|
||||
});
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
import * as api from "./base";
|
||||
|
||||
export async function toggleStream(id: number, enabled: boolean, abortController?: AbortController): Promise<boolean> {
|
||||
return await api.post(
|
||||
{
|
||||
url: `/nginx/streams/${id}/${enabled ? "enable" : "disable"}`,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
export async function toggleStream(id: number, enabled: boolean): Promise<boolean> {
|
||||
return await api.post({
|
||||
url: `/nginx/streams/${id}/${enabled ? "enable" : "disable"}`,
|
||||
});
|
||||
}
|
||||
|
@@ -1,15 +1,12 @@
|
||||
import * as api from "./base";
|
||||
import type { AccessList } from "./models";
|
||||
|
||||
export async function updateAccessList(item: AccessList, abortController?: AbortController): Promise<AccessList> {
|
||||
export async function updateAccessList(item: AccessList): Promise<AccessList> {
|
||||
// Remove readonly fields
|
||||
const { id, createdOn: _, modifiedOn: __, ...data } = item;
|
||||
|
||||
return await api.put(
|
||||
{
|
||||
url: `/nginx/access-lists/${id}`,
|
||||
data: data,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
return await api.put({
|
||||
url: `/nginx/access-lists/${id}`,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
@@ -1,12 +1,7 @@
|
||||
import * as api from "./base";
|
||||
import type { User } from "./models";
|
||||
|
||||
export async function updateAuth(
|
||||
userId: number | "me",
|
||||
newPassword: string,
|
||||
current?: string,
|
||||
abortController?: AbortController,
|
||||
): Promise<User> {
|
||||
export async function updateAuth(userId: number | "me", newPassword: string, current?: string): Promise<User> {
|
||||
const data = {
|
||||
type: "password",
|
||||
current: current,
|
||||
@@ -16,11 +11,8 @@ export async function updateAuth(
|
||||
data.current = current;
|
||||
}
|
||||
|
||||
return await api.put(
|
||||
{
|
||||
url: `/users/${userId}/auth`,
|
||||
data,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
return await api.put({
|
||||
url: `/users/${userId}/auth`,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
@@ -1,15 +1,12 @@
|
||||
import * as api from "./base";
|
||||
import type { DeadHost } from "./models";
|
||||
|
||||
export async function updateDeadHost(item: DeadHost, abortController?: AbortController): Promise<DeadHost> {
|
||||
export async function updateDeadHost(item: DeadHost): Promise<DeadHost> {
|
||||
// Remove readonly fields
|
||||
const { id, createdOn: _, modifiedOn: __, ...data } = item;
|
||||
|
||||
return await api.put(
|
||||
{
|
||||
url: `/nginx/dead-hosts/${id}`,
|
||||
data: data,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
return await api.put({
|
||||
url: `/nginx/dead-hosts/${id}`,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
@@ -1,15 +1,12 @@
|
||||
import * as api from "./base";
|
||||
import type { ProxyHost } from "./models";
|
||||
|
||||
export async function updateProxyHost(item: ProxyHost, abortController?: AbortController): Promise<ProxyHost> {
|
||||
export async function updateProxyHost(item: ProxyHost): Promise<ProxyHost> {
|
||||
// Remove readonly fields
|
||||
const { id, createdOn: _, modifiedOn: __, ...data } = item;
|
||||
|
||||
return await api.put(
|
||||
{
|
||||
url: `/nginx/proxy-hosts/${id}`,
|
||||
data: data,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
return await api.put({
|
||||
url: `/nginx/proxy-hosts/${id}`,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
@@ -1,18 +1,12 @@
|
||||
import * as api from "./base";
|
||||
import type { RedirectionHost } from "./models";
|
||||
|
||||
export async function updateRedirectionHost(
|
||||
item: RedirectionHost,
|
||||
abortController?: AbortController,
|
||||
): Promise<RedirectionHost> {
|
||||
export async function updateRedirectionHost(item: RedirectionHost): Promise<RedirectionHost> {
|
||||
// Remove readonly fields
|
||||
const { id, createdOn: _, modifiedOn: __, ...data } = item;
|
||||
|
||||
return await api.put(
|
||||
{
|
||||
url: `/nginx/redirection-hosts/${id}`,
|
||||
data: data,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
return await api.put({
|
||||
url: `/nginx/redirection-hosts/${id}`,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
@@ -1,15 +1,12 @@
|
||||
import * as api from "./base";
|
||||
import type { Setting } from "./models";
|
||||
|
||||
export async function updateSetting(item: Setting, abortController?: AbortController): Promise<Setting> {
|
||||
export async function updateSetting(item: Setting): Promise<Setting> {
|
||||
// Remove readonly fields
|
||||
const { id, ...data } = item;
|
||||
|
||||
return await api.put(
|
||||
{
|
||||
url: `/settings/${id}`,
|
||||
data: data,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
return await api.put({
|
||||
url: `/settings/${id}`,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
@@ -1,15 +1,12 @@
|
||||
import * as api from "./base";
|
||||
import type { Stream } from "./models";
|
||||
|
||||
export async function updateStream(item: Stream, abortController?: AbortController): Promise<Stream> {
|
||||
export async function updateStream(item: Stream): Promise<Stream> {
|
||||
// Remove readonly fields
|
||||
const { id, createdOn: _, modifiedOn: __, ...data } = item;
|
||||
|
||||
return await api.put(
|
||||
{
|
||||
url: `/nginx/streams/${id}`,
|
||||
data: data,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
return await api.put({
|
||||
url: `/nginx/streams/${id}`,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
@@ -1,15 +1,12 @@
|
||||
import * as api from "./base";
|
||||
import type { User } from "./models";
|
||||
|
||||
export async function updateUser(item: User, abortController?: AbortController): Promise<User> {
|
||||
export async function updateUser(item: User): Promise<User> {
|
||||
// Remove readonly fields
|
||||
const { id, createdOn: _, modifiedOn: __, ...data } = item;
|
||||
|
||||
return await api.put(
|
||||
{
|
||||
url: `/users/${id}`,
|
||||
data: data,
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
return await api.put({
|
||||
url: `/users/${id}`,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
@@ -6,13 +6,9 @@ export async function uploadCertificate(
|
||||
certificate: string,
|
||||
certificateKey: string,
|
||||
intermediateCertificate?: string,
|
||||
abortController?: AbortController,
|
||||
): Promise<Certificate> {
|
||||
return await api.post(
|
||||
{
|
||||
url: `/nginx/certificates/${id}/upload`,
|
||||
data: { certificate, certificateKey, intermediateCertificate },
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
return await api.post({
|
||||
url: `/nginx/certificates/${id}/upload`,
|
||||
data: { certificate, certificateKey, intermediateCertificate },
|
||||
});
|
||||
}
|
||||
|
@@ -5,13 +5,9 @@ export async function validateCertificate(
|
||||
certificate: string,
|
||||
certificateKey: string,
|
||||
intermediateCertificate?: string,
|
||||
abortController?: AbortController,
|
||||
): Promise<ValidatedCertificateResponse> {
|
||||
return await api.post(
|
||||
{
|
||||
url: "/nginx/certificates/validate",
|
||||
data: { certificate, certificateKey, intermediateCertificate },
|
||||
},
|
||||
abortController,
|
||||
);
|
||||
return await api.post({
|
||||
url: "/nginx/certificates/validate",
|
||||
data: { certificate, certificateKey, intermediateCertificate },
|
||||
});
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { intl } from "src/locale";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Button } from "src/components";
|
||||
import { intl } from "src/locale";
|
||||
|
||||
export function ErrorNotFound() {
|
||||
const navigate = useNavigate();
|
||||
@@ -9,9 +9,7 @@ export function ErrorNotFound() {
|
||||
<div className="container-tight py-4">
|
||||
<div className="empty">
|
||||
<p className="empty-title">{intl.formatMessage({ id: "notfound.title" })}</p>
|
||||
<p className="empty-subtitle text-secondary">
|
||||
{intl.formatMessage({ id: "notfound.text" })}
|
||||
</p>
|
||||
<p className="empty-subtitle text-secondary">{intl.formatMessage({ id: "notfound.text" })}</p>
|
||||
<div className="empty-action">
|
||||
<Button type="button" size="md" onClick={() => navigate("/")}>
|
||||
{intl.formatMessage({ id: "notfound.action" })}
|
||||
|
119
frontend/src/components/Form/DomainNamesField.tsx
Normal file
119
frontend/src/components/Form/DomainNamesField.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { Field, useFormikContext } from "formik";
|
||||
import type { ActionMeta, MultiValue } from "react-select";
|
||||
import CreatableSelect from "react-select/creatable";
|
||||
import { intl } from "src/locale";
|
||||
|
||||
export type SelectOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
maxDomains?: number;
|
||||
isWildcardPermitted?: boolean;
|
||||
dnsProviderWildcardSupported?: boolean;
|
||||
name?: string;
|
||||
label?: string;
|
||||
}
|
||||
export function DomainNamesField({
|
||||
name = "domainNames",
|
||||
label = "domain-names",
|
||||
id = "domainNames",
|
||||
maxDomains,
|
||||
isWildcardPermitted,
|
||||
dnsProviderWildcardSupported,
|
||||
}: Props) {
|
||||
const { values, setFieldValue } = useFormikContext();
|
||||
|
||||
const getDomainCount = (v: string[] | undefined): number => {
|
||||
if (v?.length) {
|
||||
return v.length;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
const handleChange = (v: MultiValue<SelectOption>, _actionMeta: ActionMeta<SelectOption>) => {
|
||||
const doms = v?.map((i: SelectOption) => {
|
||||
return i.value;
|
||||
});
|
||||
setFieldValue(name, doms);
|
||||
};
|
||||
|
||||
const isDomainValid = (d: string): boolean => {
|
||||
const dom = d.trim().toLowerCase();
|
||||
const v: any = values;
|
||||
|
||||
// Deny if the list of domains is hit
|
||||
if (maxDomains && getDomainCount(v?.[name]) >= maxDomains) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dom.length < 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent wildcards
|
||||
if ((!isWildcardPermitted || !dnsProviderWildcardSupported) && dom.indexOf("*") !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent duplicate * in domain
|
||||
if ((dom.match(/\*/g) || []).length > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent some invalid characters
|
||||
if ((dom.match(/(@|,|!|&|\$|#|%|\^|\(|\))/g) || []).length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This will match *.com type domains,
|
||||
return dom.match(/\*\.[^.]+$/m) === null;
|
||||
};
|
||||
|
||||
const helperTexts: string[] = [];
|
||||
if (maxDomains) {
|
||||
helperTexts.push(intl.formatMessage({ id: "domain_names.max" }, { count: maxDomains }));
|
||||
}
|
||||
if (!isWildcardPermitted) {
|
||||
helperTexts.push(intl.formatMessage({ id: "wildcards-not-permitted" }));
|
||||
} else if (!dnsProviderWildcardSupported) {
|
||||
helperTexts.push(intl.formatMessage({ id: "wildcards-not-supported" }));
|
||||
}
|
||||
|
||||
return (
|
||||
<Field name={name}>
|
||||
{({ field, form }: any) => (
|
||||
<div className="mb-3">
|
||||
<label className="form-label" htmlFor={id}>
|
||||
{intl.formatMessage({ id: label })}
|
||||
</label>
|
||||
<CreatableSelect
|
||||
name={field.name}
|
||||
id={id}
|
||||
closeMenuOnSelect={true}
|
||||
isClearable={false}
|
||||
isValidNewOption={isDomainValid}
|
||||
isMulti
|
||||
placeholder="Start typing to add domain..."
|
||||
onChange={handleChange}
|
||||
value={field.value?.map((d: string) => ({ label: d, value: d }))}
|
||||
/>
|
||||
{form.errors[field.name] ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors[field.name] && form.touched[field.name] ? form.errors[field.name] : null}
|
||||
</div>
|
||||
) : helperTexts.length ? (
|
||||
helperTexts.map((i) => (
|
||||
<div key={i} className="invalid-feedback text-info">
|
||||
{i}
|
||||
</div>
|
||||
))
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}
|
112
frontend/src/components/Form/SSLCertificateField.tsx
Normal file
112
frontend/src/components/Form/SSLCertificateField.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import { IconShield } from "@tabler/icons-react";
|
||||
import { Field, useFormikContext } from "formik";
|
||||
import Select, { type ActionMeta, components, type OptionProps } from "react-select";
|
||||
import type { Certificate } from "src/api/backend";
|
||||
import { useCertificates } from "src/hooks";
|
||||
import { DateTimeFormat, intl } from "src/locale";
|
||||
|
||||
interface CertOption {
|
||||
readonly value: number | "new";
|
||||
readonly label: string;
|
||||
readonly subLabel: string;
|
||||
readonly icon: React.ReactNode;
|
||||
}
|
||||
|
||||
const Option = (props: OptionProps<CertOption>) => {
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<div className="flex-fill">
|
||||
<div className="font-weight-medium">
|
||||
{props.data.icon} <strong>{props.data.label}</strong>
|
||||
</div>
|
||||
<div className="text-secondary mt-1 ps-3">{props.data.subLabel}</div>
|
||||
</div>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
name?: string;
|
||||
label?: string;
|
||||
required?: boolean;
|
||||
allowNew?: boolean;
|
||||
}
|
||||
export function SSLCertificateField({
|
||||
name = "certificateId",
|
||||
label = "ssl-certificate",
|
||||
id = "certificateId",
|
||||
required,
|
||||
allowNew,
|
||||
}: Props) {
|
||||
const { isLoading, isError, error, data } = useCertificates();
|
||||
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
const handleChange = (v: any, _actionMeta: ActionMeta<CertOption>) => {
|
||||
setFieldValue(name, v?.value);
|
||||
};
|
||||
|
||||
const options: CertOption[] =
|
||||
data?.map((cert: Certificate) => ({
|
||||
value: cert.id,
|
||||
label: cert.niceName,
|
||||
subLabel: `${cert.provider === "letsencrypt" ? "Let's Encrypt" : cert.provider} — Expires: ${
|
||||
cert.expiresOn ? DateTimeFormat(cert.expiresOn) : "N/A"
|
||||
}`,
|
||||
icon: <IconShield size={14} className="text-pink" />,
|
||||
})) || [];
|
||||
|
||||
// Prepend the Add New option
|
||||
if (allowNew) {
|
||||
options?.unshift({
|
||||
value: "new",
|
||||
label: "Request a new HTTP certificate",
|
||||
subLabel: "with Let's Encrypt",
|
||||
icon: <IconShield size={14} className="text-lime" />,
|
||||
});
|
||||
}
|
||||
|
||||
// Prepend the None option
|
||||
if (!required) {
|
||||
options?.unshift({
|
||||
value: 0,
|
||||
label: "None",
|
||||
subLabel: "This host will not use HTTPS",
|
||||
icon: <IconShield size={14} className="text-red" />,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Field name={name}>
|
||||
{({ field, form }: any) => (
|
||||
<div className="mb-3">
|
||||
<label className="form-label" htmlFor={id}>
|
||||
{intl.formatMessage({ id: label })}
|
||||
</label>
|
||||
{isLoading ? <div className="placeholder placeholder-lg col-12 my-3 placeholder-glow" /> : null}
|
||||
{isError ? <div className="invalid-feedback">{`${error}`}</div> : null}
|
||||
{!isLoading && !isError ? (
|
||||
<Select
|
||||
defaultValue={options[0]}
|
||||
options={options}
|
||||
components={{ Option }}
|
||||
styles={{
|
||||
option: (base) => ({
|
||||
...base,
|
||||
height: "100%",
|
||||
}),
|
||||
}}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
) : null}
|
||||
{form.errors[field.name] ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors[field.name] && form.touched[field.name] ? form.errors[field.name] : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
}
|
2
frontend/src/components/Form/index.ts
Normal file
2
frontend/src/components/Form/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./DomainNamesField";
|
||||
export * from "./SSLCertificateField";
|
@@ -7,11 +7,9 @@ function TableBody<T>(props: TableLayoutProps<T>) {
|
||||
const rows = tableInstance.getRowModel().rows;
|
||||
|
||||
if (rows.length === 0) {
|
||||
return emptyState ? (
|
||||
emptyState
|
||||
) : (
|
||||
return (
|
||||
<tbody className="table-tbody">
|
||||
<EmptyRow tableInstance={tableInstance} />
|
||||
{emptyState ? emptyState : <EmptyRow tableInstance={tableInstance} />}
|
||||
</tbody>
|
||||
);
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
export * from "./Button";
|
||||
export * from "./ErrorNotFound";
|
||||
export * from "./Flag";
|
||||
export * from "./Form";
|
||||
export * from "./HasPermission";
|
||||
export * from "./Loading";
|
||||
export * from "./LoadingPage";
|
||||
|
@@ -30,7 +30,7 @@ function AuthProvider({ children, tokenRefreshInterval = 5 * 60 * 1000 }: Props)
|
||||
};
|
||||
|
||||
const login = async (identity: string, secret: string) => {
|
||||
const response = await getToken({ payload: { identity, secret } });
|
||||
const response = await getToken(identity, secret);
|
||||
handleTokenUpdate(response);
|
||||
};
|
||||
|
||||
|
@@ -2,6 +2,7 @@ export * from "./useAccessLists";
|
||||
export * from "./useAuditLog";
|
||||
export * from "./useAuditLogs";
|
||||
export * from "./useCertificates";
|
||||
export * from "./useDeadHost";
|
||||
export * from "./useDeadHosts";
|
||||
export * from "./useHealth";
|
||||
export * from "./useHostReport";
|
||||
|
57
frontend/src/hooks/useDeadHost.ts
Normal file
57
frontend/src/hooks/useDeadHost.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { createDeadHost, type DeadHost, getDeadHost, updateDeadHost } from "src/api/backend";
|
||||
|
||||
const fetchDeadHost = (id: number | "new") => {
|
||||
if (id === "new") {
|
||||
return Promise.resolve({
|
||||
id: 0,
|
||||
createdOn: "",
|
||||
modifiedOn: "",
|
||||
ownerUserId: 0,
|
||||
domainNames: [],
|
||||
certificateId: 0,
|
||||
sslForced: false,
|
||||
advancedConfig: "",
|
||||
meta: {},
|
||||
http2Support: false,
|
||||
enabled: true,
|
||||
hstsEnabled: false,
|
||||
hstsSubdomains: false,
|
||||
} as DeadHost);
|
||||
}
|
||||
return getDeadHost(id, ["owner"]);
|
||||
};
|
||||
|
||||
const useDeadHost = (id: number | "new", options = {}) => {
|
||||
return useQuery<DeadHost, Error>({
|
||||
queryKey: ["dead-host", id],
|
||||
queryFn: () => fetchDeadHost(id),
|
||||
staleTime: 60 * 1000, // 1 minute
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
const useSetDeadHost = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (values: DeadHost) => (values.id ? updateDeadHost(values) : createDeadHost(values)),
|
||||
onMutate: (values: DeadHost) => {
|
||||
if (!values.id) {
|
||||
return;
|
||||
}
|
||||
const previousObject = queryClient.getQueryData(["dead-host", values.id]);
|
||||
queryClient.setQueryData(["dead-host", values.id], (old: DeadHost) => ({
|
||||
...old,
|
||||
...values,
|
||||
}));
|
||||
return () => queryClient.setQueryData(["dead-host", values.id], previousObject);
|
||||
},
|
||||
onError: (_, __, rollback: any) => rollback(),
|
||||
onSuccess: async ({ id }: DeadHost) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["dead-host", id] });
|
||||
queryClient.invalidateQueries({ queryKey: ["dead-hosts"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export { useDeadHost, useSetDeadHost };
|
@@ -1,11 +1,11 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { type DeadHost, type DeadHostExpansion, getDeadHosts } from "src/api/backend";
|
||||
import { type DeadHost, getDeadHosts, type HostExpansion } from "src/api/backend";
|
||||
|
||||
const fetchDeadHosts = (expand?: DeadHostExpansion[]) => {
|
||||
const fetchDeadHosts = (expand?: HostExpansion[]) => {
|
||||
return getDeadHosts(expand);
|
||||
};
|
||||
|
||||
const useDeadHosts = (expand?: DeadHostExpansion[], options = {}) => {
|
||||
const useDeadHosts = (expand?: HostExpansion[], options = {}) => {
|
||||
return useQuery<DeadHost[], Error>({
|
||||
queryKey: ["dead-hosts", { expand }],
|
||||
queryFn: () => fetchDeadHosts(expand),
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getRedirectionHosts, type RedirectionHost, type RedirectionHostExpansion } from "src/api/backend";
|
||||
import { getRedirectionHosts, type HostExpansion, type RedirectionHost } from "src/api/backend";
|
||||
|
||||
const fetchRedirectionHosts = (expand?: RedirectionHostExpansion[]) => {
|
||||
const fetchRedirectionHosts = (expand?: HostExpansion[]) => {
|
||||
return getRedirectionHosts(expand);
|
||||
};
|
||||
|
||||
const useRedirectionHosts = (expand?: RedirectionHostExpansion[], options = {}) => {
|
||||
const useRedirectionHosts = (expand?: HostExpansion[], options = {}) => {
|
||||
return useQuery<RedirectionHost[], Error>({
|
||||
queryKey: ["redirection-hosts", { expand }],
|
||||
queryFn: () => fetchRedirectionHosts(expand),
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getStreams, type Stream, type StreamExpansion } from "src/api/backend";
|
||||
import { getStreams, type HostExpansion, type Stream } from "src/api/backend";
|
||||
|
||||
const fetchStreams = (expand?: StreamExpansion[]) => {
|
||||
const fetchStreams = (expand?: HostExpansion[]) => {
|
||||
return getStreams(expand);
|
||||
};
|
||||
|
||||
const useStreams = (expand?: StreamExpansion[], options = {}) => {
|
||||
const useStreams = (expand?: HostExpansion[], options = {}) => {
|
||||
return useQuery<Stream[], Error>({
|
||||
queryKey: ["streams", { expand }],
|
||||
queryFn: () => fetchStreams(expand),
|
||||
|
@@ -15,7 +15,7 @@ const fetchUser = (id: number | string) => {
|
||||
avatar: "",
|
||||
} as User);
|
||||
}
|
||||
return getUser(id, { expand: "permissions" });
|
||||
return getUser(id, ["permissions"]);
|
||||
};
|
||||
|
||||
const useUser = (id: string | number, options = {}) => {
|
||||
|
@@ -24,6 +24,7 @@
|
||||
"column.access": "Access",
|
||||
"column.authorization": "Authorization",
|
||||
"column.destination": "Destination",
|
||||
"column.details": "Details",
|
||||
"column.email": "Email",
|
||||
"column.event": "Event",
|
||||
"column.expires": "Expires",
|
||||
@@ -40,12 +41,15 @@
|
||||
"column.status": "Status",
|
||||
"created-on": "Created: {date}",
|
||||
"dashboard.title": "Dashboard",
|
||||
"dead-host.edit": "Edit 404 Host",
|
||||
"dead-host.new": "New 404 Host",
|
||||
"dead-hosts.actions-title": "404 Host #{id}",
|
||||
"dead-hosts.add": "Add 404 Host",
|
||||
"dead-hosts.count": "{count} 404 Hosts",
|
||||
"dead-hosts.empty": "There are no 404 Hosts",
|
||||
"dead-hosts.title": "404 Hosts",
|
||||
"disabled": "Disabled",
|
||||
"domain-names": "Domain Names",
|
||||
"email-address": "Email address",
|
||||
"empty-subtitle": "Why don't you create one?",
|
||||
"error.invalid-auth": "Invalid email or password",
|
||||
@@ -96,6 +100,7 @@
|
||||
"setup.preamble": "Get started by creating your admin account.",
|
||||
"setup.title": "Welcome!",
|
||||
"sign-in": "Sign in",
|
||||
"ssl-certificate": "SSL Certificate",
|
||||
"streams.actions-title": "Stream #{id}",
|
||||
"streams.add": "Add Stream",
|
||||
"streams.count": "{count} Streams",
|
||||
@@ -122,5 +127,7 @@
|
||||
"user.switch-light": "Switch to Light mode",
|
||||
"users.actions-title": "User #{id}",
|
||||
"users.add": "Add User",
|
||||
"users.title": "Users"
|
||||
"users.title": "Users",
|
||||
"wildcards-not-permitted": "Wildcards not permitted for this type",
|
||||
"wildcards-not-supported": "Wildcards not supported for this CA"
|
||||
}
|
@@ -77,6 +77,9 @@
|
||||
"column.destination": {
|
||||
"defaultMessage": "Destination"
|
||||
},
|
||||
"column.details": {
|
||||
"defaultMessage": "Details"
|
||||
},
|
||||
"column.email": {
|
||||
"defaultMessage": "Email"
|
||||
},
|
||||
@@ -131,15 +134,24 @@
|
||||
"dead-hosts.count": {
|
||||
"defaultMessage": "{count} 404 Hosts"
|
||||
},
|
||||
"dead-host.edit": {
|
||||
"defaultMessage": "Edit 404 Host"
|
||||
},
|
||||
"dead-hosts.empty": {
|
||||
"defaultMessage": "There are no 404 Hosts"
|
||||
},
|
||||
"dead-host.new": {
|
||||
"defaultMessage": "New 404 Host"
|
||||
},
|
||||
"dead-hosts.title": {
|
||||
"defaultMessage": "404 Hosts"
|
||||
},
|
||||
"disabled": {
|
||||
"defaultMessage": "Disabled"
|
||||
},
|
||||
"domain-names": {
|
||||
"defaultMessage": "Domain Names"
|
||||
},
|
||||
"email-address": {
|
||||
"defaultMessage": "Email address"
|
||||
},
|
||||
@@ -290,6 +302,9 @@
|
||||
"sign-in": {
|
||||
"defaultMessage": "Sign in"
|
||||
},
|
||||
"ssl-certificate": {
|
||||
"defaultMessage": "SSL Certificate"
|
||||
},
|
||||
"streams.actions-title": {
|
||||
"defaultMessage": "Stream #{id}"
|
||||
},
|
||||
@@ -370,5 +385,11 @@
|
||||
},
|
||||
"users.title": {
|
||||
"defaultMessage": "Users"
|
||||
},
|
||||
"wildcards-not-permitted": {
|
||||
"defaultMessage": "Wildcards not permitted for this type"
|
||||
},
|
||||
"wildcards-not-supported": {
|
||||
"defaultMessage": "Wildcards not supported for this CA"
|
||||
}
|
||||
}
|
||||
|
285
frontend/src/modals/DeadHostModal.tsx
Normal file
285
frontend/src/modals/DeadHostModal.tsx
Normal file
@@ -0,0 +1,285 @@
|
||||
import { IconSettings } from "@tabler/icons-react";
|
||||
import { Form, Formik } from "formik";
|
||||
import { useState } from "react";
|
||||
import { Alert } from "react-bootstrap";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import { Button, DomainNamesField, Loading, SSLCertificateField } from "src/components";
|
||||
import { useDeadHost } from "src/hooks";
|
||||
import { intl } from "src/locale";
|
||||
|
||||
interface Props {
|
||||
id: number | "new";
|
||||
onClose: () => void;
|
||||
}
|
||||
export function DeadHostModal({ id, onClose }: Props) {
|
||||
const { data, isLoading, error } = useDeadHost(id);
|
||||
// const { mutate: setDeadHost } = useSetDeadHost();
|
||||
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
||||
|
||||
const onSubmit = async (values: any, { setSubmitting }: any) => {
|
||||
setSubmitting(true);
|
||||
setErrorMsg(null);
|
||||
console.log("SUBMIT:", values);
|
||||
setSubmitting(false);
|
||||
// const { ...payload } = {
|
||||
// id: id === "new" ? undefined : id,
|
||||
// roles: [],
|
||||
// ...values,
|
||||
// };
|
||||
|
||||
// setDeadHost(payload, {
|
||||
// onError: (err: any) => setErrorMsg(err.message),
|
||||
// onSuccess: () => {
|
||||
// showSuccess(intl.formatMessage({ id: "notification.dead-host-saved" }));
|
||||
// onClose();
|
||||
// },
|
||||
// onSettled: () => setSubmitting(false),
|
||||
// });
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show onHide={onClose} animation={false}>
|
||||
{!isLoading && error && (
|
||||
<Alert variant="danger" className="m-3">
|
||||
{error?.message || "Unknown error"}
|
||||
</Alert>
|
||||
)}
|
||||
{isLoading && <Loading noLogo />}
|
||||
{!isLoading && data && (
|
||||
<Formik
|
||||
initialValues={
|
||||
{
|
||||
domainNames: data?.domainNames,
|
||||
certificateId: data?.certificateId,
|
||||
sslForced: data?.sslForced,
|
||||
advancedConfig: data?.advancedConfig,
|
||||
http2Support: data?.http2Support,
|
||||
hstsEnabled: data?.hstsEnabled,
|
||||
hstsSubdomains: data?.hstsSubdomains,
|
||||
} as any
|
||||
}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ isSubmitting }) => (
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
{intl.formatMessage({ id: data?.id ? "dead-host.edit" : "dead-host.new" })}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body className="p-0">
|
||||
<Alert variant="danger" show={!!errorMsg} onClose={() => setErrorMsg(null)} dismissible>
|
||||
{errorMsg}
|
||||
</Alert>
|
||||
|
||||
<div className="card m-0 border-0">
|
||||
<div className="card-header">
|
||||
<ul className="nav nav-tabs card-header-tabs" data-bs-toggle="tabs">
|
||||
<li className="nav-item" role="presentation">
|
||||
<a
|
||||
href="#tab-details"
|
||||
className="nav-link active"
|
||||
data-bs-toggle="tab"
|
||||
aria-selected="true"
|
||||
role="tab"
|
||||
>
|
||||
{intl.formatMessage({ id: "column.details" })}
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item" role="presentation">
|
||||
<a
|
||||
href="#tab-ssl"
|
||||
className="nav-link"
|
||||
data-bs-toggle="tab"
|
||||
aria-selected="false"
|
||||
tabIndex={-1}
|
||||
role="tab"
|
||||
>
|
||||
{intl.formatMessage({ id: "column.ssl" })}
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item ms-auto" role="presentation">
|
||||
<a
|
||||
href="#tab-advanced"
|
||||
className="nav-link"
|
||||
title="Settings"
|
||||
data-bs-toggle="tab"
|
||||
aria-selected="false"
|
||||
tabIndex={-1}
|
||||
role="tab"
|
||||
>
|
||||
<IconSettings size={20} />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="tab-content">
|
||||
<div className="tab-pane active show" id="tab-details" role="tabpanel">
|
||||
<DomainNamesField isWildcardPermitted />
|
||||
</div>
|
||||
<div className="tab-pane" id="tab-ssl" role="tabpanel">
|
||||
<SSLCertificateField
|
||||
name="certificateId"
|
||||
label="ssl-certificate"
|
||||
allowNew
|
||||
/>
|
||||
</div>
|
||||
<div className="tab-pane" id="tab-advanced" role="tabpanel">
|
||||
<h4>Advanced</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="mb-3">
|
||||
<Field name="name" validate={validateString(1, 50)}>
|
||||
{({ field, form }: any) => (
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
id="name"
|
||||
className={`form-control ${form.errors.name && form.touched.name ? "is-invalid" : ""}`}
|
||||
placeholder={intl.formatMessage({ id: "user.full-name" })}
|
||||
{...field}
|
||||
/>
|
||||
<label htmlFor="name">
|
||||
{intl.formatMessage({ id: "user.full-name" })}
|
||||
</label>
|
||||
{form.errors.name ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.name && form.touched.name
|
||||
? form.errors.name
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="mb-3">
|
||||
<Field name="nickname" validate={validateString(1, 30)}>
|
||||
{({ field, form }: any) => (
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
id="nickname"
|
||||
className={`form-control ${form.errors.nickname && form.touched.nickname ? "is-invalid" : ""}`}
|
||||
placeholder={intl.formatMessage({ id: "user.nickname" })}
|
||||
{...field}
|
||||
/>
|
||||
<label htmlFor="nickname">
|
||||
{intl.formatMessage({ id: "user.nickname" })}
|
||||
</label>
|
||||
{form.errors.nickname ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.nickname && form.touched.nickname
|
||||
? form.errors.nickname
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Field name="email" validate={validateEmail()}>
|
||||
{({ field, form }: any) => (
|
||||
<div className="form-floating mb-3">
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
className={`form-control ${form.errors.email && form.touched.email ? "is-invalid" : ""}`}
|
||||
placeholder={intl.formatMessage({ id: "email-address" })}
|
||||
{...field}
|
||||
/>
|
||||
<label htmlFor="email">
|
||||
{intl.formatMessage({ id: "email-address" })}
|
||||
</label>
|
||||
{form.errors.email ? (
|
||||
<div className="invalid-feedback">
|
||||
{form.errors.email && form.touched.email
|
||||
? form.errors.email
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
{currentUser && data && currentUser?.id !== data?.id ? (
|
||||
<div className="my-3">
|
||||
<h3 className="py-2">{intl.formatMessage({ id: "user.flags.title" })}</h3>
|
||||
<div className="divide-y">
|
||||
<div>
|
||||
<label className="row" htmlFor="isAdmin">
|
||||
<span className="col">
|
||||
{intl.formatMessage({ id: "role.admin" })}
|
||||
</span>
|
||||
<span className="col-auto">
|
||||
<Field name="isAdmin" type="checkbox">
|
||||
{({ field }: any) => (
|
||||
<label className="form-check form-check-single form-switch">
|
||||
<input
|
||||
{...field}
|
||||
id="isAdmin"
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
</Field>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label className="row" htmlFor="isDisabled">
|
||||
<span className="col">
|
||||
{intl.formatMessage({ id: "disabled" })}
|
||||
</span>
|
||||
<span className="col-auto">
|
||||
<Field name="isDisabled" type="checkbox">
|
||||
{({ field }: any) => (
|
||||
<label className="form-check form-check-single form-switch">
|
||||
<input
|
||||
{...field}
|
||||
id="isDisabled"
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
</Field>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null} */}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={onClose} disabled={isSubmitting}>
|
||||
{intl.formatMessage({ id: "cancel" })}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
actionType="primary"
|
||||
className="ms-auto"
|
||||
data-bs-dismiss="modal"
|
||||
isLoading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{intl.formatMessage({ id: "save" })}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
export * from "./ChangePasswordModal";
|
||||
export * from "./DeadHostModal";
|
||||
export * from "./DeleteConfirmModal";
|
||||
export * from "./EventDetailsModal";
|
||||
export * from "./PermissionsModal";
|
||||
|
@@ -1,132 +0,0 @@
|
||||
import { IconDotsVertical, IconEdit, IconPower, IconSearch, IconTrash } from "@tabler/icons-react";
|
||||
import { Button } from "src/components";
|
||||
import { intl } from "src/locale";
|
||||
|
||||
export default function CertificateTable() {
|
||||
return (
|
||||
<div className="card mt-4">
|
||||
<div className="card-status-top bg-pink" />
|
||||
<div className="card-table">
|
||||
<div className="card-header">
|
||||
<div className="row w-full">
|
||||
<div className="col">
|
||||
<h2 className="mt-1 mb-0">{intl.formatMessage({ id: "certificates.title" })}</h2>
|
||||
</div>
|
||||
<div className="col-md-auto col-sm-12">
|
||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||
<div className="input-group input-group-flat w-auto">
|
||||
<span className="input-group-text input-group-text-sm">
|
||||
<IconSearch size={16} />
|
||||
</span>
|
||||
<input
|
||||
id="advanced-table-search"
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<Button size="sm" className="btn-pink">
|
||||
Add Certificate (dropdown)
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="advanced-table">
|
||||
<div className="table-responsive">
|
||||
<table className="table table-vcenter table-selectable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="w-1" />
|
||||
<th>
|
||||
<button type="button" className="table-sort d-flex justify-content-between">
|
||||
Source
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button type="button" className="table-sort d-flex justify-content-between">
|
||||
Destination
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button type="button" className="table-sort d-flex justify-content-between">
|
||||
SSL
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button type="button" className="table-sort d-flex justify-content-between">
|
||||
Access
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
<button type="button" className="table-sort d-flex justify-content-between">
|
||||
Status
|
||||
</button>
|
||||
</th>
|
||||
<th className="w-1" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-tbody">
|
||||
<tr>
|
||||
<td data-label="Owner">
|
||||
<div className="d-flex py-1 align-items-center">
|
||||
<span
|
||||
className="avatar avatar-2 me-2"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"url(//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="Destination">
|
||||
<div className="flex-fill">
|
||||
<div className="font-weight-medium">
|
||||
<span className="badge badge-lg domain-name">blog.jc21.com</span>
|
||||
</div>
|
||||
<div className="text-secondary mt-1">Created: 20th September 2024</div>
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="Source">http://172.17.0.1:3001</td>
|
||||
<td data-label="SSL">Let's Encrypt</td>
|
||||
<td data-label="Access">Public</td>
|
||||
<td data-label="Status">
|
||||
<span className="badge bg-lime-lt">Online</span>
|
||||
</td>
|
||||
<td data-label="Status" className="text-end">
|
||||
<span className="dropdown">
|
||||
<button
|
||||
type="button"
|
||||
className="btn dropdown-toggle btn-action btn-sm px-1"
|
||||
data-bs-boundary="viewport"
|
||||
data-bs-toggle="dropdown"
|
||||
>
|
||||
<IconDotsVertical />
|
||||
</button>
|
||||
<div className="dropdown-menu dropdown-menu-end">
|
||||
<span className="dropdown-header">Proxy Host #2</span>
|
||||
<a className="dropdown-item" href="#">
|
||||
<IconEdit size={16} />
|
||||
Edit
|
||||
</a>
|
||||
<a className="dropdown-item" href="#">
|
||||
<IconPower size={16} />
|
||||
Disable
|
||||
</a>
|
||||
<div className="dropdown-divider" />
|
||||
<a className="dropdown-item" href="#">
|
||||
<IconTrash size={16} />
|
||||
Delete
|
||||
</a>
|
||||
</div>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -4,15 +4,18 @@ import { intl } from "src/locale";
|
||||
|
||||
interface Props {
|
||||
tableInstance: ReactTable<any>;
|
||||
onNew?: () => void;
|
||||
}
|
||||
export default function Empty({ tableInstance }: Props) {
|
||||
export default function Empty({ tableInstance, onNew }: Props) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
|
||||
<div className="text-center my-4">
|
||||
<h2>{intl.formatMessage({ id: "dead-hosts.empty" })}</h2>
|
||||
<p className="text-muted">{intl.formatMessage({ id: "empty-subtitle" })}</p>
|
||||
<Button className="btn-red my-3">{intl.formatMessage({ id: "dead-hosts.add" })}</Button>
|
||||
<Button className="btn-red my-3" onClick={onNew}>
|
||||
{intl.formatMessage({ id: "dead-hosts.add" })}
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@@ -10,8 +10,10 @@ import Empty from "./Empty";
|
||||
interface Props {
|
||||
data: DeadHost[];
|
||||
isFetching?: boolean;
|
||||
onDelete?: (id: number) => void;
|
||||
onNew?: () => void;
|
||||
}
|
||||
export default function Table({ data, isFetching }: Props) {
|
||||
export default function Table({ data, isFetching, onDelete, onNew }: Props) {
|
||||
const columnHelper = createColumnHelper<DeadHost>();
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
@@ -78,7 +80,14 @@ export default function Table({ data, isFetching }: Props) {
|
||||
{intl.formatMessage({ id: "action.disable" })}
|
||||
</a>
|
||||
<div className="dropdown-divider" />
|
||||
<a className="dropdown-item" href="#">
|
||||
<a
|
||||
className="dropdown-item"
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onDelete?.(info.row.original.id);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={16} />
|
||||
{intl.formatMessage({ id: "action.delete" })}
|
||||
</a>
|
||||
@@ -91,7 +100,7 @@ export default function Table({ data, isFetching }: Props) {
|
||||
},
|
||||
}),
|
||||
],
|
||||
[columnHelper],
|
||||
[columnHelper, onDelete],
|
||||
);
|
||||
|
||||
const tableInstance = useReactTable<DeadHost>({
|
||||
@@ -105,5 +114,7 @@ export default function Table({ data, isFetching }: Props) {
|
||||
enableSortingRemoval: false,
|
||||
});
|
||||
|
||||
return <TableLayout tableInstance={tableInstance} emptyState={<Empty tableInstance={tableInstance} />} />;
|
||||
return (
|
||||
<TableLayout tableInstance={tableInstance} emptyState={<Empty tableInstance={tableInstance} onNew={onNew} />} />
|
||||
);
|
||||
}
|
||||
|
@@ -1,11 +1,16 @@
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
import { useState } from "react";
|
||||
import Alert from "react-bootstrap/Alert";
|
||||
import { Button, LoadingPage } from "src/components";
|
||||
import { useDeadHosts } from "src/hooks";
|
||||
import { intl } from "src/locale";
|
||||
import { DeadHostModal, DeleteConfirmModal } from "src/modals";
|
||||
import { showSuccess } from "src/notifications";
|
||||
import Table from "./Table";
|
||||
|
||||
export default function TableWrapper() {
|
||||
const [deleteId, setDeleteId] = useState(0);
|
||||
const [editId, setEditId] = useState(0 as number | "new");
|
||||
const { isFetching, isLoading, isError, error, data } = useDeadHosts(["owner", "certificate"]);
|
||||
|
||||
if (isLoading) {
|
||||
@@ -16,6 +21,11 @@ export default function TableWrapper() {
|
||||
return <Alert variant="danger">{error?.message || "Unknown error"}</Alert>;
|
||||
}
|
||||
|
||||
const handleDelete = async () => {
|
||||
// await deleteUser(deleteId);
|
||||
showSuccess(intl.formatMessage({ id: "notification.host-deleted" }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card mt-4">
|
||||
<div className="card-status-top bg-red" />
|
||||
@@ -38,14 +48,30 @@ export default function TableWrapper() {
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<Button size="sm" className="btn-red">
|
||||
<Button size="sm" className="btn-red" onClick={() => setEditId("new")}>
|
||||
{intl.formatMessage({ id: "dead-hosts.add" })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Table data={data ?? []} isFetching={isFetching} />
|
||||
<Table
|
||||
data={data ?? []}
|
||||
isFetching={isFetching}
|
||||
onDelete={(id: number) => setDeleteId(id)}
|
||||
onNew={() => setEditId("new")}
|
||||
/>
|
||||
{editId ? <DeadHostModal id={editId} onClose={() => setEditId(0)} /> : null}
|
||||
{deleteId ? (
|
||||
<DeleteConfirmModal
|
||||
title={intl.formatMessage({ id: "user.delete.title" })}
|
||||
onConfirm={handleDelete}
|
||||
onClose={() => setDeleteId(0)}
|
||||
invalidations={[["dead-hosts"], ["dead-host", deleteId]]}
|
||||
>
|
||||
{intl.formatMessage({ id: "user.delete.content" })}
|
||||
</DeleteConfirmModal>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
Reference in New Issue
Block a user