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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion workspaces/frontend/scripts/swagger.version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4f0a29dec0d3c9f0d0f02caab4dc84101bfef8b0
be67e887ad7396cf0078edca36201564a208d1b7
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { EllipsisVIcon } from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
import {
Table,
Expand All @@ -24,7 +24,9 @@ import { MenuToggle } from '@patternfly/react-core/dist/esm/components/MenuToggl
import { Form, FormGroup } from '@patternfly/react-core/dist/esm/components/Form';
import { HelperText, HelperTextItem } from '@patternfly/react-core/dist/esm/components/HelperText';
import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon';
import { WorkspacesPodSecretMount } from '~/generated/data-contracts';
import { SecretsSecretListItem, WorkspacesPodSecretMount } from '~/generated/data-contracts';
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
import { useNamespaceContext } from '~/app/context/NamespaceContextProvider';

interface WorkspaceFormPropertiesSecretsProps {
secrets: WorkspacesPodSecretMount[];
Expand All @@ -49,6 +51,18 @@ export const WorkspaceFormPropertiesSecrets: React.FC<WorkspaceFormPropertiesSec
const [deleteIndex, setDeleteIndex] = useState<number | null>(null);
const [isDefaultModeValid, setIsDefaultModeValid] = useState(true);
const [dropdownOpen, setDropdownOpen] = useState<number | null>(null);
const [, setAvailableSecrets] = useState<SecretsSecretListItem[]>([]);

const { api } = useNotebookAPI();
const { selectedNamespace } = useNamespaceContext();

useEffect(() => {
const fetchSecrets = async () => {
const secretsResponse = await api.secrets.listSecrets(selectedNamespace);
setAvailableSecrets(secretsResponse.data);
};
fetchSecrets();
}, [api.secrets, selectedNamespace]);

const openDeleteModal = useCallback((i: number) => {
setIsDeleteModalOpen(true);
Expand Down
141 changes: 141 additions & 0 deletions workspaces/frontend/src/generated/Secrets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/* eslint-disable */
/* tslint:disable */
// @ts-nocheck
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: acacode ##
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
* ---------------------------------------------------------------
*/

import {
ApiErrorEnvelope,
ApiSecretCreateEnvelope,
ApiSecretEnvelope,
ApiSecretListEnvelope,
SecretsSecretUpdate,
} from './data-contracts';
import { ContentType, HttpClient, RequestParams } from './http-client';

export class Secrets<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
/**
* @description Provides a list of all secrets that the user has access to in the specified namespace
*
* @tags secrets
* @name ListSecrets
* @summary Returns a list of all secrets in a namespace
* @request GET:/secrets/{namespace}
* @response `200` `ApiSecretListEnvelope` Successful secrets response
* @response `401` `ApiErrorEnvelope` Unauthorized
* @response `403` `ApiErrorEnvelope` Forbidden
* @response `422` `ApiErrorEnvelope` Unprocessable Entity. Validation error.
* @response `500` `ApiErrorEnvelope` Internal server error
*/
listSecrets = (namespace: string, params: RequestParams = {}) =>
this.request<ApiSecretListEnvelope, ApiErrorEnvelope>({
path: `/secrets/${namespace}`,
method: 'GET',
format: 'json',
...params,
});
/**
* @description Creates a new secret in the specified namespace
*
* @tags secrets
* @name CreateSecret
* @summary Creates a new secret
* @request POST:/secrets/{namespace}
* @response `201` `ApiSecretCreateEnvelope` Secret created successfully
* @response `400` `ApiErrorEnvelope` Bad request
* @response `401` `ApiErrorEnvelope` Unauthorized
* @response `403` `ApiErrorEnvelope` Forbidden
* @response `409` `ApiErrorEnvelope` Secret already exists
* @response `413` `ApiErrorEnvelope` Request Entity Too Large. The request body is too large.
* @response `415` `ApiErrorEnvelope` Unsupported Media Type. Content-Type header is not correct.
* @response `422` `ApiErrorEnvelope` Unprocessable Entity. Validation error.
* @response `500` `ApiErrorEnvelope` Internal server error
*/
createSecret = (namespace: string, secret: ApiSecretCreateEnvelope, params: RequestParams = {}) =>
this.request<ApiSecretCreateEnvelope, ApiErrorEnvelope>({
path: `/secrets/${namespace}`,
method: 'POST',
body: secret,
type: ContentType.Json,
format: 'json',
...params,
});
/**
* @description Provides details of a specific secret by name and namespace
*
* @tags secrets
* @name GetSecret
* @summary Returns a specific secret
* @request GET:/secrets/{namespace}/{name}
* @response `200` `ApiSecretEnvelope` Successful secret response
* @response `401` `ApiErrorEnvelope` Unauthorized
* @response `403` `ApiErrorEnvelope` Forbidden
* @response `404` `ApiErrorEnvelope` Secret not found
* @response `422` `ApiErrorEnvelope` Unprocessable Entity. Validation error.
* @response `500` `ApiErrorEnvelope` Internal server error
*/
getSecret = (namespace: string, name: string, params: RequestParams = {}) =>
this.request<ApiSecretEnvelope, ApiErrorEnvelope>({
path: `/secrets/${namespace}/${name}`,
method: 'GET',
format: 'json',
...params,
});
/**
* @description Updates an existing secret in the specified namespace
*
* @tags secrets
* @name UpdateSecret
* @summary Updates an existing secret
* @request PUT:/secrets/{namespace}/{name}
* @response `200` `ApiSecretEnvelope` Secret updated successfully
* @response `400` `ApiErrorEnvelope` Bad request
* @response `401` `ApiErrorEnvelope` Unauthorized
* @response `403` `ApiErrorEnvelope` Forbidden
* @response `404` `ApiErrorEnvelope` Secret not found
* @response `413` `ApiErrorEnvelope` Request Entity Too Large. The request body is too large.
* @response `415` `ApiErrorEnvelope` Unsupported Media Type. Content-Type header is not correct.
* @response `422` `ApiErrorEnvelope` Unprocessable Entity. Validation error.
* @response `500` `ApiErrorEnvelope` Internal server error
*/
updateSecret = (
namespace: string,
name: string,
secret: SecretsSecretUpdate,
params: RequestParams = {},
) =>
this.request<ApiSecretEnvelope, ApiErrorEnvelope>({
path: `/secrets/${namespace}/${name}`,
method: 'PUT',
body: secret,
type: ContentType.Json,
format: 'json',
...params,
});
/**
* @description Deletes a secret from the specified namespace
*
* @tags secrets
* @name DeleteSecret
* @summary Deletes a secret
* @request DELETE:/secrets/{namespace}/{name}
* @response `204` `void` No Content
* @response `401` `ApiErrorEnvelope` Unauthorized
* @response `403` `ApiErrorEnvelope` Forbidden
* @response `404` `ApiErrorEnvelope` Secret not found
* @response `500` `ApiErrorEnvelope` Internal server error
*/
deleteSecret = (namespace: string, name: string, params: RequestParams = {}) =>
this.request<void, ApiErrorEnvelope>({
path: `/secrets/${namespace}/${name}`,
method: 'DELETE',
type: ContentType.Json,
...params,
});
}
54 changes: 54 additions & 0 deletions workspaces/frontend/src/generated/data-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ export interface ApiNamespaceListEnvelope {
data: NamespacesNamespace[];
}

export interface ApiSecretCreateEnvelope {
data: SecretsSecretCreate;
}

export interface ApiSecretEnvelope {
data: SecretsSecretUpdate;
}

export interface ApiSecretListEnvelope {
data: SecretsSecretListItem[];
}

export interface ApiValidationError {
field: string;
message: string;
Expand Down Expand Up @@ -107,6 +119,13 @@ export interface ApiWorkspaceListEnvelope {
data: WorkspacesWorkspace[];
}

export interface CommonAudit {
createdAt: string;
createdBy: string;
updatedAt: string;
updatedBy: string;
}

export interface HealthCheckHealthCheck {
status: HealthCheckServiceStatus;
systemInfo: HealthCheckSystemInfo;
Expand All @@ -120,6 +139,41 @@ export interface NamespacesNamespace {
name: string;
}

export interface SecretsSecretCreate {
contents: SecretsSecretData;
immutable: boolean;
name: string;
type: string;
}

export type SecretsSecretData = Record<string, SecretsSecretValue>;

export interface SecretsSecretListItem {
audit: CommonAudit;
canMount: boolean;
canUpdate: boolean;
immutable: boolean;
mounts?: SecretsSecretMount[];
name: string;
type: string;
}

export interface SecretsSecretMount {
group: string;
kind: string;
name: string;
}

export interface SecretsSecretUpdate {
contents: SecretsSecretData;
immutable: boolean;
type: string;
}

export interface SecretsSecretValue {
base64?: string;
}

export interface WorkspacekindsImageConfig {
default: string;
values: WorkspacekindsImageConfigValue[];
Expand Down
3 changes: 3 additions & 0 deletions workspaces/frontend/src/shared/api/notebookApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Healthcheck } from '~/generated/Healthcheck';
import { Namespaces } from '~/generated/Namespaces';
import { Secrets } from '~/generated/Secrets';
import { Workspacekinds } from '~/generated/Workspacekinds';
import { Workspaces } from '~/generated/Workspaces';
import { ApiInstance } from '~/shared/api/types';
Expand All @@ -9,6 +10,7 @@ export interface NotebookApis {
namespaces: ApiInstance<typeof Namespaces>;
workspaces: ApiInstance<typeof Workspaces>;
workspaceKinds: ApiInstance<typeof Workspacekinds>;
secrets: ApiInstance<typeof Secrets>;
}

export const notebookApisImpl = (path: string): NotebookApis => {
Expand All @@ -19,5 +21,6 @@ export const notebookApisImpl = (path: string): NotebookApis => {
namespaces: new Namespaces(commonConfig),
workspaces: new Workspaces(commonConfig),
workspaceKinds: new Workspacekinds(commonConfig),
secrets: new Secrets(commonConfig),
};
};
18 changes: 18 additions & 0 deletions workspaces/frontend/src/shared/mock/mockBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
HealthCheckHealthCheck,
HealthCheckServiceStatus,
NamespacesNamespace,
SecretsSecretListItem,
WorkspacekindsRedirectMessageLevel,
WorkspacekindsWorkspaceKind,
WorkspacesImageConfig,
Expand Down Expand Up @@ -475,3 +476,20 @@ export const buildMockWorkspaceList = (args: {
}
return workspaces;
};

export const buildMockSecret = (
secret?: Partial<SecretsSecretListItem>,
): SecretsSecretListItem => ({
name: 'secret-1',
type: 'Opaque',
immutable: false,
canMount: true,
canUpdate: true,
audit: {
createdAt: new Date(2025, 4, 1).toISOString(),
createdBy: 'test',
updatedAt: new Date(2025, 4, 1).toISOString(),
updatedBy: 'test',
},
...secret,
});
20 changes: 20 additions & 0 deletions workspaces/frontend/src/shared/mock/mockNotebookApis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
mockAllWorkspaces,
mockedHealthCheckResponse,
mockNamespaces,
mockSecretCreate,
mockSecretsList,
mockWorkspace1,
mockWorkspaceKind1,
mockWorkspaceKinds,
Expand Down Expand Up @@ -80,4 +82,22 @@ export const mockNotebookApisImpl = (): NotebookApis => ({
return { data: mockWorkspaceKind1 };
},
},
secrets: {
listSecrets: async () => ({
data: mockSecretsList,
}),
createSecret: async () => ({
data: mockSecretCreate,
}),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getSecret: async () => ({
data: mockSecretCreate,
}),
updateSecret: async () => ({
data: mockSecretCreate,
}),
deleteSecret: async () => {
await delay(1500);
},
},
});
22 changes: 22 additions & 0 deletions workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
SecretsSecretCreate,
WorkspacekindsWorkspaceKind,
WorkspacesWorkspace,
WorkspacesWorkspaceKindInfo,
Expand All @@ -11,6 +12,7 @@ import {
buildMockWorkspaceKind,
buildMockWorkspaceKindInfo,
buildMockWorkspaceList,
buildMockSecret,
} from '~/shared/mock/mockBuilder';

// Health
Expand Down Expand Up @@ -171,3 +173,23 @@ export const mockAllWorkspaces = [
kind: mockWorkspaceKindInfo1,
}),
];

export const mockSecretCreate: SecretsSecretCreate = {
name: 'secret-1',
type: 'Opaque',
immutable: false,
contents: {
username: {
base64: 'abcd',
},
},
};

export const mockSecretsList = [
buildMockSecret({
name: 'secret-1',
}),
buildMockSecret({
name: 'secret-2',
}),
];
Loading