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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/client/endpoints/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,29 @@ export async function getProcessInstanceCount(
);
}

export interface ExitCodePoint {
time: string;
value: string;
description: string | null;
}

export interface ExitCodesQuery {
from: string;
to: string;
}

export async function getProcessExitCodes(
client: ApiClient,
id: string,
processId: string,
query: ExitCodesQuery,
): Promise<ExitCodePoint[]> {
return client.get<ExitCodePoint[]>(
`${BASE}/${id}/processes/${processId}/metrics/exit-codes`,
query as unknown as QueryParams,
);
}

// ---------------------------------------------------------------------------
// Environment variables
// ---------------------------------------------------------------------------
Expand Down
16 changes: 16 additions & 0 deletions src/client/endpoints/object-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,19 @@ export async function deleteObjects(
): Promise<void> {
return client.delete<undefined>(`/object-storage/${id}/objects`, body);
}

// ---------------------------------------------------------------------------
// Credentials
// ---------------------------------------------------------------------------

export interface RotateCredentialsBody {
old_keys_ttl_hours?: number;
}

export async function rotateObjectStorageCredentials(
client: ApiClient,
id: string,
body?: RotateCredentialsBody,
): Promise<ObjectStorageBucket> {
return client.post<ObjectStorageBucket>(`/object-storage/${id}/rotate-credentials`, body ?? {});
}
23 changes: 23 additions & 0 deletions src/client/endpoints/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ export interface ProcessResourceType {
is_available: boolean;
}

export interface ApiKeyPermission {
id: string;
name: string;
description: string;
resource: string;
action: string;
}

export interface ApiKeyRole {
id: string;
name: string;
description: string;
permissions: string[];
}

// ---------------------------------------------------------------------------
// Endpoints
// ---------------------------------------------------------------------------
Expand All @@ -48,3 +63,11 @@ export async function listDatabaseResourceTypes(
export async function listProcessResourceTypes(client: ApiClient): Promise<ProcessResourceType[]> {
return client.get<ProcessResourceType[]>('/resources/process-resource-types');
}

export async function listApiKeyPermissions(client: ApiClient): Promise<ApiKeyPermission[]> {
return client.get<ApiKeyPermission[]>('/resources/rbac/api-key-permissions');
}

export async function listApiKeyRoles(client: ApiClient): Promise<ApiKeyRole[]> {
return client.get<ApiKeyRole[]>('/resources/rbac/api-key-roles');
}
15 changes: 15 additions & 0 deletions src/client/endpoints/static-sites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,3 +432,18 @@ export async function getStaticSiteTopPages(
export async function purgeStaticSiteCache(client: ApiClient, id: string): Promise<void> {
return client.post<undefined>(`/static-sites/${id}/purge-cache`);
}

// ---------------------------------------------------------------------------
// Pretty URLs
// ---------------------------------------------------------------------------

export interface PrettyUrlStatus {
is_turned_on: boolean;
}

export async function toggleStaticSitePrettyUrl(
client: ApiClient,
id: string,
): Promise<PrettyUrlStatus> {
return client.post<PrettyUrlStatus>(`/static-sites/${id}/pretty-url/toggle`);
}
77 changes: 77 additions & 0 deletions src/client/endpoints/usage-alerts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { ApiClient } from '../api-client.ts';
import type { PaginatedResponse, PaginationQuery, QueryParams } from '../types.ts';

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

export interface UsageAlertTrigger {
id: string;
percentage: number;
last_fired_at: string | null;
}

export interface UsageAlertConfig {
id: string;
company_id: string;
project_id: string | null;
limit_usd: number;
emails: string[];
triggers: UsageAlertTrigger[];
created_at: string;
updated_at: string;
}

export interface CreateUsageAlertBody {
limit_usd: number;
emails: string[];
triggers: Array<{ percentage: number }>;
project_id?: string | null;
}

export interface UpdateUsageAlertBody {
limit_usd?: number;
emails?: string[];
triggers?: Array<{ percentage: number }>;
}

export interface ListUsageAlertsQuery extends PaginationQuery {
project_id?: string;
}

// ---------------------------------------------------------------------------
// Endpoints
// ---------------------------------------------------------------------------

export async function listUsageAlerts(
client: ApiClient,
query?: ListUsageAlertsQuery,
): Promise<PaginatedResponse<UsageAlertConfig>> {
return client.get<PaginatedResponse<UsageAlertConfig>>(
'/usage-alerts',
query as QueryParams | undefined,
);
}

export async function createUsageAlert(
client: ApiClient,
body: CreateUsageAlertBody,
): Promise<UsageAlertConfig> {
return client.post<UsageAlertConfig>('/usage-alerts', body);
}

export async function getUsageAlert(client: ApiClient, id: string): Promise<UsageAlertConfig> {
return client.get<UsageAlertConfig>(`/usage-alerts/${id}`);
}

export async function updateUsageAlert(
client: ApiClient,
id: string,
body: UpdateUsageAlertBody,
): Promise<UsageAlertConfig> {
return client.patch<UsageAlertConfig>(`/usage-alerts/${id}`, body);
}

export async function deleteUsageAlert(client: ApiClient, id: string): Promise<void> {
return client.delete<undefined>(`/usage-alerts/${id}`);
}
57 changes: 57 additions & 0 deletions src/commands/apps/processes/metrics/exit-codes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Command } from 'commander';
import { createClient } from '../../../../client/api-client.ts';
import { handleError } from '../../../../errors/handler.ts';
import { printJson, printTable } from '../../../../output/formatter.ts';
import { startSpinner, stopSpinner } from '../../../../output/spinner.ts';
import { jsonOption, resolveJsonMode } from '../../../../helpers/flags.ts';
import { validateId } from '../../../../helpers/validate.ts';
import { Option } from 'commander';
import type { QueryParams } from '../../../../client/types.ts';

interface ExitCodePoint {
time: string;
value: string;
description: string | null;
}

export const exitCodesCommand = new Command('exit-codes')
.description('Get process exit codes within a time range')
.argument('<app-id>', 'app-id ID')
.argument('<process-id>', 'process-id ID')
.addOption(jsonOption())
.addOption(new Option('--from <date>', 'Start date (ISO 8601, default: 24h ago)'))
.addOption(new Option('--to <date>', 'End date (ISO 8601, default: now)'))
.action(async (appId: string, processId: string, opts: Record<string, unknown>) => {
const client = createClient({ apiUrl: opts['apiUrl'] as string | undefined });
const json = resolveJsonMode(opts);
try {
validateId(appId, 'app-id');
validateId(processId, 'process-id');

const now = new Date();
const dayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const query: QueryParams = {
from: (opts['from'] as string | undefined) ?? dayAgo.toISOString(),
to: (opts['to'] as string | undefined) ?? now.toISOString(),
};

if (!json) startSpinner('Fetching exit codes...');
const result = await client.get<ExitCodePoint[]>(
`/applications/${appId}/processes/${processId}/metrics/exit-codes`,
query,
);
stopSpinner();
if (json) {
printJson(result);
} else {
printTable(result, [
{ header: 'Time', key: 'time' },
{ header: 'Exit code', key: 'value' },
{ header: 'Description', key: 'description' },
]);
}
} catch (error) {
stopSpinner();
handleError(error, json);
}
});
2 changes: 2 additions & 0 deletions src/commands/apps/processes/metrics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { cpuLimitCommand } from './cpu-limit.ts';
import { memoryUsageCommand } from './memory-usage.ts';
import { memoryLimitCommand } from './memory-limit.ts';
import { instanceCountCommand } from './instance-count.ts';
import { exitCodesCommand } from './exit-codes.ts';

export function makeProcessMetricsCommands(): Command {
const cmd = new Command('metrics').description('Process metrics');
Expand All @@ -12,5 +13,6 @@ export function makeProcessMetricsCommands(): Command {
cmd.addCommand(memoryUsageCommand);
cmd.addCommand(memoryLimitCommand);
cmd.addCommand(instanceCountCommand);
cmd.addCommand(exitCodesCommand);
return cmd;
}
2 changes: 2 additions & 0 deletions src/commands/object-storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { objectStorageGetCommand } from './get.ts';
import { objectStorageCreateCommand } from './create.ts';
import { objectStorageUpdateCommand } from './update.ts';
import { objectStorageDeleteCommand } from './delete.ts';
import { objectStorageRotateCredentialsCommand } from './rotate-credentials.ts';
import { makeCdnDomainCommands } from './cdn-domain/index.ts';
import { makeCorsPoliciesCommands } from './cors-policies/index.ts';
import { makeObjectsCommands } from './objects/index.ts';
Expand All @@ -15,6 +16,7 @@ export function makeObjectStorageCommand(): Command {
cmd.addCommand(objectStorageCreateCommand);
cmd.addCommand(objectStorageUpdateCommand);
cmd.addCommand(objectStorageDeleteCommand);
cmd.addCommand(objectStorageRotateCredentialsCommand);
cmd.addCommand(makeCdnDomainCommands());
cmd.addCommand(makeCorsPoliciesCommands());
cmd.addCommand(makeObjectsCommands());
Expand Down
32 changes: 32 additions & 0 deletions src/commands/object-storage/rotate-credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { makeActionCommand } from '../../helpers/command-factory.ts';

export const objectStorageRotateCredentialsCommand = makeActionCommand<Record<string, unknown>>({
name: 'rotate-credentials',
description: 'Rotate access and secret keys for an object storage bucket',
options: [
{
flags: '--old-keys-ttl-hours <hours>',
description:
'Keep previous keys valid for this many hours (0-168). 0 invalidates immediately. Defaults to 0.',
},
],
apiCall: (client, id, opts) => {
const body: Record<string, unknown> = {};
if (opts['oldKeysTtlHours'] !== undefined) {
body['old_keys_ttl_hours'] = Number(opts['oldKeysTtlHours']);
}
return client.post(`/object-storage/${id}/rotate-credentials`, body);
},
successMessage:
'Credentials rotated. Capture the new access_key and secret_key from the response — they are not retrievable later.',
spinnerText: 'Rotating credentials...',
displayFields: (item) => ({
ID: item['id'],
'Display Name': item['display_name'],
'New Access Key': item['access_key'],
'New Secret Key': item['secret_key'],
'Previous Access Key': item['old_access_key'],
'Previous Secret Key': item['old_secret_key'],
'Previous Keys Expire At': item['old_keys_expired_at'],
}),
});
4 changes: 4 additions & 0 deletions src/commands/resources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import { Command } from 'commander';
import { resourcesClustersCommand } from './clusters.ts';
import { resourcesDbTypesCommand } from './db-types.ts';
import { resourcesProcessTypesCommand } from './process-types.ts';
import { resourcesRbacPermissionsCommand } from './rbac-permissions.ts';
import { resourcesRbacRolesCommand } from './rbac-roles.ts';

export function makeResourcesCommand(): Command {
const cmd = new Command('resources').description('List available resources');
cmd.addCommand(resourcesClustersCommand);
cmd.addCommand(resourcesDbTypesCommand);
cmd.addCommand(resourcesProcessTypesCommand);
cmd.addCommand(resourcesRbacPermissionsCommand);
cmd.addCommand(resourcesRbacRolesCommand);
return cmd;
}
14 changes: 14 additions & 0 deletions src/commands/resources/rbac-permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { makeListCommand } from '../../helpers/command-factory.ts';

export const resourcesRbacPermissionsCommand = makeListCommand({
name: 'rbac-permissions',
description: 'List available API key permissions',
columns: [
{ header: 'ID', key: 'id' },
{ header: 'Name', key: 'name' },
{ header: 'Resource', key: 'resource' },
{ header: 'Action', key: 'action' },
{ header: 'Description', key: 'description' },
],
apiCall: (client) => client.get('/resources/rbac/api-key-permissions'),
});
12 changes: 12 additions & 0 deletions src/commands/resources/rbac-roles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { makeListCommand } from '../../helpers/command-factory.ts';

export const resourcesRbacRolesCommand = makeListCommand({
name: 'rbac-roles',
description: 'List available API key roles',
columns: [
{ header: 'ID', key: 'id' },
{ header: 'Name', key: 'name' },
{ header: 'Description', key: 'description' },
],
apiCall: (client) => client.get('/resources/rbac/api-key-roles'),
});
2 changes: 2 additions & 0 deletions src/commands/static-sites/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { staticSitesCreateCommand } from './create.ts';
import { staticSitesUpdateCommand } from './update.ts';
import { staticSitesDeleteCommand } from './delete.ts';
import { staticSitesPurgeCacheCommand } from './purge-cache.ts';
import { staticSitesPrettyUrlToggleCommand } from './pretty-url-toggle.ts';
import { makeDeploymentsCommands } from './deployments/index.ts';
import { makeDomainsCommands } from './domains/index.ts';
import { makeEnvVarsCommands } from './env-vars/index.ts';
Expand All @@ -19,6 +20,7 @@ export function makeStaticSitesCommand(): Command {
cmd.addCommand(staticSitesUpdateCommand);
cmd.addCommand(staticSitesDeleteCommand);
cmd.addCommand(staticSitesPurgeCacheCommand);
cmd.addCommand(staticSitesPrettyUrlToggleCommand);
cmd.addCommand(makeDeploymentsCommands());
cmd.addCommand(makeDomainsCommands());
cmd.addCommand(makeEnvVarsCommands());
Expand Down
13 changes: 13 additions & 0 deletions src/commands/static-sites/pretty-url-toggle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { makeActionCommand } from '../../helpers/command-factory.ts';

interface PrettyUrlStatus {
is_turned_on: boolean;
}

export const staticSitesPrettyUrlToggleCommand = makeActionCommand<PrettyUrlStatus>({
name: 'pretty-url-toggle',
description: 'Toggle pretty URL redirects for a static site',
apiCall: (client, id) => client.post(`/static-sites/${id}/pretty-url/toggle`),
successMessage: (result) => `Pretty URLs ${result.is_turned_on ? 'enabled' : 'disabled'}.`,
spinnerText: 'Toggling pretty URLs...',
});
Loading
Loading