first commit
This commit is contained in:
@@ -0,0 +1,442 @@
|
||||
import { emitApiRequest, type CloudProvider } from "@/telemetry";
|
||||
|
||||
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
||||
|
||||
export interface HttpClientConfig {
|
||||
baseUrl?: string;
|
||||
defaultHeaders?: Record<string, string>;
|
||||
cache?: RequestCache;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export interface RequestTelemetry {
|
||||
provider?: CloudProvider;
|
||||
service?: string;
|
||||
}
|
||||
|
||||
export type EndpointRegistry = Map<string, EndpointDefinition>;
|
||||
|
||||
export interface RequestOptions<TBody = unknown>
|
||||
extends Omit<RequestInit, "body" | "method"> {
|
||||
params?: Record<string, string | number | boolean | undefined>;
|
||||
body?: TBody;
|
||||
rawBody?: BodyInit;
|
||||
cache?: RequestCache;
|
||||
timeout?: number;
|
||||
baseUrlOverride?: string;
|
||||
telemetry?: RequestTelemetry;
|
||||
}
|
||||
|
||||
export interface HttpResponse<TData> {
|
||||
data: TData;
|
||||
status: number;
|
||||
headers: Headers;
|
||||
ok: boolean;
|
||||
}
|
||||
|
||||
export class HttpError extends Error {
|
||||
constructor(
|
||||
public readonly status: number,
|
||||
public readonly statusText: string,
|
||||
public readonly body: unknown,
|
||||
public readonly url: string,
|
||||
) {
|
||||
super(httpErrorMessage(status, statusText, body, url));
|
||||
this.name = "HttpError";
|
||||
}
|
||||
}
|
||||
|
||||
export class NetworkError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly url: string,
|
||||
public readonly cause?: unknown,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "NetworkError";
|
||||
}
|
||||
}
|
||||
|
||||
function httpErrorMessage(
|
||||
status: number,
|
||||
statusText: string,
|
||||
body: unknown,
|
||||
url: string,
|
||||
): string {
|
||||
const apiMessage = apiErrorMessage(body);
|
||||
if (!apiMessage) return `HTTP ${status} - ${statusText} [${url}]`;
|
||||
|
||||
const code = apiErrorCode(body);
|
||||
const suffix = code ? ` (${code})` : "";
|
||||
return `${apiMessage}${suffix} [${url}]`;
|
||||
}
|
||||
|
||||
function apiErrorMessage(body: unknown): string | null {
|
||||
if (!body || typeof body !== "object") return null;
|
||||
const value = (body as { message?: unknown; error?: unknown }).message ?? (body as { error?: unknown }).error;
|
||||
if (typeof value !== "string" || !value.trim()) return null;
|
||||
|
||||
const detail = (body as { detail?: unknown }).detail;
|
||||
if (typeof detail === "string" && detail.trim() && detail.trim() !== value.trim()) {
|
||||
return `${value.trim()}: ${detail.trim()}`;
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function apiErrorCode(body: unknown): string | null {
|
||||
if (!body || typeof body !== "object") return null;
|
||||
const value = (body as { code?: unknown }).code;
|
||||
return typeof value === "string" && value.trim() ? value : null;
|
||||
}
|
||||
|
||||
export class TimeoutError extends Error {
|
||||
constructor(
|
||||
public readonly url: string,
|
||||
public readonly timeoutMs: number,
|
||||
) {
|
||||
super(`Request timed out after ${timeoutMs}ms [${url}]`);
|
||||
this.name = "TimeoutError";
|
||||
}
|
||||
}
|
||||
|
||||
export class ResponseParseError extends Error {
|
||||
constructor(
|
||||
public readonly url: string,
|
||||
public readonly cause?: unknown,
|
||||
) {
|
||||
super(`Failed to parse response [${url}]`);
|
||||
this.name = "ResponseParseError";
|
||||
}
|
||||
}
|
||||
|
||||
export type RequestInterceptor = (
|
||||
url: string,
|
||||
init: RequestInit,
|
||||
) =>
|
||||
| Promise<{ url: string; init: RequestInit }>
|
||||
| { url: string; init: RequestInit };
|
||||
|
||||
export type ResponseInterceptor = (
|
||||
response: Response,
|
||||
) => Promise<Response> | Response;
|
||||
|
||||
export type EndpointDefaults = Omit<RequestOptions<never>, "body" | "params">;
|
||||
|
||||
export type EndpointDefinition = {
|
||||
path: string;
|
||||
method: HttpMethod;
|
||||
defaults?: Partial<EndpointDefaults>;
|
||||
baseUrlOverride?: string;
|
||||
telemetry?: RequestTelemetry;
|
||||
};
|
||||
|
||||
export class HttpClient {
|
||||
private readonly config: HttpClientConfig;
|
||||
private readonly requestInterceptors: RequestInterceptor[] = [];
|
||||
private readonly responseInterceptors: ResponseInterceptor[] = [];
|
||||
private readonly endpointRegistry: EndpointRegistry;
|
||||
|
||||
constructor(
|
||||
config: HttpClientConfig = {},
|
||||
registry: EndpointRegistry = new Map(),
|
||||
) {
|
||||
this.config = config;
|
||||
this.endpointRegistry = registry;
|
||||
}
|
||||
|
||||
addRequestInterceptor(interceptor: RequestInterceptor): this {
|
||||
this.requestInterceptors.push(interceptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
addResponseInterceptor(interceptor: ResponseInterceptor): this {
|
||||
this.responseInterceptors.push(interceptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
async request<TResponse, TBody = unknown>(
|
||||
method: HttpMethod,
|
||||
endpoint: string,
|
||||
options: RequestOptions<TBody> = {},
|
||||
): Promise<HttpResponse<TResponse>> {
|
||||
const started = performance.now();
|
||||
const {
|
||||
params,
|
||||
body,
|
||||
rawBody,
|
||||
timeout,
|
||||
cache,
|
||||
baseUrlOverride,
|
||||
telemetry,
|
||||
...restOptions
|
||||
} = options;
|
||||
let finalUrl = endpoint;
|
||||
let statusCode = 0;
|
||||
|
||||
try {
|
||||
const base = baseUrlOverride ?? this.config.baseUrl ?? "";
|
||||
const rawUrl = endpoint.startsWith("http")
|
||||
? endpoint
|
||||
: `${base}${endpoint}`;
|
||||
finalUrl = this.buildUrl(rawUrl, params);
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Accept: "application/json",
|
||||
...this.config.defaultHeaders,
|
||||
...(body !== undefined && rawBody === undefined
|
||||
? { "Content-Type": "application/json" }
|
||||
: {}),
|
||||
...(restOptions.headers as Record<string, string> | undefined),
|
||||
};
|
||||
|
||||
let init: RequestInit = {
|
||||
...restOptions,
|
||||
method,
|
||||
headers,
|
||||
cache: cache ?? this.config.cache,
|
||||
...(rawBody !== undefined
|
||||
? { body: rawBody }
|
||||
: body !== undefined
|
||||
? { body: JSON.stringify(body) }
|
||||
: {}),
|
||||
};
|
||||
|
||||
for (const interceptor of this.requestInterceptors) {
|
||||
({ url: finalUrl, init } = await interceptor(finalUrl, init));
|
||||
}
|
||||
|
||||
let response = await this.fetchWithTimeout(
|
||||
finalUrl,
|
||||
init,
|
||||
timeout ?? this.config.timeout,
|
||||
);
|
||||
statusCode = response.status;
|
||||
|
||||
for (const interceptor of this.responseInterceptors) {
|
||||
response = await interceptor(response);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw await this.createHttpError(response, finalUrl);
|
||||
}
|
||||
|
||||
return {
|
||||
data: await this.parseResponse<TResponse>(response, finalUrl),
|
||||
status: response.status,
|
||||
headers: response.headers,
|
||||
ok: response.ok,
|
||||
};
|
||||
} catch (error) {
|
||||
throw this.normalizeError(error, finalUrl, timeout ?? this.config.timeout);
|
||||
} finally {
|
||||
if (telemetry?.service && statusCode > 0) {
|
||||
emitApiRequest({
|
||||
provider: telemetry.provider ?? "aws",
|
||||
service: telemetry.service,
|
||||
method,
|
||||
path: endpoint,
|
||||
statusCode,
|
||||
latencyMs: Math.round(performance.now() - started),
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async call<TResponse, TBody = unknown>(
|
||||
key: string,
|
||||
options: RequestOptions<TBody> = {},
|
||||
pathParams?: Record<string, string>,
|
||||
): Promise<HttpResponse<TResponse>> {
|
||||
const endpoint = this.endpointRegistry.get(key);
|
||||
if (!endpoint) throw new Error(`Endpoint "${key}" is not registered.`);
|
||||
|
||||
let path = endpoint.path;
|
||||
if (pathParams) {
|
||||
for (const [param, value] of Object.entries(pathParams)) {
|
||||
path = path.replace(`:${param}`, encodeURIComponent(value));
|
||||
}
|
||||
}
|
||||
|
||||
return this.request<TResponse, TBody>(endpoint.method, path, {
|
||||
...endpoint.defaults,
|
||||
...options,
|
||||
baseUrlOverride: endpoint.baseUrlOverride,
|
||||
telemetry: {
|
||||
...endpoint.telemetry,
|
||||
...options.telemetry,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
get<TResponse>(endpoint: string, options?: RequestOptions<never>) {
|
||||
return this.request<TResponse, never>("GET", endpoint, options);
|
||||
}
|
||||
|
||||
post<TResponse, TBody = unknown>(
|
||||
endpoint: string,
|
||||
body?: TBody,
|
||||
options?: RequestOptions<TBody>,
|
||||
) {
|
||||
return this.request<TResponse, TBody>("POST", endpoint, {
|
||||
...options,
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
put<TResponse, TBody = unknown>(
|
||||
endpoint: string,
|
||||
body?: TBody,
|
||||
options?: RequestOptions<TBody>,
|
||||
) {
|
||||
return this.request<TResponse, TBody>("PUT", endpoint, {
|
||||
...options,
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
patch<TResponse, TBody = unknown>(
|
||||
endpoint: string,
|
||||
body?: TBody,
|
||||
options?: RequestOptions<TBody>,
|
||||
) {
|
||||
return this.request<TResponse, TBody>("PATCH", endpoint, {
|
||||
...options,
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
delete<TResponse>(endpoint: string, options?: RequestOptions<never>) {
|
||||
return this.request<TResponse, never>("DELETE", endpoint, options);
|
||||
}
|
||||
|
||||
private async fetchWithTimeout(
|
||||
url: string,
|
||||
init: RequestInit,
|
||||
timeoutMs?: number,
|
||||
): Promise<Response> {
|
||||
if (!timeoutMs) return fetch(url, init);
|
||||
|
||||
const timeoutController = new AbortController();
|
||||
let timedOut = false;
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
timedOut = true;
|
||||
timeoutController.abort();
|
||||
}, timeoutMs);
|
||||
const signal = this.mergeSignals(init.signal, timeoutController.signal);
|
||||
|
||||
try {
|
||||
return await fetch(url, {
|
||||
...init,
|
||||
signal,
|
||||
});
|
||||
} catch (error) {
|
||||
if (timedOut) throw new TimeoutError(url, timeoutMs);
|
||||
throw error;
|
||||
} finally {
|
||||
window.clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
private mergeSignals(
|
||||
requestSignal: AbortSignal | null | undefined,
|
||||
timeoutSignal: AbortSignal,
|
||||
): AbortSignal {
|
||||
if (!requestSignal) return timeoutSignal;
|
||||
if (requestSignal.aborted) return requestSignal;
|
||||
if (timeoutSignal.aborted) return timeoutSignal;
|
||||
|
||||
const controller = new AbortController();
|
||||
const abort = () => controller.abort();
|
||||
|
||||
requestSignal.addEventListener("abort", abort, { once: true });
|
||||
timeoutSignal.addEventListener("abort", abort, { once: true });
|
||||
|
||||
return controller.signal;
|
||||
}
|
||||
|
||||
private async createHttpError(
|
||||
response: Response,
|
||||
url: string,
|
||||
): Promise<HttpError> {
|
||||
const text = await response.text();
|
||||
let body: unknown;
|
||||
|
||||
if (text.trim()) {
|
||||
try {
|
||||
body = JSON.parse(text);
|
||||
} catch {
|
||||
body = text;
|
||||
}
|
||||
} else {
|
||||
body = undefined;
|
||||
}
|
||||
|
||||
return new HttpError(response.status, response.statusText, body, url);
|
||||
}
|
||||
|
||||
private async parseResponse<TResponse>(
|
||||
response: Response,
|
||||
url: string,
|
||||
): Promise<TResponse> {
|
||||
if (response.status === 204) return undefined as TResponse;
|
||||
|
||||
const contentLength = response.headers.get("content-length");
|
||||
if (contentLength === "0") return undefined as TResponse;
|
||||
|
||||
const text = await response.text();
|
||||
if (!text.trim()) return undefined as TResponse;
|
||||
|
||||
const contentType = response.headers.get("content-type") ?? "";
|
||||
if (!contentType.includes("application/json")) {
|
||||
return text as TResponse;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(text) as TResponse;
|
||||
} catch (error) {
|
||||
throw new ResponseParseError(url, error);
|
||||
}
|
||||
}
|
||||
|
||||
private normalizeError(
|
||||
error: unknown,
|
||||
url: string,
|
||||
timeoutMs?: number,
|
||||
): Error {
|
||||
if (
|
||||
error instanceof HttpError ||
|
||||
error instanceof NetworkError ||
|
||||
error instanceof TimeoutError ||
|
||||
error instanceof ResponseParseError
|
||||
) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (error instanceof DOMException && error.name === "AbortError") {
|
||||
if (timeoutMs) return new TimeoutError(url, timeoutMs);
|
||||
return new NetworkError("Request aborted", url, error);
|
||||
}
|
||||
|
||||
return new NetworkError(
|
||||
error instanceof Error ? error.message : "Network error",
|
||||
url,
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
private buildUrl(
|
||||
base: string,
|
||||
params?: Record<string, string | number | boolean | undefined>,
|
||||
): string {
|
||||
if (!params) return base;
|
||||
|
||||
const query = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined) query.append(key, String(value));
|
||||
}
|
||||
|
||||
const qs = query.toString();
|
||||
return qs ? `${base}?${qs}` : base;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,965 @@
|
||||
import { EndpointRegistry, HttpClient } from "./HttpClient";
|
||||
import { ACCOUNT_HEADER, getAccountId } from "@/lib/accountStore";
|
||||
|
||||
export class AuthenticationRequiredError extends Error {
|
||||
constructor() {
|
||||
super("Authentication is required before calling the API.");
|
||||
this.name = "AuthenticationRequiredError";
|
||||
}
|
||||
}
|
||||
|
||||
export const apiEndpointKeys = {
|
||||
clouds: {
|
||||
list: "clouds.list",
|
||||
services: "clouds.services.list",
|
||||
status: "clouds.status.get",
|
||||
serviceStatus: "clouds.services.status.get",
|
||||
schema: "clouds.services.schema.get",
|
||||
resources: {
|
||||
list: "clouds.services.resources.list",
|
||||
get: "clouds.services.resources.get",
|
||||
create: "clouds.services.resources.create",
|
||||
update: "clouds.services.resources.update",
|
||||
delete: "clouds.services.resources.delete",
|
||||
invoke: "clouds.services.resources.invoke",
|
||||
encrypt: "clouds.services.resources.encrypt",
|
||||
decrypt: "clouds.services.resources.decrypt",
|
||||
createApiGatewayRoute: "clouds.services.resources.apigateway.route.create",
|
||||
},
|
||||
storage: {
|
||||
objects: {
|
||||
list: "clouds.services.storage.objects.list",
|
||||
upload: "clouds.services.storage.objects.upload",
|
||||
download: "clouds.services.storage.objects.download",
|
||||
delete: "clouds.services.storage.objects.delete",
|
||||
copy: "clouds.services.storage.objects.copy",
|
||||
},
|
||||
},
|
||||
nosql: {
|
||||
cosmos: {
|
||||
containers: {
|
||||
list: "clouds.services.nosql.cosmos.containers.list",
|
||||
create: "clouds.services.nosql.cosmos.containers.create",
|
||||
delete: "clouds.services.nosql.cosmos.containers.delete",
|
||||
},
|
||||
items: {
|
||||
list: "clouds.services.nosql.cosmos.items.list",
|
||||
upsert: "clouds.services.nosql.cosmos.items.upsert",
|
||||
delete: "clouds.services.nosql.cosmos.items.delete",
|
||||
query: "clouds.services.nosql.cosmos.items.query",
|
||||
},
|
||||
},
|
||||
items: {
|
||||
list: "clouds.services.nosql.items.list",
|
||||
put: "clouds.services.nosql.items.put",
|
||||
},
|
||||
},
|
||||
database: {
|
||||
sql: {
|
||||
databases: "clouds.services.database.sql.databases.list",
|
||||
tables: "clouds.services.database.sql.tables.list",
|
||||
query: "clouds.services.database.sql.query",
|
||||
},
|
||||
snapshots: {
|
||||
list: "clouds.services.database.snapshots.list",
|
||||
create: "clouds.services.database.snapshots.create",
|
||||
},
|
||||
orderableClasses: {
|
||||
list: "clouds.services.database.orderable-classes.list",
|
||||
},
|
||||
},
|
||||
logs: {
|
||||
query: "clouds.services.logs.query",
|
||||
},
|
||||
k8s: {
|
||||
nodegroups: {
|
||||
list: "clouds.services.k8s.nodegroups.list",
|
||||
create: "clouds.services.k8s.nodegroups.create",
|
||||
delete: "clouds.services.k8s.nodegroups.delete",
|
||||
},
|
||||
fargateProfiles: {
|
||||
list: "clouds.services.k8s.fargate-profiles.list",
|
||||
create: "clouds.services.k8s.fargate-profiles.create",
|
||||
delete: "clouds.services.k8s.fargate-profiles.delete",
|
||||
},
|
||||
},
|
||||
email: {
|
||||
inbox: {
|
||||
clear: "clouds.services.email.inbox.clear",
|
||||
},
|
||||
},
|
||||
childCollections: {
|
||||
list: "clouds.services.childCollections.list",
|
||||
items: {
|
||||
list: "clouds.services.childCollections.items.list",
|
||||
},
|
||||
},
|
||||
configuration: {
|
||||
environments: {
|
||||
list: "clouds.services.configuration.environments.list",
|
||||
create: "clouds.services.configuration.environments.create",
|
||||
delete: "clouds.services.configuration.environments.delete",
|
||||
deployments: {
|
||||
start: "clouds.services.configuration.environments.deployments.start",
|
||||
get: "clouds.services.configuration.environments.deployments.get",
|
||||
},
|
||||
},
|
||||
configurationProfiles: {
|
||||
list: "clouds.services.configuration.profiles.list",
|
||||
create: "clouds.services.configuration.profiles.create",
|
||||
delete: "clouds.services.configuration.profiles.delete",
|
||||
hostedVersions: {
|
||||
list: "clouds.services.configuration.profiles.hosted-versions.list",
|
||||
get: "clouds.services.configuration.profiles.hosted-versions.get",
|
||||
create: "clouds.services.configuration.profiles.hosted-versions.create",
|
||||
delete: "clouds.services.configuration.profiles.hosted-versions.delete",
|
||||
},
|
||||
},
|
||||
deploymentStrategies: {
|
||||
list: "clouds.services.configuration.deployment-strategies.list",
|
||||
create: "clouds.services.configuration.deployment-strategies.create",
|
||||
delete: "clouds.services.configuration.deployment-strategies.delete",
|
||||
},
|
||||
},
|
||||
},
|
||||
aws: {
|
||||
eks: {
|
||||
clusters: {
|
||||
list: "aws.eks.clusters.list",
|
||||
describe: "aws.eks.clusters.describe",
|
||||
},
|
||||
nodegroups: {
|
||||
list: "aws.eks.nodegroups.list",
|
||||
describe: "aws.eks.nodegroups.describe",
|
||||
create: "aws.eks.nodegroups.create",
|
||||
delete: "aws.eks.nodegroups.delete",
|
||||
},
|
||||
fargateProfiles: {
|
||||
list: "aws.eks.fargate-profiles.list",
|
||||
describe: "aws.eks.fargate-profiles.describe",
|
||||
create: "aws.eks.fargate-profiles.create",
|
||||
delete: "aws.eks.fargate-profiles.delete",
|
||||
},
|
||||
},
|
||||
secretsmanager: {
|
||||
secrets: {
|
||||
list: "aws.secretsmanager.secrets.list",
|
||||
create: "aws.secretsmanager.secrets.create",
|
||||
describe: "aws.secretsmanager.secrets.describe",
|
||||
delete: "aws.secretsmanager.secrets.delete",
|
||||
value: {
|
||||
get: "aws.secretsmanager.secrets.value.get",
|
||||
put: "aws.secretsmanager.secrets.value.put",
|
||||
},
|
||||
},
|
||||
},
|
||||
ec2: {
|
||||
instances: {
|
||||
list: "aws.ec2.instances.list",
|
||||
describe: "aws.ec2.instances.describe",
|
||||
create: "aws.ec2.instances.create",
|
||||
start: "aws.ec2.instances.start",
|
||||
stop: "aws.ec2.instances.stop",
|
||||
reboot: "aws.ec2.instances.reboot",
|
||||
terminate: "aws.ec2.instances.terminate",
|
||||
tags: "aws.ec2.instances.tags",
|
||||
createImage: "aws.ec2.instances.create-image",
|
||||
console: "aws.ec2.instances.console",
|
||||
},
|
||||
amis: {
|
||||
list: "aws.ec2.amis.list",
|
||||
deregister: "aws.ec2.amis.deregister",
|
||||
},
|
||||
keyPairs: {
|
||||
list: "aws.ec2.key-pairs.list",
|
||||
create: "aws.ec2.key-pairs.create",
|
||||
delete: "aws.ec2.key-pairs.delete",
|
||||
},
|
||||
securityGroups: {
|
||||
list: "aws.ec2.security-groups.list",
|
||||
create: "aws.ec2.security-groups.create",
|
||||
delete: "aws.ec2.security-groups.delete",
|
||||
authorize: "aws.ec2.security-groups.authorize",
|
||||
revoke: "aws.ec2.security-groups.revoke",
|
||||
authorizeEgress: "aws.ec2.security-groups.authorize-egress",
|
||||
revokeEgress: "aws.ec2.security-groups.revoke-egress",
|
||||
},
|
||||
vpcs: {
|
||||
list: "aws.ec2.vpcs.list",
|
||||
create: "aws.ec2.vpcs.create",
|
||||
delete: "aws.ec2.vpcs.delete",
|
||||
getAttributes: "aws.ec2.vpcs.get-attributes",
|
||||
modifyAttribute: "aws.ec2.vpcs.modify-attribute",
|
||||
},
|
||||
subnets: {
|
||||
list: "aws.ec2.subnets.list",
|
||||
create: "aws.ec2.subnets.create",
|
||||
delete: "aws.ec2.subnets.delete",
|
||||
modifyAttribute: "aws.ec2.subnets.modify-attribute",
|
||||
},
|
||||
internetGateways: {
|
||||
list: "aws.ec2.igw.list",
|
||||
create: "aws.ec2.igw.create",
|
||||
attach: "aws.ec2.igw.attach",
|
||||
detach: "aws.ec2.igw.detach",
|
||||
delete: "aws.ec2.igw.delete",
|
||||
},
|
||||
natGateways: {
|
||||
list: "aws.ec2.nat.list",
|
||||
create: "aws.ec2.nat.create",
|
||||
delete: "aws.ec2.nat.delete",
|
||||
},
|
||||
routeTables: {
|
||||
list: "aws.ec2.rtb.list",
|
||||
create: "aws.ec2.rtb.create",
|
||||
delete: "aws.ec2.rtb.delete",
|
||||
createRoute: "aws.ec2.rtb.route.create",
|
||||
deleteRoute: "aws.ec2.rtb.route.delete",
|
||||
associate: "aws.ec2.rtb.associate",
|
||||
disassociate: "aws.ec2.rtb.disassociate",
|
||||
},
|
||||
elasticIps: {
|
||||
list: "aws.ec2.eip.list",
|
||||
create: "aws.ec2.eip.create",
|
||||
release: "aws.ec2.eip.release",
|
||||
associate: "aws.ec2.eip.associate",
|
||||
disassociate: "aws.ec2.eip.disassociate",
|
||||
},
|
||||
availabilityZones: "aws.ec2.availability-zones",
|
||||
instanceTypes: "aws.ec2.instance-types",
|
||||
vpcWizard: "aws.ec2.vpc-wizard",
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "/api";
|
||||
|
||||
export const endpointRegistry: EndpointRegistry = new Map([
|
||||
// Cloud Proxy
|
||||
[
|
||||
apiEndpointKeys.clouds.list,
|
||||
{
|
||||
path: "/clouds",
|
||||
method: "GET",
|
||||
telemetry: { provider: "system", service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.services,
|
||||
{
|
||||
path: "/clouds/:cloud/services",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.status,
|
||||
{
|
||||
path: "/clouds/:cloud/status",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.serviceStatus,
|
||||
{
|
||||
path: "/clouds/:cloud/services/:service/status",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.schema,
|
||||
{
|
||||
path: "/clouds/:cloud/services/:service/schema",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.resources.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/:service/resources",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.resources.invoke,
|
||||
{
|
||||
path: "/clouds/:cloud/services/:service/resources/:id/invoke",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.resources.createApiGatewayRoute,
|
||||
{
|
||||
path: "/clouds/:cloud/services/apigateway/resources/:id/routes",
|
||||
method: "POST",
|
||||
telemetry: { service: "apigateway" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.resources.get,
|
||||
{
|
||||
path: "/clouds/:cloud/services/:service/resources/:id",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.resources.encrypt,
|
||||
{
|
||||
path: "/clouds/:cloud/services/kms/resources/:id/encrypt",
|
||||
method: "POST",
|
||||
telemetry: { service: "kms" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.resources.decrypt,
|
||||
{
|
||||
path: "/clouds/:cloud/services/kms/resources/:id/decrypt",
|
||||
method: "POST",
|
||||
telemetry: { service: "kms" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.resources.create,
|
||||
{
|
||||
path: "/clouds/:cloud/services/:service/resources",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.resources.update,
|
||||
{
|
||||
path: "/clouds/:cloud/services/:service/resources/:id",
|
||||
method: "PATCH",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.resources.delete,
|
||||
{
|
||||
path: "/clouds/:cloud/services/:service/resources/:id",
|
||||
method: "DELETE",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
apiEndpointKeys.clouds.resources.invoke,
|
||||
{
|
||||
path: "/clouds/:cloud/services/:service/resources/:id/invoke",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
apiEndpointKeys.clouds.storage.objects.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/storage/resources/:id/objects",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.storage.objects.upload,
|
||||
{
|
||||
path: "/clouds/:cloud/services/storage/resources/:id/object",
|
||||
method: "PUT",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.storage.objects.download,
|
||||
{
|
||||
path: "/clouds/:cloud/services/storage/resources/:id/object",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.storage.objects.delete,
|
||||
{
|
||||
path: "/clouds/:cloud/services/storage/resources/:id/object",
|
||||
method: "DELETE",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.storage.objects.copy,
|
||||
{
|
||||
path: "/clouds/:cloud/services/storage/resources/:id/object/copy",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.nosql.cosmos.containers.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/nosql/resources/:id/containers",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.nosql.cosmos.containers.create,
|
||||
{
|
||||
path: "/clouds/:cloud/services/nosql/resources/:id/containers",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.nosql.cosmos.containers.delete,
|
||||
{
|
||||
path: "/clouds/:cloud/services/nosql/resources/:id/containers/:containerId",
|
||||
method: "DELETE",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.nosql.cosmos.items.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/nosql/resources/:id/containers/:containerId/items",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.nosql.cosmos.items.upsert,
|
||||
{
|
||||
path: "/clouds/:cloud/services/nosql/resources/:id/containers/:containerId/items",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.nosql.cosmos.items.delete,
|
||||
{
|
||||
path: "/clouds/:cloud/services/nosql/resources/:id/containers/:containerId/items/:itemId",
|
||||
method: "DELETE",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.nosql.cosmos.items.query,
|
||||
{
|
||||
path: "/clouds/:cloud/services/nosql/resources/:id/containers/:containerId/query",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.database.sql.databases,
|
||||
{
|
||||
path: "/clouds/:cloud/services/database/resources/:id/sql/databases",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.database.sql.tables,
|
||||
{
|
||||
path: "/clouds/:cloud/services/database/resources/:id/sql/tables",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.database.sql.query,
|
||||
{
|
||||
path: "/clouds/:cloud/services/database/resources/:id/sql/query",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.logs.query,
|
||||
{
|
||||
path: "/clouds/:cloud/services/logs/resources/:id/query",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.database.snapshots.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/database/snapshots",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.database.snapshots.create,
|
||||
{
|
||||
path: "/clouds/:cloud/services/database/snapshots",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.database.orderableClasses.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/database/orderable-classes",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.nosql.items.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/nosql/resources/:id/items",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.nosql.items.put,
|
||||
{
|
||||
path: "/clouds/:cloud/services/nosql/resources/:id/items",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.k8s.nodegroups.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/k8s/resources/:id/nodegroups",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.k8s.nodegroups.create,
|
||||
{
|
||||
path: "/clouds/:cloud/services/k8s/resources/:id/nodegroups",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.k8s.nodegroups.delete,
|
||||
{
|
||||
path: "/clouds/:cloud/services/k8s/resources/:id/nodegroups/:nodegroupId",
|
||||
method: "DELETE",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.k8s.fargateProfiles.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/k8s/resources/:id/fargate-profiles",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.k8s.fargateProfiles.create,
|
||||
{
|
||||
path: "/clouds/:cloud/services/k8s/resources/:id/fargate-profiles",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.k8s.fargateProfiles.delete,
|
||||
{
|
||||
path: "/clouds/:cloud/services/k8s/resources/:id/fargate-profiles/:profileId",
|
||||
method: "DELETE",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.email.inbox.clear,
|
||||
{
|
||||
path: "/clouds/:cloud/services/email/inbox",
|
||||
method: "DELETE",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.environments.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/resources/:id/environments",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.environments.create,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/resources/:id/environments",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.environments.delete,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/resources/:id/environments/:environmentId",
|
||||
method: "DELETE",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.environments.deployments.start,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/resources/:id/environments/:environmentId/deployments",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.environments.deployments.get,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/resources/:id/environments/:environmentId/deployments/:deploymentNumber",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/resources/:id/configuration-profiles",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.create,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/resources/:id/configuration-profiles",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.delete,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/resources/:id/configuration-profiles/:profileId",
|
||||
method: "DELETE",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.hostedVersions.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/resources/:id/configuration-profiles/:profileId/hosted-configuration-versions",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.hostedVersions.get,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/resources/:id/configuration-profiles/:profileId/hosted-configuration-versions/:versionNumber",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.hostedVersions.create,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/resources/:id/configuration-profiles/:profileId/hosted-configuration-versions",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.hostedVersions.delete,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/resources/:id/configuration-profiles/:profileId/hosted-configuration-versions/:versionNumber",
|
||||
method: "DELETE",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.deploymentStrategies.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/deployment-strategies",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.deploymentStrategies.create,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/deployment-strategies",
|
||||
method: "POST",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.configuration.deploymentStrategies.delete,
|
||||
{
|
||||
path: "/clouds/:cloud/services/configuration/deployment-strategies/:strategyId",
|
||||
method: "DELETE",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
apiEndpointKeys.clouds.childCollections.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/:service/resources/:id/collections",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.clouds.childCollections.items.list,
|
||||
{
|
||||
path: "/clouds/:cloud/services/:service/resources/:id/collections/:cid/items",
|
||||
method: "GET",
|
||||
telemetry: { service: "cloud-proxy" },
|
||||
},
|
||||
],
|
||||
// AWS EKS
|
||||
[
|
||||
apiEndpointKeys.aws.eks.clusters.list,
|
||||
{
|
||||
path: "/eks/clusters",
|
||||
method: "GET",
|
||||
telemetry: { provider: "aws", service: "eks" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.eks.clusters.describe,
|
||||
{
|
||||
path: "/eks/clusters/:name",
|
||||
method: "GET",
|
||||
telemetry: { provider: "aws", service: "eks" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.eks.nodegroups.list,
|
||||
{
|
||||
path: "/eks/clusters/:name/nodegroups",
|
||||
method: "GET",
|
||||
telemetry: { provider: "aws", service: "eks" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.eks.nodegroups.describe,
|
||||
{
|
||||
path: "/eks/clusters/:name/nodegroups/:nodegroup",
|
||||
method: "GET",
|
||||
telemetry: { provider: "aws", service: "eks" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.eks.nodegroups.create,
|
||||
{
|
||||
path: "/eks/clusters/:name/nodegroups",
|
||||
method: "POST",
|
||||
telemetry: { provider: "aws", service: "eks" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.eks.nodegroups.delete,
|
||||
{
|
||||
path: "/eks/clusters/:name/nodegroups/:nodegroup",
|
||||
method: "DELETE",
|
||||
telemetry: { provider: "aws", service: "eks" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.eks.fargateProfiles.list,
|
||||
{
|
||||
path: "/eks/clusters/:name/fargate-profiles",
|
||||
method: "GET",
|
||||
telemetry: { provider: "aws", service: "eks" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.eks.fargateProfiles.describe,
|
||||
{
|
||||
path: "/eks/clusters/:name/fargate-profiles/:profile",
|
||||
method: "GET",
|
||||
telemetry: { provider: "aws", service: "eks" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.eks.fargateProfiles.create,
|
||||
{
|
||||
path: "/eks/clusters/:name/fargate-profiles",
|
||||
method: "POST",
|
||||
telemetry: { provider: "aws", service: "eks" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.eks.fargateProfiles.delete,
|
||||
{
|
||||
path: "/eks/clusters/:name/fargate-profiles/:profile",
|
||||
method: "DELETE",
|
||||
telemetry: { provider: "aws", service: "eks" },
|
||||
},
|
||||
],
|
||||
|
||||
// AWS Secrets Manager
|
||||
[
|
||||
apiEndpointKeys.aws.secretsmanager.secrets.list,
|
||||
{
|
||||
path: "/secretsmanager/secrets",
|
||||
method: "GET",
|
||||
telemetry: { provider: "aws", service: "secretsmanager" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.secretsmanager.secrets.create,
|
||||
{
|
||||
path: "/secretsmanager/secrets",
|
||||
method: "POST",
|
||||
telemetry: { provider: "aws", service: "secretsmanager" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.secretsmanager.secrets.describe,
|
||||
{
|
||||
path: "/secretsmanager/secret",
|
||||
method: "GET",
|
||||
telemetry: { provider: "aws", service: "secretsmanager" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.secretsmanager.secrets.delete,
|
||||
{
|
||||
path: "/secretsmanager/secret",
|
||||
method: "DELETE",
|
||||
telemetry: { provider: "aws", service: "secretsmanager" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.secretsmanager.secrets.value.get,
|
||||
{
|
||||
path: "/secretsmanager/secret/value",
|
||||
method: "GET",
|
||||
telemetry: { provider: "aws", service: "secretsmanager" },
|
||||
},
|
||||
],
|
||||
[
|
||||
apiEndpointKeys.aws.secretsmanager.secrets.value.put,
|
||||
{
|
||||
path: "/secretsmanager/secret/value",
|
||||
method: "PUT",
|
||||
telemetry: { provider: "aws", service: "secretsmanager" },
|
||||
},
|
||||
],
|
||||
|
||||
// AWS EC2
|
||||
[apiEndpointKeys.aws.ec2.instances.list, { path: "/ec2/instances", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.instances.describe, { path: "/ec2/instances/:instanceId", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.instances.create, { path: "/ec2/instances", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.instances.start, { path: "/ec2/instances/:instanceId/start", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.instances.stop, { path: "/ec2/instances/:instanceId/stop", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.instances.reboot, { path: "/ec2/instances/:instanceId/reboot", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.instances.terminate,{ path: "/ec2/instances/:instanceId/terminate", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.instances.tags, { path: "/ec2/instances/:instanceId/tags", method: "PUT", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.instances.createImage, { path: "/ec2/instances/:instanceId/image", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.instances.console, { path: "/ec2/instances/:instanceId/console", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.amis.list, { path: "/ec2/amis", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.amis.deregister, { path: "/ec2/amis/:imageId/deregister", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.keyPairs.list, { path: "/ec2/key-pairs", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.keyPairs.create, { path: "/ec2/key-pairs", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.keyPairs.delete, { path: "/ec2/key-pairs/:name", method: "DELETE", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.securityGroups.list, { path: "/ec2/security-groups", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.securityGroups.create, { path: "/ec2/security-groups", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.securityGroups.delete, { path: "/ec2/security-groups/:groupId", method: "DELETE", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.securityGroups.authorize, { path: "/ec2/security-groups/:groupId/ingress", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.securityGroups.revoke, { path: "/ec2/security-groups/:groupId/ingress", method: "DELETE", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.securityGroups.authorizeEgress, { path: "/ec2/security-groups/:groupId/egress", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.securityGroups.revokeEgress, { path: "/ec2/security-groups/:groupId/egress", method: "DELETE", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.vpcs.list, { path: "/ec2/vpcs", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.vpcs.create, { path: "/ec2/vpcs", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.vpcs.delete, { path: "/ec2/vpcs/:vpcId", method: "DELETE", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.vpcs.getAttributes, { path: "/ec2/vpcs/:vpcId/attributes", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.vpcs.modifyAttribute, { path: "/ec2/vpcs/:vpcId/attributes", method: "PUT", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.subnets.list, { path: "/ec2/subnets", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.subnets.create, { path: "/ec2/subnets", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.subnets.delete, { path: "/ec2/subnets/:subnetId", method: "DELETE", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.subnets.modifyAttribute, { path: "/ec2/subnets/:subnetId/attributes", method: "PUT", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.internetGateways.list, { path: "/ec2/internet-gateways", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.internetGateways.create, { path: "/ec2/internet-gateways", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.internetGateways.attach, { path: "/ec2/internet-gateways/:igwId/attach", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.internetGateways.detach, { path: "/ec2/internet-gateways/:igwId/detach", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.internetGateways.delete, { path: "/ec2/internet-gateways/:igwId", method: "DELETE", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.natGateways.list, { path: "/ec2/nat-gateways", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.natGateways.create, { path: "/ec2/nat-gateways", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.natGateways.delete, { path: "/ec2/nat-gateways/:natId", method: "DELETE", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.routeTables.list, { path: "/ec2/route-tables", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.routeTables.create, { path: "/ec2/route-tables", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.routeTables.delete, { path: "/ec2/route-tables/:rtbId", method: "DELETE", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.routeTables.createRoute, { path: "/ec2/route-tables/:rtbId/routes", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.routeTables.deleteRoute, { path: "/ec2/route-tables/:rtbId/routes", method: "DELETE", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.routeTables.associate, { path: "/ec2/route-tables/:rtbId/associations", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.routeTables.disassociate,{ path: "/ec2/route-table-associations/:associationId", method: "DELETE", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.elasticIps.list, { path: "/ec2/elastic-ips", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.elasticIps.create, { path: "/ec2/elastic-ips", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.elasticIps.release, { path: "/ec2/elastic-ips/:allocationId/release", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.elasticIps.associate, { path: "/ec2/elastic-ips/:allocationId/associate", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.elasticIps.disassociate, { path: "/ec2/elastic-ips/:allocationId/disassociate", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.availabilityZones, { path: "/ec2/availability-zones", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.instanceTypes, { path: "/ec2/instance-types", method: "GET", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
[apiEndpointKeys.aws.ec2.vpcWizard, { path: "/ec2/vpc-wizard", method: "POST", telemetry: { provider: "aws", service: "ec2" } }],
|
||||
|
||||
]);
|
||||
|
||||
// ─── Client Factory ───────────────────────────────────────────────────────────
|
||||
|
||||
export function createApiClient(
|
||||
getToken?: () => string | null | Promise<string | null>,
|
||||
) {
|
||||
const client = new HttpClient(
|
||||
{
|
||||
baseUrl: API_BASE_URL,
|
||||
defaultHeaders: { Accept: "application/json" },
|
||||
timeout: 10_000,
|
||||
},
|
||||
endpointRegistry,
|
||||
);
|
||||
|
||||
// Account scope — every request carries the active AWS account so Floci can
|
||||
// isolate resources per account (the API maps this header to its SDK creds).
|
||||
client.addRequestInterceptor((url, init) => {
|
||||
const headers = (init.headers ?? {}) as Record<string, string>;
|
||||
headers[ACCOUNT_HEADER] = getAccountId();
|
||||
init.headers = headers;
|
||||
return { url, init };
|
||||
});
|
||||
|
||||
// Auth interceptor — attaches Bearer token if available
|
||||
if (getToken) {
|
||||
client.addRequestInterceptor(async (url, init) => {
|
||||
const token = await getToken();
|
||||
if (token) {
|
||||
(init.headers as Record<string, string>)["Authorization"] =
|
||||
`Bearer ${token}`;
|
||||
}
|
||||
return { url, init };
|
||||
});
|
||||
}
|
||||
|
||||
// Logging interceptor (dev only)
|
||||
if (import.meta.env.DEV) {
|
||||
client.addResponseInterceptor((res) => {
|
||||
console.debug(`[HTTP] ${res.status} ${res.url}`);
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
// client.addResponseInterceptor((res) => {
|
||||
// if (res.status === 401) {
|
||||
// handleUnauthorizedApiResponse();
|
||||
// }
|
||||
// return res;
|
||||
// });
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export const apiClient = createApiClient();
|
||||
@@ -0,0 +1,44 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createEc2SecurityGroup } from "./ec2.api";
|
||||
|
||||
describe("createEc2SecurityGroup", () => {
|
||||
const fetchMock = vi.fn<typeof fetch>();
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.mockResolvedValue(
|
||||
new Response(JSON.stringify({ groupId: "sg-created" }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}),
|
||||
);
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
fetchMock.mockReset();
|
||||
});
|
||||
|
||||
it.each(["vpc-selected", undefined])(
|
||||
"sends one JSON object through HttpClient with VPC %s",
|
||||
async (vpcId) => {
|
||||
const name = "web-servers";
|
||||
const description = 'Allow HTTP for "public" clients';
|
||||
|
||||
await expect(createEc2SecurityGroup(name, description, vpcId)).resolves.toEqual({
|
||||
groupId: "sg-created",
|
||||
});
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
const [url, init] = fetchMock.mock.calls[0];
|
||||
expect(String(url)).toContain("/api/ec2/security-groups");
|
||||
expect(init?.method).toBe("POST");
|
||||
expect(JSON.parse(String(init?.body))).toEqual({
|
||||
name,
|
||||
description,
|
||||
...(vpcId === undefined ? {} : { vpcId }),
|
||||
});
|
||||
expect(new Headers(init?.headers).get("content-type")).toBe("application/json");
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,730 @@
|
||||
import { apiClient, apiEndpointKeys } from "@/api/api";
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export type Ec2Tag = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type Ec2SecurityGroupRef = {
|
||||
id?: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export type Ec2IpPermission = {
|
||||
protocol: string;
|
||||
fromPort: number | null;
|
||||
toPort: number | null;
|
||||
ipRanges: string[];
|
||||
ipv6Ranges: string[];
|
||||
};
|
||||
|
||||
export type Ec2SecurityGroup = {
|
||||
groupId: string;
|
||||
groupName: string;
|
||||
description: string;
|
||||
vpcId?: string;
|
||||
inboundRules: Ec2IpPermission[];
|
||||
outboundRules: Ec2IpPermission[];
|
||||
tags: Ec2Tag[];
|
||||
};
|
||||
|
||||
export type Ec2Image = {
|
||||
imageId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
state?: string;
|
||||
architecture?: string;
|
||||
platform?: string;
|
||||
virtualizationType?: string;
|
||||
rootDeviceType?: string;
|
||||
createdAt?: string;
|
||||
ownerId?: string;
|
||||
public?: boolean;
|
||||
tags: Ec2Tag[];
|
||||
};
|
||||
|
||||
export type Ec2Instance = {
|
||||
instanceId: string;
|
||||
name: string;
|
||||
state?: string;
|
||||
instanceType?: string;
|
||||
availabilityZone?: string;
|
||||
publicIpAddress?: string;
|
||||
privateIpAddress?: string;
|
||||
vpcId?: string;
|
||||
subnetId?: string;
|
||||
imageId?: string;
|
||||
keyName?: string;
|
||||
launchTime?: string;
|
||||
architecture?: string;
|
||||
platform?: string;
|
||||
securityGroups: Ec2SecurityGroupRef[];
|
||||
tags: Ec2Tag[];
|
||||
};
|
||||
|
||||
export type Ec2KeyPair = {
|
||||
keyPairId: string;
|
||||
keyName: string;
|
||||
keyFingerprint?: string;
|
||||
tags: Ec2Tag[];
|
||||
};
|
||||
|
||||
export type Ec2KeyPairMaterial = {
|
||||
keyPairId: string;
|
||||
keyName: string;
|
||||
keyMaterial: string;
|
||||
};
|
||||
|
||||
export type Ec2Vpc = {
|
||||
vpcId: string;
|
||||
cidrBlock: string;
|
||||
state?: string;
|
||||
isDefault: boolean;
|
||||
tags: Ec2Tag[];
|
||||
};
|
||||
|
||||
export type Ec2Subnet = {
|
||||
subnetId: string;
|
||||
vpcId: string;
|
||||
cidrBlock: string;
|
||||
availabilityZone: string;
|
||||
availableIpAddressCount?: number;
|
||||
mapPublicIpOnLaunch: boolean;
|
||||
state?: string;
|
||||
tags: Ec2Tag[];
|
||||
};
|
||||
|
||||
export type Ec2AvailabilityZone = {
|
||||
zoneName: string;
|
||||
zoneId: string;
|
||||
state: string;
|
||||
};
|
||||
|
||||
export type Ec2InstanceType = {
|
||||
instanceType: string;
|
||||
vcpu: number;
|
||||
memoryMiB: number;
|
||||
};
|
||||
|
||||
export type Ec2VpcAttributes = {
|
||||
enableDnsHostnames: boolean;
|
||||
enableDnsSupport: boolean;
|
||||
};
|
||||
|
||||
export type Ec2ConsoleOutput = {
|
||||
instanceId: string;
|
||||
output: string;
|
||||
timestamp?: string;
|
||||
};
|
||||
|
||||
export type RunEc2InstanceInput = {
|
||||
name: string;
|
||||
imageId: string;
|
||||
instanceType: string;
|
||||
keyName?: string;
|
||||
subnetId?: string;
|
||||
securityGroupIds?: string[];
|
||||
userData?: string;
|
||||
iamInstanceProfileName?: string;
|
||||
associatePublicIpAddress?: boolean;
|
||||
rootVolumeSize?: number;
|
||||
rootVolumeType?: string;
|
||||
};
|
||||
|
||||
export type CreateAmiInput = {
|
||||
name: string;
|
||||
description?: string;
|
||||
noReboot?: boolean;
|
||||
};
|
||||
|
||||
export type IpPermissionInput = {
|
||||
protocol: string;
|
||||
fromPort: number;
|
||||
toPort: number;
|
||||
cidr: string;
|
||||
};
|
||||
|
||||
export type SubnetGroup = {
|
||||
name: string;
|
||||
count: number;
|
||||
isPublic: boolean;
|
||||
az?: string;
|
||||
prefix?: number;
|
||||
};
|
||||
|
||||
export type VpcWizardInput = {
|
||||
name: string;
|
||||
cidrBlock: string;
|
||||
subnetGroups: SubnetGroup[];
|
||||
natGateway: boolean;
|
||||
availabilityZone?: string;
|
||||
};
|
||||
|
||||
export type VpcWizardResult = {
|
||||
vpcId: string;
|
||||
igwId: string;
|
||||
publicSubnetIds: string[];
|
||||
privateSubnetIds: string[];
|
||||
subnetGroups: Array<{ name: string; isPublic: boolean; subnetIds: string[] }>;
|
||||
natGatewayId?: string;
|
||||
eipAllocationId?: string;
|
||||
publicRouteTableId: string;
|
||||
privateRouteTableId?: string;
|
||||
};
|
||||
|
||||
export type Ec2InternetGateway = {
|
||||
internetGatewayId: string;
|
||||
attachments: Array<{ vpcId: string; state: string }>;
|
||||
tags: Ec2Tag[];
|
||||
};
|
||||
|
||||
export type Ec2NatGateway = {
|
||||
natGatewayId: string;
|
||||
subnetId: string;
|
||||
vpcId: string;
|
||||
state?: string;
|
||||
publicIp?: string;
|
||||
privateIp?: string;
|
||||
eipAllocationId?: string;
|
||||
tags: Ec2Tag[];
|
||||
};
|
||||
|
||||
export type Ec2Route = {
|
||||
destinationCidrBlock?: string;
|
||||
gatewayId?: string;
|
||||
natGatewayId?: string;
|
||||
vpcPeeringConnectionId?: string;
|
||||
state?: string;
|
||||
origin?: string;
|
||||
};
|
||||
|
||||
export type Ec2RouteTableAssociation = {
|
||||
associationId: string;
|
||||
subnetId?: string;
|
||||
isMain: boolean;
|
||||
};
|
||||
|
||||
export type Ec2RouteTable = {
|
||||
routeTableId: string;
|
||||
vpcId: string;
|
||||
routes: Ec2Route[];
|
||||
associations: Ec2RouteTableAssociation[];
|
||||
tags: Ec2Tag[];
|
||||
};
|
||||
|
||||
export type Ec2ElasticIp = {
|
||||
allocationId: string;
|
||||
publicIp: string;
|
||||
domain?: string;
|
||||
associationId?: string;
|
||||
instanceId?: string;
|
||||
networkInterfaceId?: string;
|
||||
tags: Ec2Tag[];
|
||||
};
|
||||
|
||||
export type CreateNatGatewayInput = {
|
||||
name?: string;
|
||||
subnetId: string;
|
||||
allocationId?: string;
|
||||
};
|
||||
|
||||
export type CreateRouteInput = {
|
||||
destinationCidrBlock: string;
|
||||
gatewayId?: string;
|
||||
natGatewayId?: string;
|
||||
vpcPeeringConnectionId?: string;
|
||||
};
|
||||
|
||||
export type ResourceSummary = {
|
||||
id: string;
|
||||
name: string;
|
||||
status?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
// ─── Functions ────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function listEc2Instances(signal?: AbortSignal): Promise<Ec2Instance[]> {
|
||||
const res = await apiClient.call<Ec2Instance[]>(apiEndpointKeys.aws.ec2.instances.list, { signal });
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function describeEc2Instance(instanceId: string, signal?: AbortSignal): Promise<Ec2Instance> {
|
||||
const res = await apiClient.call<Ec2Instance>(
|
||||
apiEndpointKeys.aws.ec2.instances.describe,
|
||||
{ signal },
|
||||
{ instanceId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function runEc2Instance(input: RunEc2InstanceInput): Promise<Ec2Instance> {
|
||||
const res = await apiClient.call<Ec2Instance>(
|
||||
apiEndpointKeys.aws.ec2.instances.create,
|
||||
{ body: JSON.stringify(input), headers: { "content-type": "application/json" } },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function startEc2Instance(instanceId: string): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.instances.start,
|
||||
{ body: "{}", headers: { "content-type": "application/json" } },
|
||||
{ instanceId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function stopEc2Instance(instanceId: string): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.instances.stop,
|
||||
{ body: "{}", headers: { "content-type": "application/json" } },
|
||||
{ instanceId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function rebootEc2Instance(instanceId: string): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.instances.reboot,
|
||||
{ body: "{}", headers: { "content-type": "application/json" } },
|
||||
{ instanceId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function terminateEc2Instance(instanceId: string): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.instances.terminate,
|
||||
{},
|
||||
{ instanceId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function updateEc2InstanceTags(
|
||||
instanceId: string,
|
||||
toAdd: Ec2Tag[],
|
||||
toRemove: string[],
|
||||
): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.instances.tags,
|
||||
{ body: JSON.stringify({ toAdd, toRemove }), headers: { "content-type": "application/json" } },
|
||||
{ instanceId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function createEc2Ami(instanceId: string, input: CreateAmiInput): Promise<{ imageId: string }> {
|
||||
const res = await apiClient.call<{ imageId: string }>(
|
||||
apiEndpointKeys.aws.ec2.instances.createImage,
|
||||
{ body: JSON.stringify(input), headers: { "content-type": "application/json" } },
|
||||
{ instanceId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function getEc2ConsoleOutput(instanceId: string, signal?: AbortSignal): Promise<Ec2ConsoleOutput> {
|
||||
const res = await apiClient.call<Ec2ConsoleOutput>(
|
||||
apiEndpointKeys.aws.ec2.instances.console,
|
||||
{ signal },
|
||||
{ instanceId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listEc2Amis(signal?: AbortSignal): Promise<Ec2Image[]> {
|
||||
const res = await apiClient.call<Ec2Image[]>(apiEndpointKeys.aws.ec2.amis.list, { signal });
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deregisterEc2Ami(imageId: string): Promise<void> {
|
||||
await apiClient.call(apiEndpointKeys.aws.ec2.amis.deregister, {}, { imageId });
|
||||
}
|
||||
|
||||
export async function listEc2KeyPairs(signal?: AbortSignal): Promise<Ec2KeyPair[]> {
|
||||
const res = await apiClient.call<Ec2KeyPair[]>(apiEndpointKeys.aws.ec2.keyPairs.list, { signal });
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createEc2KeyPair(name: string): Promise<Ec2KeyPairMaterial> {
|
||||
const res = await apiClient.call<Ec2KeyPairMaterial>(
|
||||
apiEndpointKeys.aws.ec2.keyPairs.create,
|
||||
{ body: JSON.stringify({ name }), headers: { "content-type": "application/json" } },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteEc2KeyPair(name: string): Promise<void> {
|
||||
await apiClient.call(apiEndpointKeys.aws.ec2.keyPairs.delete, {}, { name });
|
||||
}
|
||||
|
||||
export async function listEc2SecurityGroups(vpcId?: string, signal?: AbortSignal): Promise<Ec2SecurityGroup[]> {
|
||||
const res = await apiClient.call<Ec2SecurityGroup[]>(
|
||||
apiEndpointKeys.aws.ec2.securityGroups.list,
|
||||
{ params: { vpcId }, signal },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createEc2SecurityGroup(
|
||||
name: string,
|
||||
description: string,
|
||||
vpcId?: string,
|
||||
): Promise<{ groupId: string }> {
|
||||
const res = await apiClient.call<
|
||||
{ groupId: string },
|
||||
{ name: string; description: string; vpcId?: string }
|
||||
>(
|
||||
apiEndpointKeys.aws.ec2.securityGroups.create,
|
||||
{ body: { name, description, vpcId } },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteEc2SecurityGroup(groupId: string): Promise<void> {
|
||||
await apiClient.call(apiEndpointKeys.aws.ec2.securityGroups.delete, {}, { groupId });
|
||||
}
|
||||
|
||||
export async function authorizeEc2SecurityGroupIngress(
|
||||
groupId: string,
|
||||
permission: IpPermissionInput,
|
||||
): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.securityGroups.authorize,
|
||||
{ body: JSON.stringify(permission), headers: { "content-type": "application/json" } },
|
||||
{ groupId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function revokeEc2SecurityGroupIngress(
|
||||
groupId: string,
|
||||
permission: IpPermissionInput,
|
||||
): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.securityGroups.revoke,
|
||||
{ body: JSON.stringify(permission), headers: { "content-type": "application/json" } },
|
||||
{ groupId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function authorizeEc2SecurityGroupEgress(
|
||||
groupId: string,
|
||||
permission: IpPermissionInput,
|
||||
): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.securityGroups.authorizeEgress,
|
||||
{ body: JSON.stringify(permission), headers: { "content-type": "application/json" } },
|
||||
{ groupId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function revokeEc2SecurityGroupEgress(
|
||||
groupId: string,
|
||||
permission: IpPermissionInput,
|
||||
): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.securityGroups.revokeEgress,
|
||||
{ body: JSON.stringify(permission), headers: { "content-type": "application/json" } },
|
||||
{ groupId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function listEc2AvailabilityZones(signal?: AbortSignal): Promise<Ec2AvailabilityZone[]> {
|
||||
const res = await apiClient.call<Ec2AvailabilityZone[]>(apiEndpointKeys.aws.ec2.availabilityZones, { signal });
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listEc2InstanceTypes(signal?: AbortSignal): Promise<Ec2InstanceType[]> {
|
||||
const res = await apiClient.call<Ec2InstanceType[]>(apiEndpointKeys.aws.ec2.instanceTypes, { signal });
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function modifyEc2SubnetAttribute(subnetId: string, mapPublicIpOnLaunch: boolean): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.subnets.modifyAttribute,
|
||||
{ body: JSON.stringify({ mapPublicIpOnLaunch }), headers: { "content-type": "application/json" } },
|
||||
{ subnetId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function getEc2VpcAttributes(vpcId: string, signal?: AbortSignal): Promise<Ec2VpcAttributes> {
|
||||
const res = await apiClient.call<Ec2VpcAttributes>(
|
||||
apiEndpointKeys.aws.ec2.vpcs.getAttributes,
|
||||
{ signal },
|
||||
{ vpcId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function modifyEc2VpcAttribute(
|
||||
vpcId: string,
|
||||
attribute: "enableDnsHostnames" | "enableDnsSupport",
|
||||
value: boolean,
|
||||
): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.vpcs.modifyAttribute,
|
||||
{ body: JSON.stringify({ attribute, value }), headers: { "content-type": "application/json" } },
|
||||
{ vpcId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function associateEc2ElasticIp(
|
||||
allocationId: string,
|
||||
instanceId: string,
|
||||
): Promise<{ associationId: string }> {
|
||||
const res = await apiClient.call<{ associationId: string }>(
|
||||
apiEndpointKeys.aws.ec2.elasticIps.associate,
|
||||
{ body: JSON.stringify({ instanceId }), headers: { "content-type": "application/json" } },
|
||||
{ allocationId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function disassociateEc2ElasticIp(allocationId: string, associationId: string): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.elasticIps.disassociate,
|
||||
{ body: JSON.stringify({ associationId }), headers: { "content-type": "application/json" } },
|
||||
{ allocationId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function listEc2Vpcs(signal?: AbortSignal): Promise<Ec2Vpc[]> {
|
||||
const res = await apiClient.call<Ec2Vpc[]>(apiEndpointKeys.aws.ec2.vpcs.list, { signal });
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createEc2Vpc(cidrBlock: string): Promise<Ec2Vpc> {
|
||||
const res = await apiClient.call<Ec2Vpc>(
|
||||
apiEndpointKeys.aws.ec2.vpcs.create,
|
||||
{ body: JSON.stringify({ cidrBlock }), headers: { "content-type": "application/json" } },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteEc2Vpc(vpcId: string): Promise<void> {
|
||||
await apiClient.call(apiEndpointKeys.aws.ec2.vpcs.delete, {}, { vpcId });
|
||||
}
|
||||
|
||||
export async function listEc2Subnets(vpcId?: string, signal?: AbortSignal): Promise<Ec2Subnet[]> {
|
||||
const res = await apiClient.call<Ec2Subnet[]>(
|
||||
apiEndpointKeys.aws.ec2.subnets.list,
|
||||
{ params: { vpcId }, signal },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createEc2Subnet(
|
||||
vpcId: string,
|
||||
cidrBlock: string,
|
||||
availabilityZone?: string,
|
||||
): Promise<Ec2Subnet> {
|
||||
const res = await apiClient.call<Ec2Subnet>(
|
||||
apiEndpointKeys.aws.ec2.subnets.create,
|
||||
{ body: JSON.stringify({ vpcId, cidrBlock, availabilityZone }), headers: { "content-type": "application/json" } },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteEc2Subnet(subnetId: string): Promise<void> {
|
||||
await apiClient.call(apiEndpointKeys.aws.ec2.subnets.delete, {}, { subnetId });
|
||||
}
|
||||
|
||||
export async function listEc2InternetGateways(signal?: AbortSignal): Promise<Ec2InternetGateway[]> {
|
||||
const res = await apiClient.call<Ec2InternetGateway[]>(apiEndpointKeys.aws.ec2.internetGateways.list, { signal });
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createEc2InternetGateway(name?: string): Promise<Ec2InternetGateway> {
|
||||
const res = await apiClient.call<Ec2InternetGateway>(
|
||||
apiEndpointKeys.aws.ec2.internetGateways.create,
|
||||
{ body: JSON.stringify({ name }), headers: { "content-type": "application/json" } },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function attachEc2InternetGateway(igwId: string, vpcId: string): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.internetGateways.attach,
|
||||
{ body: JSON.stringify({ vpcId }), headers: { "content-type": "application/json" } },
|
||||
{ igwId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function detachEc2InternetGateway(igwId: string, vpcId: string): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.internetGateways.detach,
|
||||
{ body: JSON.stringify({ vpcId }), headers: { "content-type": "application/json" } },
|
||||
{ igwId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteEc2InternetGateway(igwId: string): Promise<void> {
|
||||
await apiClient.call(apiEndpointKeys.aws.ec2.internetGateways.delete, {}, { igwId });
|
||||
}
|
||||
|
||||
export async function listEc2NatGateways(vpcId?: string, signal?: AbortSignal): Promise<Ec2NatGateway[]> {
|
||||
const res = await apiClient.call<Ec2NatGateway[]>(
|
||||
apiEndpointKeys.aws.ec2.natGateways.list,
|
||||
{ params: { vpcId }, signal },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createEc2NatGateway(input: CreateNatGatewayInput): Promise<Ec2NatGateway> {
|
||||
const res = await apiClient.call<Ec2NatGateway>(
|
||||
apiEndpointKeys.aws.ec2.natGateways.create,
|
||||
{ body: JSON.stringify(input), headers: { "content-type": "application/json" } },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteEc2NatGateway(natId: string): Promise<void> {
|
||||
await apiClient.call(apiEndpointKeys.aws.ec2.natGateways.delete, {}, { natId });
|
||||
}
|
||||
|
||||
export async function listEc2RouteTables(vpcId?: string, signal?: AbortSignal): Promise<Ec2RouteTable[]> {
|
||||
const res = await apiClient.call<Ec2RouteTable[]>(
|
||||
apiEndpointKeys.aws.ec2.routeTables.list,
|
||||
{ params: { vpcId }, signal },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createEc2RouteTable(vpcId: string, name?: string): Promise<Ec2RouteTable> {
|
||||
const res = await apiClient.call<Ec2RouteTable>(
|
||||
apiEndpointKeys.aws.ec2.routeTables.create,
|
||||
{ body: JSON.stringify({ vpcId, name }), headers: { "content-type": "application/json" } },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteEc2RouteTable(rtbId: string): Promise<void> {
|
||||
await apiClient.call(apiEndpointKeys.aws.ec2.routeTables.delete, {}, { rtbId });
|
||||
}
|
||||
|
||||
export async function createEc2Route(rtbId: string, input: CreateRouteInput): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.routeTables.createRoute,
|
||||
{ body: JSON.stringify(input), headers: { "content-type": "application/json" } },
|
||||
{ rtbId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteEc2Route(rtbId: string, cidr: string): Promise<void> {
|
||||
await apiClient.call(
|
||||
apiEndpointKeys.aws.ec2.routeTables.deleteRoute,
|
||||
{ params: { cidr } },
|
||||
{ rtbId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function associateEc2RouteTable(rtbId: string, subnetId: string): Promise<string> {
|
||||
const res = await apiClient.call<{ associationId: string }>(
|
||||
apiEndpointKeys.aws.ec2.routeTables.associate,
|
||||
{ body: JSON.stringify({ subnetId }), headers: { "content-type": "application/json" } },
|
||||
{ rtbId },
|
||||
);
|
||||
return res.data.associationId;
|
||||
}
|
||||
|
||||
export async function disassociateEc2RouteTable(associationId: string): Promise<void> {
|
||||
await apiClient.call(apiEndpointKeys.aws.ec2.routeTables.disassociate, {}, { associationId });
|
||||
}
|
||||
|
||||
export async function listEc2ElasticIps(signal?: AbortSignal): Promise<Ec2ElasticIp[]> {
|
||||
const res = await apiClient.call<Ec2ElasticIp[]>(apiEndpointKeys.aws.ec2.elasticIps.list, { signal });
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function allocateEc2ElasticIp(name?: string): Promise<Ec2ElasticIp> {
|
||||
const res = await apiClient.call<Ec2ElasticIp>(
|
||||
apiEndpointKeys.aws.ec2.elasticIps.create,
|
||||
{ body: JSON.stringify({ name }), headers: { "content-type": "application/json" } },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function releaseEc2ElasticIp(allocationId: string): Promise<void> {
|
||||
await apiClient.call(apiEndpointKeys.aws.ec2.elasticIps.release, {}, { allocationId });
|
||||
}
|
||||
|
||||
export async function createVpcWizard(input: VpcWizardInput): Promise<VpcWizardResult> {
|
||||
const res = await apiClient.call<VpcWizardResult>(
|
||||
apiEndpointKeys.aws.ec2.vpcWizard,
|
||||
{ body: JSON.stringify(input), headers: { "content-type": "application/json" } },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listEc2Resources(signal?: AbortSignal): Promise<ResourceSummary[]> {
|
||||
const instances = await listEc2Instances(signal);
|
||||
return instances.map((instance) => ({
|
||||
id: instance.instanceId,
|
||||
name: instance.name,
|
||||
status: instance.state,
|
||||
metadata: {
|
||||
instanceType: instance.instanceType,
|
||||
az: instance.availabilityZone,
|
||||
publicIp: instance.publicIpAddress,
|
||||
privateIp: instance.privateIpAddress,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
// ─── Client ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export const ec2Client = {
|
||||
listInstances: listEc2Instances,
|
||||
describeInstance: describeEc2Instance,
|
||||
runInstance: runEc2Instance,
|
||||
startInstance: startEc2Instance,
|
||||
stopInstance: stopEc2Instance,
|
||||
rebootInstance: rebootEc2Instance,
|
||||
terminateInstance: terminateEc2Instance,
|
||||
updateInstanceTags: updateEc2InstanceTags,
|
||||
createAmi: createEc2Ami,
|
||||
getConsoleOutput: getEc2ConsoleOutput,
|
||||
listAmis: listEc2Amis,
|
||||
deregisterAmi: deregisterEc2Ami,
|
||||
listResources: listEc2Resources,
|
||||
listKeyPairs: listEc2KeyPairs,
|
||||
createKeyPair: createEc2KeyPair,
|
||||
deleteKeyPair: deleteEc2KeyPair,
|
||||
listSecurityGroups: listEc2SecurityGroups,
|
||||
createSecurityGroup: createEc2SecurityGroup,
|
||||
deleteSecurityGroup: deleteEc2SecurityGroup,
|
||||
authorizeSecurityGroupIngress: authorizeEc2SecurityGroupIngress,
|
||||
revokeSecurityGroupIngress: revokeEc2SecurityGroupIngress,
|
||||
authorizeSecurityGroupEgress: authorizeEc2SecurityGroupEgress,
|
||||
revokeSecurityGroupEgress: revokeEc2SecurityGroupEgress,
|
||||
listAvailabilityZones: listEc2AvailabilityZones,
|
||||
listInstanceTypes: listEc2InstanceTypes,
|
||||
modifySubnetAttribute: modifyEc2SubnetAttribute,
|
||||
getVpcAttributes: getEc2VpcAttributes,
|
||||
modifyVpcAttribute: modifyEc2VpcAttribute,
|
||||
associateElasticIp: associateEc2ElasticIp,
|
||||
disassociateElasticIp: disassociateEc2ElasticIp,
|
||||
listVpcs: listEc2Vpcs,
|
||||
createVpc: createEc2Vpc,
|
||||
deleteVpc: deleteEc2Vpc,
|
||||
listSubnets: listEc2Subnets,
|
||||
createSubnet: createEc2Subnet,
|
||||
deleteSubnet: deleteEc2Subnet,
|
||||
listInternetGateways: listEc2InternetGateways,
|
||||
createInternetGateway: createEc2InternetGateway,
|
||||
attachInternetGateway: attachEc2InternetGateway,
|
||||
detachInternetGateway: detachEc2InternetGateway,
|
||||
deleteInternetGateway: deleteEc2InternetGateway,
|
||||
listNatGateways: listEc2NatGateways,
|
||||
createNatGateway: createEc2NatGateway,
|
||||
deleteNatGateway: deleteEc2NatGateway,
|
||||
listRouteTables: listEc2RouteTables,
|
||||
createRouteTable: createEc2RouteTable,
|
||||
deleteRouteTable: deleteEc2RouteTable,
|
||||
createRoute: createEc2Route,
|
||||
deleteRoute: deleteEc2Route,
|
||||
associateRouteTable: associateEc2RouteTable,
|
||||
disassociateRouteTable: disassociateEc2RouteTable,
|
||||
listElasticIps: listEc2ElasticIps,
|
||||
allocateElasticIp: allocateEc2ElasticIp,
|
||||
releaseElasticIp: releaseEc2ElasticIp,
|
||||
createVpcWizard,
|
||||
};
|
||||
@@ -0,0 +1,493 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
allocateEc2ElasticIp,
|
||||
associateEc2ElasticIp,
|
||||
associateEc2RouteTable,
|
||||
attachEc2InternetGateway,
|
||||
authorizeEc2SecurityGroupEgress,
|
||||
authorizeEc2SecurityGroupIngress,
|
||||
createEc2Ami,
|
||||
createEc2InternetGateway,
|
||||
createEc2KeyPair,
|
||||
createEc2NatGateway,
|
||||
createEc2Route,
|
||||
createEc2RouteTable,
|
||||
createEc2SecurityGroup,
|
||||
createEc2Subnet,
|
||||
createEc2Vpc,
|
||||
deregisterEc2Ami,
|
||||
deleteEc2InternetGateway,
|
||||
deleteEc2KeyPair,
|
||||
deleteEc2NatGateway,
|
||||
deleteEc2Route,
|
||||
deleteEc2RouteTable,
|
||||
deleteEc2SecurityGroup,
|
||||
deleteEc2Subnet,
|
||||
deleteEc2Vpc,
|
||||
detachEc2InternetGateway,
|
||||
disassociateEc2ElasticIp,
|
||||
disassociateEc2RouteTable,
|
||||
modifyEc2SubnetAttribute,
|
||||
modifyEc2VpcAttribute,
|
||||
rebootEc2Instance,
|
||||
releaseEc2ElasticIp,
|
||||
revokeEc2SecurityGroupEgress,
|
||||
revokeEc2SecurityGroupIngress,
|
||||
runEc2Instance,
|
||||
startEc2Instance,
|
||||
stopEc2Instance,
|
||||
terminateEc2Instance,
|
||||
updateEc2InstanceTags,
|
||||
} from "./ec2.api";
|
||||
import type {
|
||||
CreateAmiInput,
|
||||
CreateNatGatewayInput,
|
||||
CreateRouteInput,
|
||||
Ec2Tag,
|
||||
IpPermissionInput,
|
||||
RunEc2InstanceInput,
|
||||
} from "./ec2.api";
|
||||
import { ec2QueryKeys } from "./ec2.queries";
|
||||
|
||||
// ─── Instance lifecycle ───────────────────────────────────────────────────────
|
||||
|
||||
export function useCreateEc2InstanceMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (input: RunEc2InstanceInput) => runEc2Instance(input),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.instances });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useStartEc2InstanceMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (instanceId: string) => startEc2Instance(instanceId),
|
||||
onSuccess: (_data, instanceId) => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.instances });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.instance(instanceId) });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useStopEc2InstanceMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (instanceId: string) => stopEc2Instance(instanceId),
|
||||
onSuccess: (_data, instanceId) => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.instances });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.instance(instanceId) });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRebootEc2InstanceMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (instanceId: string) => rebootEc2Instance(instanceId),
|
||||
onSuccess: (_data, instanceId) => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.instances });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.instance(instanceId) });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useTerminateEc2InstanceMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (instanceId: string) => terminateEc2Instance(instanceId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.instances });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateEc2InstanceTagsMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ instanceId, toAdd, toRemove }: { instanceId: string; toAdd: Ec2Tag[]; toRemove: string[] }) =>
|
||||
updateEc2InstanceTags(instanceId, toAdd, toRemove),
|
||||
onSuccess: (_data, { instanceId }) => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.instance(instanceId) });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.instances });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ─── AMIs ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export function useCreateAmiMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ instanceId, input }: { instanceId: string; input: CreateAmiInput }) =>
|
||||
createEc2Ami(instanceId, input),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.amis });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeregisterAmiMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (imageId: string) => deregisterEc2Ami(imageId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.amis });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Key pairs ────────────────────────────────────────────────────────────────
|
||||
|
||||
export function useCreateKeyPairMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (name: string) => createEc2KeyPair(name),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.keyPairs });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteKeyPairMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (name: string) => deleteEc2KeyPair(name),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.keyPairs });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Security groups ──────────────────────────────────────────────────────────
|
||||
|
||||
export function useCreateSecurityGroupMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ name, description, vpcId }: { name: string; description: string; vpcId: string }) =>
|
||||
createEc2SecurityGroup(name, description, vpcId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.securityGroupsAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteSecurityGroupMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (groupId: string) => deleteEc2SecurityGroup(groupId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.securityGroupsAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAuthorizeSecurityGroupIngressMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ groupId, permission }: { groupId: string; permission: IpPermissionInput }) =>
|
||||
authorizeEc2SecurityGroupIngress(groupId, permission),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.securityGroupsAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRevokeSecurityGroupIngressMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ groupId, permission }: { groupId: string; permission: IpPermissionInput }) =>
|
||||
revokeEc2SecurityGroupIngress(groupId, permission),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.securityGroupsAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAuthorizeSecurityGroupEgressMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ groupId, permission }: { groupId: string; permission: IpPermissionInput }) =>
|
||||
authorizeEc2SecurityGroupEgress(groupId, permission),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.securityGroupsAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRevokeSecurityGroupEgressMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ groupId, permission }: { groupId: string; permission: IpPermissionInput }) =>
|
||||
revokeEc2SecurityGroupEgress(groupId, permission),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.securityGroupsAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ─── VPCs ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export function useCreateVpcMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (cidrBlock: string) => createEc2Vpc(cidrBlock),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.vpcs });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteVpcMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (vpcId: string) => deleteEc2Vpc(vpcId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.vpcs });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.subnetsAll });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.securityGroupsAll });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.internetGateways });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.routeTablesAll });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.elasticIps });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Subnets ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export function useCreateSubnetMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
vpcId,
|
||||
cidrBlock,
|
||||
availabilityZone,
|
||||
}: {
|
||||
vpcId: string;
|
||||
cidrBlock: string;
|
||||
availabilityZone?: string;
|
||||
}) => createEc2Subnet(vpcId, cidrBlock, availabilityZone),
|
||||
onSuccess: (_data, { vpcId }) => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.subnets(vpcId) });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.subnets() });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteSubnetMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (subnetId: string) => deleteEc2Subnet(subnetId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.subnetsAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Internet Gateways ────────────────────────────────────────────────────────
|
||||
|
||||
export function useCreateInternetGatewayMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (name?: string) => createEc2InternetGateway(name),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.internetGateways });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAttachInternetGatewayMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ igwId, vpcId }: { igwId: string; vpcId: string }) =>
|
||||
attachEc2InternetGateway(igwId, vpcId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.internetGateways });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDetachInternetGatewayMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ igwId, vpcId }: { igwId: string; vpcId: string }) =>
|
||||
detachEc2InternetGateway(igwId, vpcId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.internetGateways });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteInternetGatewayMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (igwId: string) => deleteEc2InternetGateway(igwId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.internetGateways });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.routeTablesAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ─── NAT Gateways ─────────────────────────────────────────────────────────────
|
||||
|
||||
export function useCreateNatGatewayMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (input: CreateNatGatewayInput) => createEc2NatGateway(input),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.natGatewaysAll });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.elasticIps });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteNatGatewayMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (natId: string) => deleteEc2NatGateway(natId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.natGatewaysAll });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.routeTablesAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Route Tables ─────────────────────────────────────────────────────────────
|
||||
|
||||
export function useCreateRouteTableMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ vpcId, name }: { vpcId: string; name?: string }) =>
|
||||
createEc2RouteTable(vpcId, name),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.routeTablesAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteRouteTableMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (rtbId: string) => deleteEc2RouteTable(rtbId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.routeTablesAll });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.subnetsAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateRouteMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ rtbId, input }: { rtbId: string; input: CreateRouteInput }) =>
|
||||
createEc2Route(rtbId, input),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.routeTablesAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteRouteMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ rtbId, cidr }: { rtbId: string; cidr: string }) =>
|
||||
deleteEc2Route(rtbId, cidr),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.routeTablesAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAssociateRouteTableMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ rtbId, subnetId }: { rtbId: string; subnetId: string }) =>
|
||||
associateEc2RouteTable(rtbId, subnetId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.routeTablesAll });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.subnetsAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDisassociateRouteTableMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (associationId: string) => disassociateEc2RouteTable(associationId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.routeTablesAll });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.subnetsAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Elastic IPs ──────────────────────────────────────────────────────────────
|
||||
|
||||
export function useAllocateElasticIpMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (name?: string) => allocateEc2ElasticIp(name),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.elasticIps });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useReleaseElasticIpMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (allocationId: string) => releaseEc2ElasticIp(allocationId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.elasticIps });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAssociateElasticIpMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ allocationId, instanceId }: { allocationId: string; instanceId: string }) =>
|
||||
associateEc2ElasticIp(allocationId, instanceId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.elasticIps });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.instances });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDisassociateElasticIpMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ allocationId, associationId }: { allocationId: string; associationId: string }) =>
|
||||
disassociateEc2ElasticIp(allocationId, associationId),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.elasticIps });
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.instances });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useModifySubnetAttributeMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ subnetId, mapPublicIpOnLaunch }: { subnetId: string; mapPublicIpOnLaunch: boolean }) =>
|
||||
modifyEc2SubnetAttribute(subnetId, mapPublicIpOnLaunch),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.subnetsAll });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useModifyVpcAttributeMutation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ vpcId, attribute, value }: { vpcId: string; attribute: "enableDnsHostnames" | "enableDnsSupport"; value: boolean }) =>
|
||||
modifyEc2VpcAttribute(vpcId, attribute, value),
|
||||
onSuccess: (_data, { vpcId }) => {
|
||||
void qc.invalidateQueries({ queryKey: ec2QueryKeys.vpcAttributes(vpcId) });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ec2Client } from "./ec2.api";
|
||||
|
||||
export const ec2QueryKeys = {
|
||||
instances: ["ec2", "instances"] as const,
|
||||
instance: (instanceId: string | null) => ["ec2", "instance", instanceId] as const,
|
||||
amis: ["ec2", "amis"] as const,
|
||||
keyPairs: ["ec2", "key-pairs"] as const,
|
||||
// Parameterized list queries — call with vpcId for scoped, omit for all
|
||||
securityGroups: (vpcId?: string) => ["ec2", "security-groups", vpcId ?? "all"] as const,
|
||||
subnets: (vpcId?: string) => ["ec2", "subnets", vpcId ?? "all"] as const,
|
||||
natGateways: (vpcId?: string) => ["ec2", "nat-gateways", vpcId ?? "all"] as const,
|
||||
routeTables: (vpcId?: string) => ["ec2", "route-tables", vpcId ?? "all"] as const,
|
||||
// Root keys — use in mutations to invalidate all variants of a parameterized query
|
||||
securityGroupsAll: ["ec2", "security-groups"] as const,
|
||||
subnetsAll: ["ec2", "subnets"] as const,
|
||||
natGatewaysAll: ["ec2", "nat-gateways"] as const,
|
||||
routeTablesAll: ["ec2", "route-tables"] as const,
|
||||
// Static list keys
|
||||
vpcs: ["ec2", "vpcs"] as const,
|
||||
consoleOutput: (instanceId: string | null) => ["ec2", "console", instanceId] as const,
|
||||
internetGateways: ["ec2", "internet-gateways"] as const,
|
||||
elasticIps: ["ec2", "elastic-ips"] as const,
|
||||
availabilityZones: ["ec2", "availability-zones"] as const,
|
||||
instanceTypes: ["ec2", "instance-types"] as const,
|
||||
vpcAttributes: (vpcId: string) => ["ec2", "vpc-attributes", vpcId] as const,
|
||||
};
|
||||
|
||||
export function useEc2InstancesQuery() {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.instances,
|
||||
queryFn: ({ signal }) => ec2Client.listInstances(signal),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2InstanceQuery(instanceId: string | null) {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.instance(instanceId),
|
||||
queryFn: ({ signal }) => ec2Client.describeInstance(instanceId!, signal),
|
||||
enabled: Boolean(instanceId),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2AmisQuery() {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.amis,
|
||||
queryFn: ({ signal }) => ec2Client.listAmis(signal),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2KeyPairsQuery(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.keyPairs,
|
||||
queryFn: ({ signal }) => ec2Client.listKeyPairs(signal),
|
||||
enabled,
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2SecurityGroupsQuery(vpcId?: string, enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.securityGroups(vpcId),
|
||||
queryFn: ({ signal }) => ec2Client.listSecurityGroups(vpcId, signal),
|
||||
enabled,
|
||||
staleTime: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2VpcsQuery(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.vpcs,
|
||||
queryFn: ({ signal }) => ec2Client.listVpcs(signal),
|
||||
enabled,
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2SubnetsQuery(vpcId?: string, enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.subnets(vpcId),
|
||||
queryFn: ({ signal }) => ec2Client.listSubnets(vpcId, signal),
|
||||
enabled,
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2ConsoleOutputQuery(instanceId: string | null) {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.consoleOutput(instanceId),
|
||||
queryFn: ({ signal }) => ec2Client.getConsoleOutput(instanceId!, signal),
|
||||
enabled: Boolean(instanceId),
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2InternetGatewaysQuery(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.internetGateways,
|
||||
queryFn: ({ signal }) => ec2Client.listInternetGateways(signal),
|
||||
enabled,
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2NatGatewaysQuery(vpcId?: string, enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.natGateways(vpcId),
|
||||
queryFn: ({ signal }) => ec2Client.listNatGateways(vpcId, signal),
|
||||
enabled,
|
||||
staleTime: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2RouteTablesQuery(vpcId?: string, enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.routeTables(vpcId),
|
||||
queryFn: ({ signal }) => ec2Client.listRouteTables(vpcId, signal),
|
||||
enabled,
|
||||
staleTime: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2ElasticIpsQuery(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.elasticIps,
|
||||
queryFn: ({ signal }) => ec2Client.listElasticIps(signal),
|
||||
enabled,
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2AvailabilityZonesQuery() {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.availabilityZones,
|
||||
queryFn: ({ signal }) => ec2Client.listAvailabilityZones(signal),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2InstanceTypesQuery() {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.instanceTypes,
|
||||
queryFn: ({ signal }) => ec2Client.listInstanceTypes(signal),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEc2VpcAttributesQuery(vpcId: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: ec2QueryKeys.vpcAttributes(vpcId ?? ""),
|
||||
queryFn: ({ signal }) => ec2Client.getVpcAttributes(vpcId!, signal),
|
||||
enabled: Boolean(vpcId),
|
||||
staleTime: 30_000,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
import { apiClient, apiEndpointKeys } from "@/api/api";
|
||||
import type { ResourceSummary } from "@/api/types";
|
||||
|
||||
export type EksVpcConfig = {
|
||||
subnetIds: string[];
|
||||
securityGroupIds: string[];
|
||||
clusterSecurityGroupId?: string;
|
||||
vpcId?: string;
|
||||
endpointPublicAccess?: boolean;
|
||||
endpointPrivateAccess?: boolean;
|
||||
publicAccessCidrs: string[];
|
||||
};
|
||||
|
||||
export type EksCluster = {
|
||||
name: string;
|
||||
arn?: string;
|
||||
createdAt?: string;
|
||||
version?: string;
|
||||
endpoint?: string;
|
||||
roleArn?: string;
|
||||
status?: string;
|
||||
platformVersion?: string;
|
||||
certificateAuthority?: {
|
||||
data?: string;
|
||||
};
|
||||
resourcesVpcConfig?: EksVpcConfig;
|
||||
tags: Record<string, string>;
|
||||
nodegroupCount?: number;
|
||||
fargateProfileCount?: number;
|
||||
};
|
||||
|
||||
export type EksNodegroup = {
|
||||
name: string;
|
||||
arn?: string;
|
||||
clusterName: string;
|
||||
version?: string;
|
||||
releaseVersion?: string;
|
||||
createdAt?: string;
|
||||
modifiedAt?: string;
|
||||
status?: string;
|
||||
capacityType?: string;
|
||||
instanceTypes: string[];
|
||||
subnets: string[];
|
||||
nodeRole?: string;
|
||||
scalingConfig?: {
|
||||
minSize?: number;
|
||||
maxSize?: number;
|
||||
desiredSize?: number;
|
||||
};
|
||||
labels: Record<string, string>;
|
||||
tags: Record<string, string>;
|
||||
};
|
||||
|
||||
export type EksFargateProfileSelector = {
|
||||
namespace?: string;
|
||||
labels: Record<string, string>;
|
||||
};
|
||||
|
||||
export type EksFargateProfile = {
|
||||
name: string;
|
||||
arn?: string;
|
||||
clusterName: string;
|
||||
createdAt?: string;
|
||||
status?: string;
|
||||
podExecutionRoleArn?: string;
|
||||
subnets: string[];
|
||||
selectors: EksFargateProfileSelector[];
|
||||
tags: Record<string, string>;
|
||||
};
|
||||
|
||||
export type CreateEksNodegroupInput = {
|
||||
name: string;
|
||||
nodeRole: string;
|
||||
subnets: string[];
|
||||
instanceTypes?: string[];
|
||||
scalingConfig?: {
|
||||
minSize?: number;
|
||||
maxSize?: number;
|
||||
desiredSize?: number;
|
||||
};
|
||||
labels?: Record<string, string>;
|
||||
tags?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type CreateEksFargateProfileInput = {
|
||||
name: string;
|
||||
podExecutionRoleArn: string;
|
||||
subnets?: string[];
|
||||
selectors: {
|
||||
namespace?: string;
|
||||
labels?: Record<string, string>;
|
||||
}[];
|
||||
tags?: Record<string, string>;
|
||||
};
|
||||
|
||||
export async function listEksClusters(
|
||||
signal?: AbortSignal,
|
||||
): Promise<EksCluster[]> {
|
||||
const res = await apiClient.call<EksCluster[]>(
|
||||
apiEndpointKeys.aws.eks.clusters.list,
|
||||
{ signal },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function describeEksCluster(
|
||||
name: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<EksCluster> {
|
||||
const res = await apiClient.call<EksCluster>(
|
||||
apiEndpointKeys.aws.eks.clusters.describe,
|
||||
{ signal },
|
||||
{ name },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listEksNodegroups(
|
||||
clusterName: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<EksNodegroup[]> {
|
||||
const res = await apiClient.call<EksNodegroup[]>(
|
||||
apiEndpointKeys.aws.eks.nodegroups.list,
|
||||
{ signal },
|
||||
{ name: clusterName },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function describeEksNodegroup(
|
||||
clusterName: string,
|
||||
nodegroupName: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<EksNodegroup> {
|
||||
const res = await apiClient.call<EksNodegroup>(
|
||||
apiEndpointKeys.aws.eks.nodegroups.describe,
|
||||
{ signal },
|
||||
{ name: clusterName, nodegroup: nodegroupName },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createEksNodegroup(
|
||||
clusterName: string,
|
||||
input: CreateEksNodegroupInput,
|
||||
): Promise<EksNodegroup> {
|
||||
const res = await apiClient.call<EksNodegroup, CreateEksNodegroupInput>(
|
||||
apiEndpointKeys.aws.eks.nodegroups.create,
|
||||
{ body: input },
|
||||
{ name: clusterName },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteEksNodegroup(
|
||||
clusterName: string,
|
||||
nodegroupName: string,
|
||||
): Promise<EksNodegroup> {
|
||||
const res = await apiClient.call<EksNodegroup>(
|
||||
apiEndpointKeys.aws.eks.nodegroups.delete,
|
||||
{},
|
||||
{ name: clusterName, nodegroup: nodegroupName },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listEksFargateProfiles(
|
||||
clusterName: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<EksFargateProfile[]> {
|
||||
const res = await apiClient.call<EksFargateProfile[]>(
|
||||
apiEndpointKeys.aws.eks.fargateProfiles.list,
|
||||
{ signal },
|
||||
{ name: clusterName },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function describeEksFargateProfile(
|
||||
clusterName: string,
|
||||
profileName: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<EksFargateProfile> {
|
||||
const res = await apiClient.call<EksFargateProfile>(
|
||||
apiEndpointKeys.aws.eks.fargateProfiles.describe,
|
||||
{ signal },
|
||||
{ name: clusterName, profile: profileName },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createEksFargateProfile(
|
||||
clusterName: string,
|
||||
input: CreateEksFargateProfileInput,
|
||||
): Promise<EksFargateProfile> {
|
||||
const res = await apiClient.call<
|
||||
EksFargateProfile,
|
||||
CreateEksFargateProfileInput
|
||||
>(
|
||||
apiEndpointKeys.aws.eks.fargateProfiles.create,
|
||||
{ body: input },
|
||||
{ name: clusterName },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteEksFargateProfile(
|
||||
clusterName: string,
|
||||
profileName: string,
|
||||
): Promise<EksFargateProfile> {
|
||||
const res = await apiClient.call<EksFargateProfile>(
|
||||
apiEndpointKeys.aws.eks.fargateProfiles.delete,
|
||||
{},
|
||||
{ name: clusterName, profile: profileName },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listEksResources(
|
||||
signal?: AbortSignal,
|
||||
): Promise<ResourceSummary[]> {
|
||||
const clusters = await listEksClusters(signal);
|
||||
|
||||
return clusters.map((cluster) => ({
|
||||
id: cluster.arn ?? cluster.name,
|
||||
name: cluster.name,
|
||||
status: cluster.status,
|
||||
metadata: {
|
||||
version: cluster.version,
|
||||
platformVersion: cluster.platformVersion,
|
||||
nodegroups: cluster.nodegroupCount ?? 0,
|
||||
fargateProfiles: cluster.fargateProfileCount ?? 0,
|
||||
vpcId: cluster.resourcesVpcConfig?.vpcId,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
export const eksClient = {
|
||||
listClusters: listEksClusters,
|
||||
describeCluster: describeEksCluster,
|
||||
listNodegroups: listEksNodegroups,
|
||||
describeNodegroup: describeEksNodegroup,
|
||||
createNodegroup: createEksNodegroup,
|
||||
deleteNodegroup: deleteEksNodegroup,
|
||||
listFargateProfiles: listEksFargateProfiles,
|
||||
describeFargateProfile: describeEksFargateProfile,
|
||||
createFargateProfile: createEksFargateProfile,
|
||||
deleteFargateProfile: deleteEksFargateProfile,
|
||||
listResources: listEksResources,
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
eksClient,
|
||||
type CreateEksFargateProfileInput,
|
||||
type CreateEksNodegroupInput,
|
||||
} from "./eks.api";
|
||||
import { eksQueryKeys } from "./eks.queries";
|
||||
|
||||
export type CreateEksNodegroupMutationInput = {
|
||||
clusterName: string;
|
||||
nodegroup: CreateEksNodegroupInput;
|
||||
};
|
||||
|
||||
export type DeleteEksNodegroupMutationInput = {
|
||||
clusterName: string;
|
||||
nodegroupName: string;
|
||||
};
|
||||
|
||||
export type CreateEksFargateProfileMutationInput = {
|
||||
clusterName: string;
|
||||
profile: CreateEksFargateProfileInput;
|
||||
};
|
||||
|
||||
export type DeleteEksFargateProfileMutationInput = {
|
||||
clusterName: string;
|
||||
profileName: string;
|
||||
};
|
||||
|
||||
export function useCreateEksNodegroupMutation() {
|
||||
const qc = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ clusterName, nodegroup }: CreateEksNodegroupMutationInput) =>
|
||||
eksClient.createNodegroup(clusterName, nodegroup),
|
||||
onSuccess: (_, input) => {
|
||||
void qc.invalidateQueries({
|
||||
queryKey: eksQueryKeys.nodegroups(input.clusterName),
|
||||
});
|
||||
void qc.invalidateQueries({ queryKey: eksQueryKeys.clusters });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteEksNodegroupMutation() {
|
||||
const qc = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ clusterName, nodegroupName }: DeleteEksNodegroupMutationInput) =>
|
||||
eksClient.deleteNodegroup(clusterName, nodegroupName),
|
||||
onSuccess: (_, input) => {
|
||||
void qc.invalidateQueries({
|
||||
queryKey: eksQueryKeys.nodegroups(input.clusterName),
|
||||
});
|
||||
void qc.invalidateQueries({ queryKey: eksQueryKeys.clusters });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateEksFargateProfileMutation() {
|
||||
const qc = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ clusterName, profile }: CreateEksFargateProfileMutationInput) =>
|
||||
eksClient.createFargateProfile(clusterName, profile),
|
||||
onSuccess: (_, input) => {
|
||||
void qc.invalidateQueries({
|
||||
queryKey: eksQueryKeys.fargateProfiles(input.clusterName),
|
||||
});
|
||||
void qc.invalidateQueries({ queryKey: eksQueryKeys.clusters });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteEksFargateProfileMutation() {
|
||||
const qc = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ clusterName, profileName }: DeleteEksFargateProfileMutationInput) =>
|
||||
eksClient.deleteFargateProfile(clusterName, profileName),
|
||||
onSuccess: (_, input) => {
|
||||
void qc.invalidateQueries({
|
||||
queryKey: eksQueryKeys.fargateProfiles(input.clusterName),
|
||||
});
|
||||
void qc.invalidateQueries({ queryKey: eksQueryKeys.clusters });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { eksClient } from "./eks.api";
|
||||
|
||||
export const eksQueryKeys = {
|
||||
clusters: ["eks", "clusters"] as const,
|
||||
cluster: (name: string | null) => ["eks", "cluster", name] as const,
|
||||
nodegroups: (clusterName: string | null) =>
|
||||
["eks", "nodegroups", clusterName] as const,
|
||||
nodegroup: (clusterName: string | null, nodegroupName: string | null) =>
|
||||
["eks", "nodegroup", clusterName, nodegroupName] as const,
|
||||
fargateProfiles: (clusterName: string | null) =>
|
||||
["eks", "fargate-profiles", clusterName] as const,
|
||||
fargateProfile: (clusterName: string | null, profileName: string | null) =>
|
||||
["eks", "fargate-profile", clusterName, profileName] as const,
|
||||
};
|
||||
|
||||
export function useEksClustersQuery() {
|
||||
return useQuery({
|
||||
queryKey: eksQueryKeys.clusters,
|
||||
queryFn: ({ signal }) => eksClient.listClusters(signal),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEksClusterQuery(name: string | null) {
|
||||
return useQuery({
|
||||
queryKey: eksQueryKeys.cluster(name),
|
||||
queryFn: ({ signal }) => eksClient.describeCluster(name!, signal),
|
||||
enabled: Boolean(name),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEksNodegroupsQuery(clusterName: string | null) {
|
||||
return useQuery({
|
||||
queryKey: eksQueryKeys.nodegroups(clusterName),
|
||||
queryFn: ({ signal }) => eksClient.listNodegroups(clusterName!, signal),
|
||||
enabled: Boolean(clusterName),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEksNodegroupQuery(
|
||||
clusterName: string | null,
|
||||
nodegroupName: string | null,
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: eksQueryKeys.nodegroup(clusterName, nodegroupName),
|
||||
queryFn: ({ signal }) =>
|
||||
eksClient.describeNodegroup(clusterName!, nodegroupName!, signal),
|
||||
enabled: Boolean(clusterName && nodegroupName),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEksFargateProfilesQuery(clusterName: string | null) {
|
||||
return useQuery({
|
||||
queryKey: eksQueryKeys.fargateProfiles(clusterName),
|
||||
queryFn: ({ signal }) => eksClient.listFargateProfiles(clusterName!, signal),
|
||||
enabled: Boolean(clusterName),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useEksFargateProfileQuery(
|
||||
clusterName: string | null,
|
||||
profileName: string | null,
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: eksQueryKeys.fargateProfile(clusterName, profileName),
|
||||
queryFn: ({ signal }) =>
|
||||
eksClient.describeFargateProfile(clusterName!, profileName!, signal),
|
||||
enabled: Boolean(clusterName && profileName),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import { apiClient, apiEndpointKeys } from "@/api/api";
|
||||
import type { ResourceSummary } from "@/api/types";
|
||||
|
||||
export interface SecretTag {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface SecretSummary {
|
||||
name: string;
|
||||
arn?: string;
|
||||
description?: string;
|
||||
rotationEnabled: boolean;
|
||||
kmsKeyId?: string;
|
||||
lastChangedDate?: string;
|
||||
lastAccessedDate?: string;
|
||||
createdDate?: string;
|
||||
tags: SecretTag[];
|
||||
}
|
||||
|
||||
export interface SecretDetail extends SecretSummary {
|
||||
deletedDate?: string;
|
||||
versionIds: string[];
|
||||
}
|
||||
|
||||
export interface SecretValue {
|
||||
name: string;
|
||||
arn?: string;
|
||||
versionId?: string;
|
||||
secretString?: string;
|
||||
secretBinary?: string;
|
||||
createdDate?: string;
|
||||
}
|
||||
|
||||
export async function listSecrets(
|
||||
signal?: AbortSignal,
|
||||
): Promise<SecretSummary[]> {
|
||||
const res = await apiClient.call<SecretSummary[]>(
|
||||
apiEndpointKeys.aws.secretsmanager.secrets.list,
|
||||
{ signal },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function describeSecret(
|
||||
id: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SecretDetail> {
|
||||
const res = await apiClient.call<SecretDetail>(
|
||||
apiEndpointKeys.aws.secretsmanager.secrets.describe,
|
||||
{ signal, params: { id } },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function getSecretValue(
|
||||
id: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SecretValue> {
|
||||
const res = await apiClient.call<SecretValue>(
|
||||
apiEndpointKeys.aws.secretsmanager.secrets.value.get,
|
||||
{ signal, params: { id } },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createSecret(
|
||||
name: string,
|
||||
secretString: string,
|
||||
description?: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{ name: string; arn?: string; versionId?: string }> {
|
||||
const res = await apiClient.call<
|
||||
{ name: string; arn?: string; versionId?: string },
|
||||
{ name: string; secretString: string; description?: string }
|
||||
>(
|
||||
apiEndpointKeys.aws.secretsmanager.secrets.create,
|
||||
{ signal, body: { name, secretString, description } },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function putSecretValue(
|
||||
id: string,
|
||||
secretString: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{ arn?: string; versionId?: string }> {
|
||||
const res = await apiClient.call<
|
||||
{ arn?: string; versionId?: string },
|
||||
{ id: string; secretString: string }
|
||||
>(
|
||||
apiEndpointKeys.aws.secretsmanager.secrets.value.put,
|
||||
{ signal, body: { id, secretString } },
|
||||
);
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteSecret(
|
||||
id: string,
|
||||
force = false,
|
||||
signal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void>(
|
||||
apiEndpointKeys.aws.secretsmanager.secrets.delete,
|
||||
{ signal, params: { id, force: force ? "true" : undefined } },
|
||||
);
|
||||
}
|
||||
|
||||
export async function listSecretResources(
|
||||
signal?: AbortSignal,
|
||||
): Promise<ResourceSummary[]> {
|
||||
const secrets = await listSecrets(signal);
|
||||
|
||||
return secrets.map((s) => ({
|
||||
id: s.arn ?? s.name,
|
||||
name: s.name,
|
||||
status: s.rotationEnabled ? "rotation enabled" : "available",
|
||||
description: s.description,
|
||||
metadata: {
|
||||
lastChangedDate: s.lastChangedDate,
|
||||
lastAccessedDate: s.lastAccessedDate,
|
||||
createdDate: s.createdDate,
|
||||
tagCount: s.tags.length,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
export const secretsManagerClient = {
|
||||
listSecrets,
|
||||
describeSecret,
|
||||
getSecretValue,
|
||||
createSecret,
|
||||
putSecretValue,
|
||||
deleteSecret,
|
||||
listResources: listSecretResources,
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
type UseMutationOptions,
|
||||
} from "@tanstack/react-query";
|
||||
import { secretsManagerClient } from "./secretsmanager.api";
|
||||
import { secretsManagerQueryKeys } from "./secretsmanager.queries";
|
||||
|
||||
export type CreateSecretMutationInput = {
|
||||
name: string;
|
||||
secretString: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type PutSecretValueMutationInput = {
|
||||
id: string;
|
||||
secretString: string;
|
||||
};
|
||||
|
||||
export type DeleteSecretMutationInput = {
|
||||
id: string;
|
||||
force?: boolean;
|
||||
};
|
||||
|
||||
export function useCreateSecretMutation(
|
||||
options?: UseMutationOptions<
|
||||
{ name: string; arn?: string; versionId?: string },
|
||||
Error,
|
||||
CreateSecretMutationInput
|
||||
>,
|
||||
) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ name, secretString, description }) =>
|
||||
secretsManagerClient.createSecret(name, secretString, description),
|
||||
...options,
|
||||
onSuccess: (...args) => {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: secretsManagerQueryKeys.list,
|
||||
});
|
||||
options?.onSuccess?.(...args);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function usePutSecretValueMutation(
|
||||
options?: UseMutationOptions<
|
||||
{ arn?: string; versionId?: string },
|
||||
Error,
|
||||
PutSecretValueMutationInput
|
||||
>,
|
||||
) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ id, secretString }) =>
|
||||
secretsManagerClient.putSecretValue(id, secretString),
|
||||
...options,
|
||||
onSuccess: (...args) => {
|
||||
const [, { id }] = args;
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: secretsManagerQueryKeys.value(id),
|
||||
});
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: secretsManagerQueryKeys.detail(id),
|
||||
});
|
||||
options?.onSuccess?.(...args);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteSecretMutation(
|
||||
options?: UseMutationOptions<void, Error, DeleteSecretMutationInput>,
|
||||
) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ id, force }) =>
|
||||
secretsManagerClient.deleteSecret(id, force ?? false),
|
||||
...options,
|
||||
onSuccess: (...args) => {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: secretsManagerQueryKeys.list,
|
||||
});
|
||||
options?.onSuccess?.(...args);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { secretsManagerClient } from "./secretsmanager.api";
|
||||
|
||||
export const secretsManagerQueryKeys = {
|
||||
list: ["secretsmanager", "secrets"] as const,
|
||||
detail: (id: string | null) => ["secretsmanager", "detail", id] as const,
|
||||
value: (id: string | null) => ["secretsmanager", "value", id] as const,
|
||||
};
|
||||
|
||||
export function useSecretsQuery(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: secretsManagerQueryKeys.list,
|
||||
queryFn: ({ signal }) => secretsManagerClient.listSecrets(signal),
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSecretDetailQuery(id: string | null) {
|
||||
return useQuery({
|
||||
queryKey: secretsManagerQueryKeys.detail(id),
|
||||
queryFn: ({ signal }) => secretsManagerClient.describeSecret(id!, signal),
|
||||
enabled: Boolean(id),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSecretValueQuery(id: string | null, enabled = false) {
|
||||
return useQuery({
|
||||
queryKey: secretsManagerQueryKeys.value(id),
|
||||
queryFn: ({ signal }) => secretsManagerClient.getSecretValue(id!, signal),
|
||||
enabled: Boolean(id) && enabled,
|
||||
// Keep the plaintext out of the long-lived cache: always refetch on reveal
|
||||
// and drop it the moment the query loses its observer so a hidden value
|
||||
// cannot be read back later.
|
||||
gcTime: 0,
|
||||
staleTime: 0,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,983 @@
|
||||
import { API_BASE_URL, apiClient, apiEndpointKeys } from "./api";
|
||||
import type {
|
||||
CloudDescriptor,
|
||||
CloudProvider,
|
||||
CloudServiceDescriptor,
|
||||
CloudServiceStatus,
|
||||
CloudServiceType,
|
||||
CloudStatus,
|
||||
} from "@/types/cloud";
|
||||
import type {
|
||||
AppConfigConfigurationProfile,
|
||||
AppConfigDeployment,
|
||||
AppConfigDeploymentStrategy,
|
||||
AppConfigEnvironment,
|
||||
AppConfigHostedConfigurationVersion,
|
||||
ChildCollection,
|
||||
ChildItem,
|
||||
CloudResource,
|
||||
CollectionPage,
|
||||
CosmosContainer,
|
||||
CosmosItem,
|
||||
CosmosQueryResult,
|
||||
CreateDatabaseSnapshotInput,
|
||||
CreateKubernetesFargateProfileInput,
|
||||
CreateKubernetesNodegroupInput,
|
||||
DatabaseSnapshot,
|
||||
KubernetesFargateProfile,
|
||||
KubernetesNodegroup,
|
||||
LogsInsightsQueryInput,
|
||||
LogsInsightsQueryResult,
|
||||
NoSqlItem,
|
||||
SqlCredentials,
|
||||
SqlDatabase,
|
||||
SqlEngine,
|
||||
SqlQueryResult,
|
||||
SqlTable,
|
||||
StorageObjectList,
|
||||
} from "@/types/resource";
|
||||
import type { ServiceSchema } from "@/types/schema";
|
||||
import { getAccountId } from "@/lib/accountStore";
|
||||
|
||||
type CloudPathParams = Record<string, string>;
|
||||
|
||||
const DATABASE_MUTATION_TIMEOUT_MS = 5 * 60_000;
|
||||
const SQL_DATA_TIMEOUT_MS = 45_000;
|
||||
/** Floci completes Insights queries instantly by default, but bounded server-side polling can take longer. */
|
||||
const LOGS_QUERY_TIMEOUT_MS = 20_000;
|
||||
/** Cold starts pull a container image; measured at ~60s on a first invoke. */
|
||||
const INVOKE_TIMEOUT_MS = 120_000;
|
||||
|
||||
export async function listClouds(
|
||||
signal?: AbortSignal,
|
||||
): Promise<CloudDescriptor[]> {
|
||||
const res = await apiClient.call<CloudDescriptor[]>(
|
||||
apiEndpointKeys.clouds.list,
|
||||
{ signal },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listCloudServices(
|
||||
cloud: CloudProvider,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CloudServiceDescriptor[]> {
|
||||
const res = await apiClient.call<CloudServiceDescriptor[]>(
|
||||
apiEndpointKeys.clouds.services,
|
||||
requestOptions(cloud, "cloud-proxy", { signal }),
|
||||
{ cloud },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function getCloudStatus(
|
||||
cloud: CloudProvider,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CloudStatus> {
|
||||
const res = await apiClient.call<CloudStatus>(
|
||||
apiEndpointKeys.clouds.status,
|
||||
requestOptions(cloud, "cloud-proxy", { signal }),
|
||||
{ cloud },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function getCloudServiceStatus(
|
||||
cloud: CloudProvider,
|
||||
service: CloudServiceType,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CloudServiceStatus> {
|
||||
const res = await apiClient.call<CloudServiceStatus>(
|
||||
apiEndpointKeys.clouds.serviceStatus,
|
||||
requestOptions(cloud, service, { signal }),
|
||||
{ cloud, service },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function getServiceSchema(
|
||||
cloud: CloudProvider,
|
||||
service: CloudServiceType,
|
||||
signal?: AbortSignal,
|
||||
): Promise<ServiceSchema> {
|
||||
const res = await apiClient.call<ServiceSchema>(
|
||||
apiEndpointKeys.clouds.schema,
|
||||
requestOptions(cloud, service, { signal }),
|
||||
{ cloud, service },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listCloudResources(
|
||||
cloud: CloudProvider,
|
||||
service: CloudServiceType,
|
||||
search?: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CloudResource[]> {
|
||||
const res = await apiClient.call<CloudResource[]>(
|
||||
apiEndpointKeys.clouds.resources.list,
|
||||
requestOptions(cloud, service, { signal, params: { search } }),
|
||||
{ cloud, service },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function getCloudResource(
|
||||
cloud: CloudProvider,
|
||||
service: CloudServiceType,
|
||||
id: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CloudResource> {
|
||||
const res = await apiClient.call<CloudResource>(
|
||||
apiEndpointKeys.clouds.resources.get,
|
||||
requestOptions(cloud, service, { signal }),
|
||||
{ cloud, service, id },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createCloudResource(
|
||||
cloud: CloudProvider,
|
||||
service: CloudServiceType,
|
||||
values: Record<string, unknown>,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CloudResource> {
|
||||
const timeout =
|
||||
(cloud === "azure" || cloud === "aws") && service === "database"
|
||||
? DATABASE_MUTATION_TIMEOUT_MS
|
||||
: undefined;
|
||||
const res = await apiClient.call<CloudResource, Record<string, unknown>>(
|
||||
apiEndpointKeys.clouds.resources.create,
|
||||
requestOptions(cloud, service, { signal, body: values, timeout }),
|
||||
{ cloud, service },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function updateCloudResource(
|
||||
cloud: CloudProvider,
|
||||
service: CloudServiceType,
|
||||
id: string,
|
||||
values: Record<string, unknown>,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CloudResource> {
|
||||
const timeout =
|
||||
(cloud === "azure" || cloud === "aws") && service === "database"
|
||||
? DATABASE_MUTATION_TIMEOUT_MS
|
||||
: undefined;
|
||||
const res = await apiClient.call<CloudResource, Record<string, unknown>>(
|
||||
apiEndpointKeys.clouds.resources.update,
|
||||
requestOptions(cloud, service, { signal, body: values, timeout }),
|
||||
{ cloud, service, id },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteCloudResource(
|
||||
cloud: CloudProvider,
|
||||
service: CloudServiceType,
|
||||
id: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void>(
|
||||
apiEndpointKeys.clouds.resources.delete,
|
||||
requestOptions(cloud, service, { signal }),
|
||||
{ cloud, service, id },
|
||||
);
|
||||
}
|
||||
|
||||
export async function clearEmailInbox(
|
||||
cloud: CloudProvider,
|
||||
signal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void>(
|
||||
apiEndpointKeys.clouds.email.inbox.clear,
|
||||
requestOptions(cloud, "email", { signal }),
|
||||
{ cloud },
|
||||
);
|
||||
}
|
||||
export interface ServerlessInvokeResult {
|
||||
statusCode: number;
|
||||
payload: string;
|
||||
functionError?: string;
|
||||
logResult?: string;
|
||||
executionDuration?: number;
|
||||
}
|
||||
|
||||
export interface CreateApiGatewayLambdaRouteInput {
|
||||
path: string;
|
||||
method: string;
|
||||
stage: string;
|
||||
functionName: string;
|
||||
}
|
||||
|
||||
export interface ApiGatewayLambdaRoute {
|
||||
resourceId: string;
|
||||
deploymentId: string;
|
||||
invokeUrl: string;
|
||||
}
|
||||
|
||||
export async function createApiGatewayLambdaRoute(
|
||||
cloud: CloudProvider,
|
||||
apiId: string,
|
||||
input: CreateApiGatewayLambdaRouteInput,
|
||||
signal?: AbortSignal,
|
||||
): Promise<ApiGatewayLambdaRoute> {
|
||||
const res = await apiClient.call<ApiGatewayLambdaRoute, CreateApiGatewayLambdaRouteInput>(
|
||||
apiEndpointKeys.clouds.resources.createApiGatewayRoute,
|
||||
requestOptions(cloud, "apigateway", { signal, body: input }),
|
||||
{ cloud, id: apiId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export type KmsEncryptionAlgorithm =
|
||||
| "SYMMETRIC_DEFAULT"
|
||||
| "RSAES_OAEP_SHA_1"
|
||||
| "RSAES_OAEP_SHA_256";
|
||||
|
||||
export interface KmsEncryptRequest {
|
||||
plaintextBase64: string;
|
||||
encryptionAlgorithm: KmsEncryptionAlgorithm;
|
||||
encryptionContext?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface KmsEncryptResponse {
|
||||
ciphertextBlobBase64: string;
|
||||
keyId: string;
|
||||
encryptionAlgorithm: KmsEncryptionAlgorithm;
|
||||
}
|
||||
|
||||
export interface KmsDecryptRequest {
|
||||
ciphertextBlobBase64: string;
|
||||
encryptionAlgorithm: KmsEncryptionAlgorithm;
|
||||
encryptionContext?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface KmsDecryptResponse {
|
||||
plaintextBase64: string;
|
||||
keyId: string;
|
||||
encryptionAlgorithm: KmsEncryptionAlgorithm;
|
||||
}
|
||||
|
||||
export async function invokeCloudResource(
|
||||
cloud: CloudProvider,
|
||||
service: CloudServiceType,
|
||||
id: string,
|
||||
payload: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<ServerlessInvokeResult> {
|
||||
const res = await apiClient.call<ServerlessInvokeResult, { payload: string }>(
|
||||
apiEndpointKeys.clouds.resources.invoke,
|
||||
requestOptions(cloud, service, {
|
||||
signal,
|
||||
body: { payload },
|
||||
// A cold invoke can exceed a minute while the runtime pulls and starts the
|
||||
// function container, so the 10s default would abort a request that is
|
||||
// working. The user-visible alternative is a timeout error for a function
|
||||
// that in fact ran.
|
||||
timeout: INVOKE_TIMEOUT_MS,
|
||||
}),
|
||||
{ cloud, service, id },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function encryptKmsResource(
|
||||
cloud: CloudProvider,
|
||||
id: string,
|
||||
body: KmsEncryptRequest,
|
||||
signal?: AbortSignal,
|
||||
): Promise<KmsEncryptResponse> {
|
||||
const res = await apiClient.call<KmsEncryptResponse, KmsEncryptRequest>(
|
||||
apiEndpointKeys.clouds.resources.encrypt,
|
||||
requestOptions(cloud, "kms", { signal, body }),
|
||||
{ cloud, id },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function decryptKmsResource(
|
||||
cloud: CloudProvider,
|
||||
id: string,
|
||||
body: KmsDecryptRequest,
|
||||
signal?: AbortSignal,
|
||||
): Promise<KmsDecryptResponse> {
|
||||
const res = await apiClient.call<KmsDecryptResponse, KmsDecryptRequest>(
|
||||
apiEndpointKeys.clouds.resources.decrypt,
|
||||
requestOptions(cloud, "kms", { signal, body }),
|
||||
{ cloud, id },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listStorageObjects(
|
||||
cloud: CloudProvider,
|
||||
resourceId: string,
|
||||
prefix?: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<StorageObjectList> {
|
||||
const res = await apiClient.call<StorageObjectList>(
|
||||
apiEndpointKeys.clouds.storage.objects.list,
|
||||
requestOptions(cloud, "storage", { signal, params: { prefix } }),
|
||||
storagePathParams(cloud, resourceId),
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function uploadStorageObject(
|
||||
cloud: CloudProvider,
|
||||
resourceId: string,
|
||||
key: string,
|
||||
file: File | Blob,
|
||||
signal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void>(
|
||||
apiEndpointKeys.clouds.storage.objects.upload,
|
||||
requestOptions(cloud, "storage", {
|
||||
signal,
|
||||
rawBody: file,
|
||||
params: { key },
|
||||
headers: { "Content-Type": file.type || "application/octet-stream" },
|
||||
}),
|
||||
storagePathParams(cloud, resourceId),
|
||||
);
|
||||
}
|
||||
|
||||
export function storageObjectDownloadUrl(
|
||||
cloud: CloudProvider,
|
||||
resourceId: string,
|
||||
key: string,
|
||||
): string {
|
||||
const path = `/clouds/${encodeURIComponent(
|
||||
cloud,
|
||||
)}/services/storage/resources/${encodeURIComponent(resourceId)}/object`;
|
||||
// This URL is opened directly by the browser (anchor/img), so it bypasses the
|
||||
// request interceptor — pass the account as a query param the API also reads.
|
||||
return `${API_BASE_URL}${path}?key=${encodeURIComponent(
|
||||
key,
|
||||
)}&account=${encodeURIComponent(getAccountId())}`;
|
||||
}
|
||||
|
||||
export async function deleteStorageObject(
|
||||
cloud: CloudProvider,
|
||||
resourceId: string,
|
||||
key: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void>(
|
||||
apiEndpointKeys.clouds.storage.objects.delete,
|
||||
requestOptions(cloud, "storage", { signal, params: { key } }),
|
||||
storagePathParams(cloud, resourceId),
|
||||
);
|
||||
}
|
||||
|
||||
export async function copyStorageObject(
|
||||
cloud: CloudProvider,
|
||||
srcResourceId: string,
|
||||
srcKey: string,
|
||||
destKey: string,
|
||||
destResourceId?: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void, Record<string, unknown>>(
|
||||
apiEndpointKeys.clouds.storage.objects.copy,
|
||||
requestOptions(cloud, "storage", {
|
||||
signal,
|
||||
body: { srcKey, destKey, ...(destResourceId ? { destResourceId } : {}) },
|
||||
}),
|
||||
storagePathParams(cloud, srcResourceId),
|
||||
);
|
||||
}
|
||||
|
||||
export async function listCosmosContainers(
|
||||
cloud: CloudProvider,
|
||||
databaseId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CosmosContainer[]> {
|
||||
const res = await apiClient.call<CosmosContainer[]>(
|
||||
apiEndpointKeys.clouds.nosql.cosmos.containers.list,
|
||||
requestOptions(cloud, "nosql", { signal }),
|
||||
databasePathParams(cloud, databaseId),
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createCosmosContainer(
|
||||
cloud: CloudProvider,
|
||||
databaseId: string,
|
||||
values: Record<string, unknown>,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CosmosContainer> {
|
||||
const res = await apiClient.call<CosmosContainer, Record<string, unknown>>(
|
||||
apiEndpointKeys.clouds.nosql.cosmos.containers.create,
|
||||
requestOptions(cloud, "nosql", { signal, body: values }),
|
||||
databasePathParams(cloud, databaseId),
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteCosmosContainer(
|
||||
cloud: CloudProvider,
|
||||
databaseId: string,
|
||||
containerId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void>(
|
||||
apiEndpointKeys.clouds.nosql.cosmos.containers.delete,
|
||||
requestOptions(cloud, "nosql", { signal }),
|
||||
{ ...databasePathParams(cloud, databaseId), containerId },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function listCosmosItems(
|
||||
cloud: CloudProvider,
|
||||
databaseId: string,
|
||||
containerId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CosmosItem[]> {
|
||||
const res = await apiClient.call<CosmosItem[]>(
|
||||
apiEndpointKeys.clouds.nosql.cosmos.items.list,
|
||||
requestOptions(cloud, "nosql", { signal }),
|
||||
{ ...databasePathParams(cloud, databaseId), containerId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function upsertCosmosItem(
|
||||
cloud: CloudProvider,
|
||||
databaseId: string,
|
||||
containerId: string,
|
||||
document: Record<string, unknown>,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CosmosItem> {
|
||||
const res = await apiClient.call<CosmosItem, Record<string, unknown>>(
|
||||
apiEndpointKeys.clouds.nosql.cosmos.items.upsert,
|
||||
requestOptions(cloud, "nosql", { signal, body: document }),
|
||||
{ ...databasePathParams(cloud, databaseId), containerId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteCosmosItem(
|
||||
cloud: CloudProvider,
|
||||
databaseId: string,
|
||||
containerId: string,
|
||||
itemId: string,
|
||||
partitionKey?: string | null,
|
||||
signal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void>(
|
||||
apiEndpointKeys.clouds.nosql.cosmos.items.delete,
|
||||
requestOptions(cloud, "nosql", {
|
||||
signal,
|
||||
params: partitionKey ? { partitionKey } : undefined,
|
||||
}),
|
||||
{ ...databasePathParams(cloud, databaseId), containerId, itemId },
|
||||
);
|
||||
}
|
||||
|
||||
export async function queryCosmosItems(
|
||||
cloud: CloudProvider,
|
||||
databaseId: string,
|
||||
containerId: string,
|
||||
query: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CosmosQueryResult> {
|
||||
const res = await apiClient.call<CosmosQueryResult, { query: string }>(
|
||||
apiEndpointKeys.clouds.nosql.cosmos.items.query,
|
||||
requestOptions(cloud, "nosql", { signal, body: { query } }),
|
||||
{ ...databasePathParams(cloud, databaseId), containerId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listChildCollections(
|
||||
cloud: CloudProvider,
|
||||
service: CloudServiceType,
|
||||
resourceId: string,
|
||||
cursor?: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CollectionPage<ChildCollection>> {
|
||||
const res = await apiClient.call<CollectionPage<ChildCollection>>(
|
||||
apiEndpointKeys.clouds.childCollections.list,
|
||||
requestOptions(cloud, service, { signal, params: cursor ? { cursor } : undefined }),
|
||||
{ cloud, service, id: resourceId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listCollectionItems(
|
||||
cloud: CloudProvider,
|
||||
service: CloudServiceType,
|
||||
resourceId: string,
|
||||
collectionId: string,
|
||||
cursor?: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CollectionPage<ChildItem>> {
|
||||
const res = await apiClient.call<CollectionPage<ChildItem>>(
|
||||
apiEndpointKeys.clouds.childCollections.items.list,
|
||||
requestOptions(cloud, service, { signal, params: cursor ? { cursor } : undefined }),
|
||||
{ cloud, service, id: resourceId, cid: collectionId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listSqlDatabases(
|
||||
cloud: CloudProvider,
|
||||
serverId: string,
|
||||
engine: SqlEngine,
|
||||
credentials: SqlCredentials,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SqlDatabase[]> {
|
||||
const res = await apiClient.call<SqlDatabase[], SqlCredentials & { engine: SqlEngine }>(
|
||||
apiEndpointKeys.clouds.database.sql.databases,
|
||||
requestOptions(cloud, "database", {
|
||||
signal,
|
||||
body: { ...credentials, engine },
|
||||
timeout: SQL_DATA_TIMEOUT_MS,
|
||||
}),
|
||||
{ cloud, id: serverId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listSqlTables(
|
||||
cloud: CloudProvider,
|
||||
serverId: string,
|
||||
engine: SqlEngine,
|
||||
database: string,
|
||||
credentials: SqlCredentials,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SqlTable[]> {
|
||||
const res = await apiClient.call<
|
||||
SqlTable[],
|
||||
SqlCredentials & { database: string; engine: SqlEngine }
|
||||
>(
|
||||
apiEndpointKeys.clouds.database.sql.tables,
|
||||
requestOptions(cloud, "database", {
|
||||
signal,
|
||||
body: { ...credentials, database, engine },
|
||||
timeout: SQL_DATA_TIMEOUT_MS,
|
||||
}),
|
||||
{ cloud, id: serverId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function querySql(
|
||||
cloud: CloudProvider,
|
||||
serverId: string,
|
||||
engine: SqlEngine,
|
||||
database: string,
|
||||
credentials: SqlCredentials,
|
||||
query: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SqlQueryResult> {
|
||||
const res = await apiClient.call<
|
||||
SqlQueryResult,
|
||||
SqlCredentials & { database: string; engine: SqlEngine; query: string }
|
||||
>(
|
||||
apiEndpointKeys.clouds.database.sql.query,
|
||||
requestOptions(cloud, "database", {
|
||||
signal,
|
||||
body: { ...credentials, database, engine, query },
|
||||
timeout: SQL_DATA_TIMEOUT_MS,
|
||||
}),
|
||||
{ cloud, id: serverId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function queryLogs(
|
||||
cloud: CloudProvider,
|
||||
logGroupName: string,
|
||||
input: LogsInsightsQueryInput,
|
||||
signal?: AbortSignal,
|
||||
): Promise<LogsInsightsQueryResult> {
|
||||
const res = await apiClient.call<LogsInsightsQueryResult, LogsInsightsQueryInput>(
|
||||
apiEndpointKeys.clouds.logs.query,
|
||||
requestOptions(cloud, "logs", { signal, body: input, timeout: LOGS_QUERY_TIMEOUT_MS }),
|
||||
{ cloud, id: logGroupName },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listDatabaseSnapshots(
|
||||
cloud: CloudProvider,
|
||||
instanceIdentifier?: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<DatabaseSnapshot[]> {
|
||||
const res = await apiClient.call<DatabaseSnapshot[]>(
|
||||
apiEndpointKeys.clouds.database.snapshots.list,
|
||||
requestOptions(cloud, "database", {
|
||||
signal,
|
||||
params: instanceIdentifier ? { instanceIdentifier } : undefined,
|
||||
}),
|
||||
{ cloud },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createDatabaseSnapshot(
|
||||
cloud: CloudProvider,
|
||||
input: CreateDatabaseSnapshotInput,
|
||||
signal?: AbortSignal,
|
||||
): Promise<DatabaseSnapshot> {
|
||||
const res = await apiClient.call<DatabaseSnapshot, CreateDatabaseSnapshotInput>(
|
||||
apiEndpointKeys.clouds.database.snapshots.create,
|
||||
requestOptions(cloud, "database", { signal, body: input }),
|
||||
{ cloud },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listDatabaseOrderableClasses(
|
||||
cloud: CloudProvider,
|
||||
engine?: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<string[]> {
|
||||
const res = await apiClient.call<string[]>(
|
||||
apiEndpointKeys.clouds.database.orderableClasses.list,
|
||||
requestOptions(cloud, "database", {
|
||||
signal,
|
||||
params: engine ? { engine } : undefined,
|
||||
}),
|
||||
{ cloud },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listNoSqlItems(
|
||||
cloud: CloudProvider,
|
||||
resourceId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<NoSqlItem[]> {
|
||||
const res = await apiClient.call<NoSqlItem[]>(
|
||||
apiEndpointKeys.clouds.nosql.items.list,
|
||||
requestOptions(cloud, "nosql", { signal }),
|
||||
{ cloud, id: resourceId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function putNoSqlItem(
|
||||
cloud: CloudProvider,
|
||||
resourceId: string,
|
||||
document: Record<string, unknown>,
|
||||
signal?: AbortSignal,
|
||||
): Promise<NoSqlItem> {
|
||||
const res = await apiClient.call<NoSqlItem, Record<string, unknown>>(
|
||||
apiEndpointKeys.clouds.nosql.items.put,
|
||||
requestOptions(cloud, "nosql", { signal, body: document }),
|
||||
{ cloud, id: resourceId },
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function listKubernetesNodegroups(
|
||||
cloud: CloudProvider,
|
||||
clusterId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<KubernetesNodegroup[]> {
|
||||
const res = await apiClient.call<KubernetesNodegroup[]>(
|
||||
apiEndpointKeys.clouds.k8s.nodegroups.list,
|
||||
requestOptions(cloud, "k8s", {signal}),
|
||||
{cloud, id: clusterId},
|
||||
)
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function createKubernetesNodegroup(
|
||||
cloud: CloudProvider,
|
||||
clusterId: string,
|
||||
input: CreateKubernetesNodegroupInput,
|
||||
): Promise<KubernetesNodegroup> {
|
||||
const res = await apiClient.call<KubernetesNodegroup, CreateKubernetesNodegroupInput>(
|
||||
apiEndpointKeys.clouds.k8s.nodegroups.create,
|
||||
requestOptions(cloud, "k8s", {body: input}),
|
||||
{cloud, id: clusterId},
|
||||
)
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function deleteKubernetesNodegroup(
|
||||
cloud: CloudProvider,
|
||||
clusterId: string,
|
||||
nodegroupId: string,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void>(
|
||||
apiEndpointKeys.clouds.k8s.nodegroups.delete,
|
||||
requestOptions(cloud, "k8s"),
|
||||
{cloud, id: clusterId, nodegroupId},
|
||||
)
|
||||
}
|
||||
|
||||
export async function listKubernetesFargateProfiles(
|
||||
cloud: CloudProvider,
|
||||
clusterId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<KubernetesFargateProfile[]> {
|
||||
const res = await apiClient.call<KubernetesFargateProfile[]>(
|
||||
apiEndpointKeys.clouds.k8s.fargateProfiles.list,
|
||||
requestOptions(cloud, "k8s", {signal}),
|
||||
{cloud, id: clusterId},
|
||||
)
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function createKubernetesFargateProfile(
|
||||
cloud: CloudProvider,
|
||||
clusterId: string,
|
||||
input: CreateKubernetesFargateProfileInput,
|
||||
): Promise<KubernetesFargateProfile> {
|
||||
const res = await apiClient.call<KubernetesFargateProfile, CreateKubernetesFargateProfileInput>(
|
||||
apiEndpointKeys.clouds.k8s.fargateProfiles.create,
|
||||
requestOptions(cloud, "k8s", {body: input}),
|
||||
{cloud, id: clusterId},
|
||||
)
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function deleteKubernetesFargateProfile(
|
||||
cloud: CloudProvider,
|
||||
clusterId: string,
|
||||
profileId: string,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void>(
|
||||
apiEndpointKeys.clouds.k8s.fargateProfiles.delete,
|
||||
requestOptions(cloud, "k8s"),
|
||||
{cloud, id: clusterId, profileId},
|
||||
)
|
||||
}
|
||||
|
||||
export async function listAppConfigEnvironments(
|
||||
cloud: CloudProvider,
|
||||
applicationId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<AppConfigEnvironment[]> {
|
||||
const res = await apiClient.call<AppConfigEnvironment[]>(
|
||||
apiEndpointKeys.clouds.configuration.environments.list,
|
||||
requestOptions(cloud, "configuration", {signal}),
|
||||
{cloud, id: applicationId},
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createAppConfigEnvironment(
|
||||
cloud: CloudProvider,
|
||||
applicationId: string,
|
||||
values: Record<string, unknown>,
|
||||
): Promise<AppConfigEnvironment> {
|
||||
const res = await apiClient.call<AppConfigEnvironment, Record<string, unknown>>(
|
||||
apiEndpointKeys.clouds.configuration.environments.create,
|
||||
requestOptions(cloud, "configuration", {body: values}),
|
||||
{cloud, id: applicationId},
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteAppConfigEnvironment(
|
||||
cloud: CloudProvider,
|
||||
applicationId: string,
|
||||
environmentId: string,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void>(
|
||||
apiEndpointKeys.clouds.configuration.environments.delete,
|
||||
requestOptions(cloud, "configuration"),
|
||||
{cloud, id: applicationId, environmentId},
|
||||
);
|
||||
}
|
||||
|
||||
export async function listAppConfigConfigurationProfiles(
|
||||
cloud: CloudProvider,
|
||||
applicationId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<AppConfigConfigurationProfile[]> {
|
||||
const res = await apiClient.call<AppConfigConfigurationProfile[]>(
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.list,
|
||||
requestOptions(cloud, "configuration", {signal}),
|
||||
{cloud, id: applicationId},
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createAppConfigConfigurationProfile(
|
||||
cloud: CloudProvider,
|
||||
applicationId: string,
|
||||
values: Record<string, unknown>,
|
||||
): Promise<AppConfigConfigurationProfile> {
|
||||
const res = await apiClient.call<AppConfigConfigurationProfile, Record<string, unknown>>(
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.create,
|
||||
requestOptions(cloud, "configuration", {body: values}),
|
||||
{cloud, id: applicationId},
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteAppConfigConfigurationProfile(
|
||||
cloud: CloudProvider,
|
||||
applicationId: string,
|
||||
profileId: string,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void>(
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.delete,
|
||||
requestOptions(cloud, "configuration"),
|
||||
{cloud, id: applicationId, profileId},
|
||||
);
|
||||
}
|
||||
|
||||
export async function listAppConfigHostedConfigurationVersions(
|
||||
cloud: CloudProvider,
|
||||
applicationId: string,
|
||||
profileId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<AppConfigHostedConfigurationVersion[]> {
|
||||
const res = await apiClient.call<AppConfigHostedConfigurationVersion[]>(
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.hostedVersions.list,
|
||||
requestOptions(cloud, "configuration", {signal}),
|
||||
{cloud, id: applicationId, profileId},
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function getAppConfigHostedConfigurationVersion(
|
||||
cloud: CloudProvider,
|
||||
applicationId: string,
|
||||
profileId: string,
|
||||
versionNumber: number,
|
||||
signal?: AbortSignal,
|
||||
): Promise<AppConfigHostedConfigurationVersion> {
|
||||
const res = await apiClient.call<AppConfigHostedConfigurationVersion>(
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.hostedVersions.get,
|
||||
requestOptions(cloud, "configuration", {signal}),
|
||||
{cloud, id: applicationId, profileId, versionNumber: String(versionNumber)},
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createAppConfigHostedConfigurationVersion(
|
||||
cloud: CloudProvider,
|
||||
applicationId: string,
|
||||
profileId: string,
|
||||
values: Record<string, unknown>,
|
||||
): Promise<AppConfigHostedConfigurationVersion> {
|
||||
const res = await apiClient.call<AppConfigHostedConfigurationVersion, Record<string, unknown>>(
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.hostedVersions.create,
|
||||
requestOptions(cloud, "configuration", {body: values}),
|
||||
{cloud, id: applicationId, profileId},
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteAppConfigHostedConfigurationVersion(
|
||||
cloud: CloudProvider,
|
||||
applicationId: string,
|
||||
profileId: string,
|
||||
versionNumber: number,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void>(
|
||||
apiEndpointKeys.clouds.configuration.configurationProfiles.hostedVersions.delete,
|
||||
requestOptions(cloud, "configuration"),
|
||||
{cloud, id: applicationId, profileId, versionNumber: String(versionNumber)},
|
||||
);
|
||||
}
|
||||
|
||||
export async function listAppConfigDeploymentStrategies(
|
||||
cloud: CloudProvider,
|
||||
signal?: AbortSignal,
|
||||
): Promise<AppConfigDeploymentStrategy[]> {
|
||||
const res = await apiClient.call<AppConfigDeploymentStrategy[]>(
|
||||
apiEndpointKeys.clouds.configuration.deploymentStrategies.list,
|
||||
requestOptions(cloud, "configuration", {signal}),
|
||||
{cloud},
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function createAppConfigDeploymentStrategy(
|
||||
cloud: CloudProvider,
|
||||
values: Record<string, unknown>,
|
||||
): Promise<AppConfigDeploymentStrategy> {
|
||||
const res = await apiClient.call<AppConfigDeploymentStrategy, Record<string, unknown>>(
|
||||
apiEndpointKeys.clouds.configuration.deploymentStrategies.create,
|
||||
requestOptions(cloud, "configuration", {body: values}),
|
||||
{cloud},
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteAppConfigDeploymentStrategy(
|
||||
cloud: CloudProvider,
|
||||
strategyId: string,
|
||||
): Promise<void> {
|
||||
await apiClient.call<void>(
|
||||
apiEndpointKeys.clouds.configuration.deploymentStrategies.delete,
|
||||
requestOptions(cloud, "configuration"),
|
||||
{cloud, strategyId},
|
||||
);
|
||||
}
|
||||
|
||||
export async function startAppConfigDeployment(
|
||||
cloud: CloudProvider,
|
||||
applicationId: string,
|
||||
environmentId: string,
|
||||
values: Record<string, unknown>,
|
||||
): Promise<AppConfigDeployment> {
|
||||
const res = await apiClient.call<AppConfigDeployment, Record<string, unknown>>(
|
||||
apiEndpointKeys.clouds.configuration.environments.deployments.start,
|
||||
requestOptions(cloud, "configuration", {body: values}),
|
||||
{cloud, id: applicationId, environmentId},
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function getAppConfigDeployment(
|
||||
cloud: CloudProvider,
|
||||
applicationId: string,
|
||||
environmentId: string,
|
||||
deploymentNumber: number,
|
||||
signal?: AbortSignal,
|
||||
): Promise<AppConfigDeployment> {
|
||||
const res = await apiClient.call<AppConfigDeployment>(
|
||||
apiEndpointKeys.clouds.configuration.environments.deployments.get,
|
||||
requestOptions(cloud, "configuration", {signal}),
|
||||
{cloud, id: applicationId, environmentId, deploymentNumber: String(deploymentNumber)},
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
function requestOptions<TBody = unknown>(
|
||||
cloud: CloudProvider,
|
||||
service: string,
|
||||
options: {
|
||||
signal?: AbortSignal;
|
||||
params?: Record<string, string | number | boolean | undefined>;
|
||||
body?: TBody;
|
||||
rawBody?: BodyInit;
|
||||
headers?: HeadersInit;
|
||||
/** Overrides the client default; needed for calls that can legitimately run long. */
|
||||
timeout?: number;
|
||||
} = {},
|
||||
) {
|
||||
return {
|
||||
...options,
|
||||
telemetry: { provider: cloud, service },
|
||||
};
|
||||
}
|
||||
|
||||
function storagePathParams(
|
||||
cloud: CloudProvider,
|
||||
resourceId: string,
|
||||
): CloudPathParams {
|
||||
return { cloud, id: resourceId };
|
||||
}
|
||||
|
||||
function databasePathParams(
|
||||
cloud: CloudProvider,
|
||||
databaseId: string,
|
||||
): CloudPathParams {
|
||||
return { cloud, id: databaseId };
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { apiClient } from "./api";
|
||||
import type { HttpMethod } from "./HttpClient";
|
||||
import {
|
||||
subscribeApiRequests,
|
||||
type ApiRequestEvent,
|
||||
} from "@/telemetry";
|
||||
|
||||
export type FlociRequestEvent = ApiRequestEvent;
|
||||
|
||||
export function subscribeRequests(
|
||||
cb: (event: FlociRequestEvent) => void,
|
||||
): () => void {
|
||||
return subscribeApiRequests(cb);
|
||||
}
|
||||
|
||||
export function apiGet<T>(
|
||||
path: string,
|
||||
service: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<T> {
|
||||
return apiRequest<T>("GET", path, service, undefined, signal);
|
||||
}
|
||||
|
||||
export function apiPost<T>(
|
||||
path: string,
|
||||
service: string,
|
||||
body: unknown,
|
||||
signal?: AbortSignal,
|
||||
): Promise<T> {
|
||||
return apiRequest<T>("POST", path, service, body, signal);
|
||||
}
|
||||
|
||||
export function apiPut<T>(
|
||||
path: string,
|
||||
service: string,
|
||||
body: unknown,
|
||||
signal?: AbortSignal,
|
||||
): Promise<T> {
|
||||
return apiRequest<T>("PUT", path, service, body, signal);
|
||||
}
|
||||
|
||||
export function apiDelete<T>(
|
||||
path: string,
|
||||
service: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<T> {
|
||||
return apiRequest<T>("DELETE", path, service, undefined, signal);
|
||||
}
|
||||
|
||||
async function apiRequest<T>(
|
||||
method: HttpMethod,
|
||||
path: string,
|
||||
service: string,
|
||||
body?: unknown,
|
||||
signal?: AbortSignal,
|
||||
): Promise<T> {
|
||||
const res = await apiClient.request<T>(method, path, {
|
||||
signal,
|
||||
...(body === undefined ? {} : { body }),
|
||||
telemetry: { provider: "aws", service },
|
||||
});
|
||||
return res.data;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
import {
|
||||
getCloudServiceStatus,
|
||||
getCloudStatus,
|
||||
listCloudResources,
|
||||
listCloudServices,
|
||||
listClouds,
|
||||
} from '@/api/cloudProxyClient'
|
||||
import type {CloudProvider, CloudServiceDescriptor, CloudServiceType, CloudStatus} from '@/types/cloud'
|
||||
|
||||
/**
|
||||
* Shared cloud queries.
|
||||
*
|
||||
* These live under api/ rather than in a feature folder because the app shell
|
||||
* (nav, connection indicator) needs them as much as the console home does.
|
||||
* Key strings are unchanged so existing caches still share.
|
||||
*/
|
||||
export const cloudQueryKeys = {
|
||||
clouds: ['clouds'] as const,
|
||||
services: (cloud: CloudProvider) => ['cloud-services', cloud] as const,
|
||||
status: (cloud: CloudProvider) => ['cloud-status', cloud] as const,
|
||||
serviceStatus: (cloud: CloudProvider, service: CloudServiceType) =>
|
||||
['cloud-service-status', cloud, service] as const,
|
||||
resources: (cloud: CloudProvider, service: CloudServiceType) =>
|
||||
['cloud-console-resources', cloud, service] as const,
|
||||
}
|
||||
|
||||
export function useCloudsQuery() {
|
||||
return useQuery({
|
||||
queryKey: cloudQueryKeys.clouds,
|
||||
queryFn: ({signal}) => listClouds(signal),
|
||||
})
|
||||
}
|
||||
|
||||
export function useCloudServicesQuery(cloud: CloudProvider) {
|
||||
return useQuery({
|
||||
queryKey: cloudQueryKeys.services(cloud),
|
||||
queryFn: ({signal}) => listCloudServices(cloud, signal),
|
||||
// The catalog only changes when the API restarts, so this is effectively
|
||||
// free on every navigation while still driving the whole nav.
|
||||
staleTime: 60_000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useCloudStatusQuery(cloud: CloudProvider, options: {refetchInterval?: number} = {}) {
|
||||
return useQuery({
|
||||
queryKey: cloudQueryKeys.status(cloud),
|
||||
queryFn: ({signal}) => getCloudStatus(cloud, signal),
|
||||
refetchInterval: options.refetchInterval ?? 10_000,
|
||||
})
|
||||
}
|
||||
|
||||
/** Health of the single service being viewed, rather than of the whole cloud. */
|
||||
export function useCloudServiceStatusQuery(
|
||||
cloud: CloudProvider,
|
||||
service: CloudServiceType,
|
||||
options: {enabled?: boolean} = {},
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: cloudQueryKeys.serviceStatus(cloud, service),
|
||||
queryFn: ({signal}) => getCloudServiceStatus(cloud, service, signal),
|
||||
refetchInterval: 10_000,
|
||||
enabled: options.enabled ?? true,
|
||||
})
|
||||
}
|
||||
|
||||
export function useCloudConsoleResourcesQuery({
|
||||
cloud,
|
||||
service,
|
||||
services,
|
||||
status,
|
||||
}: {
|
||||
cloud: CloudProvider
|
||||
service: CloudServiceType
|
||||
services?: CloudServiceDescriptor[]
|
||||
status?: CloudStatus
|
||||
}) {
|
||||
return useQuery({
|
||||
queryKey: cloudQueryKeys.resources(cloud, service),
|
||||
queryFn: ({signal}) => listCloudResources(cloud, service, undefined, signal),
|
||||
enabled: hasAvailableService(services, service) && status?.runtime === 'reachable',
|
||||
})
|
||||
}
|
||||
|
||||
function hasAvailableService(services: CloudServiceDescriptor[] | undefined, service: CloudServiceType): boolean {
|
||||
return services?.some((item) => item.service === service && item.availability === 'available') ?? false
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export {
|
||||
apiRequestEventBus,
|
||||
emitApiRequest,
|
||||
subscribeApiRequests,
|
||||
} from "@/telemetry/requestEventBus";
|
||||
export type { ApiRequestEvent, CloudProvider } from "@/telemetry/types";
|
||||
@@ -0,0 +1,114 @@
|
||||
export type ServiceStatus = 'healthy' | 'degraded' | 'unavailable' | 'unknown'
|
||||
|
||||
export type ServiceName =
|
||||
| 's3'
|
||||
| 'sqs'
|
||||
| 'dynamodb'
|
||||
| 'sns'
|
||||
| 'lambda'
|
||||
| 'eks'
|
||||
| 'secretsmanager'
|
||||
| 'cognito'
|
||||
| 'rds'
|
||||
| 'elasticache'
|
||||
| 'iam'
|
||||
| 'ssm'
|
||||
| 'kms'
|
||||
| 'cloudwatch'
|
||||
|
||||
export interface ServiceInfo {
|
||||
name: ServiceName
|
||||
displayName: string
|
||||
status: ServiceStatus
|
||||
requestCount: number
|
||||
errorCount: number
|
||||
latencyP50Ms?: number
|
||||
lastActivity?: string
|
||||
}
|
||||
|
||||
export interface HealthReport {
|
||||
status: ServiceStatus
|
||||
services: ServiceInfo[]
|
||||
checkedAt: string
|
||||
version?: string
|
||||
}
|
||||
|
||||
export interface ResourceSummary {
|
||||
id: string
|
||||
name: string
|
||||
status?: string
|
||||
description?: string
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface ServiceResourceSnapshot {
|
||||
service: ServiceName
|
||||
displayName: string
|
||||
implemented: boolean
|
||||
status: ServiceStatus
|
||||
count?: number
|
||||
latencyMs?: number
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface ConsoleOverview {
|
||||
checkedAt: string
|
||||
health: HealthReport
|
||||
resources: ServiceResourceSnapshot[]
|
||||
logGroupCount: number
|
||||
alarmCount: number
|
||||
metricCount: number
|
||||
totalResourceCount: number
|
||||
}
|
||||
|
||||
export interface CWLogGroup {
|
||||
name: string
|
||||
arn?: string
|
||||
retentionInDays?: number
|
||||
createdAt: number
|
||||
storedBytes: number
|
||||
metricFilterCount: number
|
||||
}
|
||||
|
||||
export interface CWLogStream {
|
||||
name: string
|
||||
createdAt?: number
|
||||
firstEventAt?: number
|
||||
lastEventAt?: number
|
||||
lastIngestionAt?: number
|
||||
storedBytes: number
|
||||
}
|
||||
|
||||
export interface CWLogEvent {
|
||||
id: string
|
||||
timestamp: number
|
||||
message: string
|
||||
ingestionTime?: number
|
||||
}
|
||||
|
||||
export interface CWAlarm {
|
||||
alarmName: string
|
||||
stateValue: 'ALARM' | 'OK' | 'INSUFFICIENT_DATA'
|
||||
stateReason?: string
|
||||
metricName?: string
|
||||
namespace?: string
|
||||
threshold?: number
|
||||
}
|
||||
|
||||
export interface CWMetric {
|
||||
id: string
|
||||
namespace: string
|
||||
metricName: string
|
||||
dimensions: Array<{ name: string; value: string }>
|
||||
}
|
||||
|
||||
export class FlociError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly statusCode?: number,
|
||||
public readonly endpoint?: string
|
||||
) {
|
||||
super(message)
|
||||
this.name = 'FlociError'
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user