From ad22d2be231f6d5aa1c5f070dec5c3cf354d7264 Mon Sep 17 00:00:00 2001 From: Simon Murray Date: Thu, 29 Feb 2024 14:50:03 +0000 Subject: [PATCH] Get Projects Working Very rudimentary, but it's a start! --- src/lib/StatusIcon.svelte | 52 +++---- .../openapi/server/.openapi-generator/FILES | 1 + src/lib/openapi/server/apis/DefaultApi.ts | 140 ++++++++++++++++-- src/lib/openapi/server/models/Project.ts | 9 ++ src/lib/openapi/server/models/Projects.ts | 51 +++++++ src/lib/openapi/server/models/index.ts | 1 + src/lib/validation.ts | 2 +- src/routes/+layout.svelte | 7 +- src/routes/identity/projects/+page.svelte | 72 ++++++++- .../identity/projects/create/+page.svelte | 77 ++++++++++ .../clusters/create/+page.svelte | 32 ++-- 11 files changed, 372 insertions(+), 72 deletions(-) create mode 100644 src/lib/openapi/server/models/Projects.ts create mode 100644 src/routes/identity/projects/create/+page.svelte diff --git a/src/lib/StatusIcon.svelte b/src/lib/StatusIcon.svelte index 7e53843..985fd85 100644 --- a/src/lib/StatusIcon.svelte +++ b/src/lib/StatusIcon.svelte @@ -1,36 +1,22 @@ - - -{#if status == 'ok'} - -{:else if status == 'warning'} - -{:else if status == 'error'} - -{:else if status == 'progressing'} - -{:else} - -{/if} + + + diff --git a/src/lib/openapi/server/.openapi-generator/FILES b/src/lib/openapi/server/.openapi-generator/FILES index 8e36db6..e11c994 100644 --- a/src/lib/openapi/server/.openapi-generator/FILES +++ b/src/lib/openapi/server/.openapi-generator/FILES @@ -42,6 +42,7 @@ models/OpenstackProject.ts models/OpenstackProjects.ts models/OpenstackVolume.ts models/Project.ts +models/Projects.ts models/Region.ts models/Regions.ts models/TimeWindow.ts diff --git a/src/lib/openapi/server/apis/DefaultApi.ts b/src/lib/openapi/server/apis/DefaultApi.ts index 2bd2bb5..087db8b 100644 --- a/src/lib/openapi/server/apis/DefaultApi.ts +++ b/src/lib/openapi/server/apis/DefaultApi.ts @@ -27,6 +27,8 @@ import type { OpenstackFlavors, OpenstackImages, OpenstackKeyPairs, + Project, + Projects, Regions, } from '../models/index'; import { @@ -54,6 +56,10 @@ import { OpenstackImagesToJSON, OpenstackKeyPairsFromJSON, OpenstackKeyPairsToJSON, + ProjectFromJSON, + ProjectToJSON, + ProjectsFromJSON, + ProjectsToJSON, RegionsFromJSON, RegionsToJSON, } from '../models/index'; @@ -105,6 +111,14 @@ export interface ApiV1ControlplanesPostRequest { controlPlane: ControlPlane; } +export interface ApiV1ProjectsPostRequest { + project: Project; +} + +export interface ApiV1ProjectsProjectNameDeleteRequest { + projectName: string; +} + export interface ApiV1RegionsRegionNameAvailabilityZonesBlockStorageGetRequest { regionName: string; } @@ -642,9 +656,9 @@ export class DefaultApi extends runtime.BaseAPI { } /** - * Deletes the project associated with the authenticated user\'s scoped authorisation token. This is a cascading operation and will delete all contained control planes and clusters. + * Deletes an organization and all its resources based on the access token claims. */ - async apiV1ProjectDeleteRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async apiV1OrganizationDeleteRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -655,7 +669,7 @@ export class DefaultApi extends runtime.BaseAPI { } const response = await this.request({ - path: `/api/v1/project`, + path: `/api/v1/organization`, method: 'DELETE', headers: headerParameters, query: queryParameters, @@ -665,40 +679,142 @@ export class DefaultApi extends runtime.BaseAPI { } /** - * Deletes the project associated with the authenticated user\'s scoped authorisation token. This is a cascading operation and will delete all contained control planes and clusters. + * Deletes an organization and all its resources based on the access token claims. + */ + async apiV1OrganizationDelete(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.apiV1OrganizationDeleteRaw(initOverrides); + } + + /** + * Creates a new organization based on the access token claims. + */ + async apiV1OrganizationPostRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + // oauth required + headerParameters["Authorization"] = await this.configuration.accessToken("oauth2Authentication", []); + } + + const response = await this.request({ + path: `/api/v1/organization`, + method: 'POST', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.VoidApiResponse(response); + } + + /** + * Creates a new organization based on the access token claims. + */ + async apiV1OrganizationPost(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.apiV1OrganizationPostRaw(initOverrides); + } + + /** + * List all projects for the organization. + */ + async apiV1ProjectsGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + // oauth required + headerParameters["Authorization"] = await this.configuration.accessToken("oauth2Authentication", []); + } + + const response = await this.request({ + path: `/api/v1/projects`, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => ProjectsFromJSON(jsonValue)); + } + + /** + * List all projects for the organization. */ - async apiV1ProjectDelete(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - await this.apiV1ProjectDeleteRaw(initOverrides); + async apiV1ProjectsGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.apiV1ProjectsGetRaw(initOverrides); + return await response.value(); } /** - * Creates a new project resource associated with the authenticated user\'s scoped authorisation token. + * Creates a new project resource for the user\'s organization. */ - async apiV1ProjectPostRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async apiV1ProjectsPostRaw(requestParameters: ApiV1ProjectsPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.project === null || requestParameters.project === undefined) { + throw new runtime.RequiredError('project','Required parameter requestParameters.project was null or undefined when calling apiV1ProjectsPost.'); + } + const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; + headerParameters['Content-Type'] = 'application/json'; + if (this.configuration && this.configuration.accessToken) { // oauth required headerParameters["Authorization"] = await this.configuration.accessToken("oauth2Authentication", []); } const response = await this.request({ - path: `/api/v1/project`, + path: `/api/v1/projects`, method: 'POST', headers: headerParameters, query: queryParameters, + body: ProjectToJSON(requestParameters.project), }, initOverrides); return new runtime.VoidApiResponse(response); } /** - * Creates a new project resource associated with the authenticated user\'s scoped authorisation token. + * Creates a new project resource for the user\'s organization. + */ + async apiV1ProjectsPost(requestParameters: ApiV1ProjectsPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.apiV1ProjectsPostRaw(requestParameters, initOverrides); + } + + /** + * Deletes the project associated with the authenticated user\'s scoped authorisation token. This is a cascading operation and will delete all contained control planes and clusters. + */ + async apiV1ProjectsProjectNameDeleteRaw(requestParameters: ApiV1ProjectsProjectNameDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.projectName === null || requestParameters.projectName === undefined) { + throw new runtime.RequiredError('projectName','Required parameter requestParameters.projectName was null or undefined when calling apiV1ProjectsProjectNameDelete.'); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + // oauth required + headerParameters["Authorization"] = await this.configuration.accessToken("oauth2Authentication", []); + } + + const response = await this.request({ + path: `/api/v1/projects/{projectName}`.replace(`{${"projectName"}}`, encodeURIComponent(String(requestParameters.projectName))), + method: 'DELETE', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.VoidApiResponse(response); + } + + /** + * Deletes the project associated with the authenticated user\'s scoped authorisation token. This is a cascading operation and will delete all contained control planes and clusters. */ - async apiV1ProjectPost(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - await this.apiV1ProjectPostRaw(initOverrides); + async apiV1ProjectsProjectNameDelete(requestParameters: ApiV1ProjectsProjectNameDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.apiV1ProjectsProjectNameDeleteRaw(requestParameters, initOverrides); } /** diff --git a/src/lib/openapi/server/models/Project.ts b/src/lib/openapi/server/models/Project.ts index 816328a..2053990 100644 --- a/src/lib/openapi/server/models/Project.ts +++ b/src/lib/openapi/server/models/Project.ts @@ -26,6 +26,12 @@ import { * @interface Project */ export interface Project { + /** + * + * @type {string} + * @memberof Project + */ + name: string; /** * * @type {KubernetesResourceStatus} @@ -39,6 +45,7 @@ export interface Project { */ export function instanceOfProject(value: object): boolean { let isInstance = true; + isInstance = isInstance && "name" in value; return isInstance; } @@ -53,6 +60,7 @@ export function ProjectFromJSONTyped(json: any, ignoreDiscriminator: boolean): P } return { + 'name': json['name'], 'status': !exists(json, 'status') ? undefined : KubernetesResourceStatusFromJSON(json['status']), }; } @@ -66,6 +74,7 @@ export function ProjectToJSON(value?: Project | null): any { } return { + 'name': value.name, 'status': KubernetesResourceStatusToJSON(value.status), }; } diff --git a/src/lib/openapi/server/models/Projects.ts b/src/lib/openapi/server/models/Projects.ts new file mode 100644 index 0000000..9dcf37e --- /dev/null +++ b/src/lib/openapi/server/models/Projects.ts @@ -0,0 +1,51 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Kubernetes Service API + * The Kubernetes Service API provides services that allows provisioning and life cycle management of Kubernetes clusters. The API is logically composed of authentication services, platform provider specific calls to get a set of resource types that can be then used by abstract Kubernetes Service resources to create and manage Kubernetes clusters. Requests must specify the HTML content type header. + * + * The version of the OpenAPI document: 0.2.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { Project } from './Project'; +import { + ProjectFromJSON, + ProjectFromJSONTyped, + ProjectToJSON, +} from './Project'; + +/** + * A list of projects. + * @export + * @interface Projects + */ +export interface Projects extends Array { +} + +/** + * Check if a given object implements the Projects interface. + */ +export function instanceOfProjects(value: object): boolean { + let isInstance = true; + + return isInstance; +} + +export function ProjectsFromJSON(json: any): Projects { + return ProjectsFromJSONTyped(json, false); +} + +export function ProjectsFromJSONTyped(json: any, ignoreDiscriminator: boolean): Projects { + return json; +} + +export function ProjectsToJSON(value?: Projects | null): any { + return value; +} + diff --git a/src/lib/openapi/server/models/index.ts b/src/lib/openapi/server/models/index.ts index 9b8b565..411fbd3 100644 --- a/src/lib/openapi/server/models/index.ts +++ b/src/lib/openapi/server/models/index.ts @@ -41,6 +41,7 @@ export * from './OpenstackProject'; export * from './OpenstackProjects'; export * from './OpenstackVolume'; export * from './Project'; +export * from './Projects'; export * from './Region'; export * from './Regions'; export * from './TimeWindow'; diff --git a/src/lib/validation.ts b/src/lib/validation.ts index 54ddd63..a4049a5 100644 --- a/src/lib/validation.ts +++ b/src/lib/validation.ts @@ -12,5 +12,5 @@ export function namedResourceNames(resources: any): Array { export function unique(needle: string, haystack: Array): boolean { if (haystack == null) return true; - return haystack.includes(needle); + return !haystack.includes(needle); } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 532e6c5..69c254b 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -8,8 +8,8 @@ /* Required for configuration */ import { env } from '$env/dynamic/public'; - /* Required for drawers */ - import { initializeStores, AppShell } from '@skeletonlabs/skeleton'; + /* Required for drawers and modals */ + import { initializeStores, Modal } from '@skeletonlabs/skeleton'; initializeStores(); /* Required for popups */ @@ -17,6 +17,8 @@ import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floating-ui/dom'; storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow }); + /* Shell components */ + import { AppShell } from '@skeletonlabs/skeleton'; import ShellAppBar from '$lib/shell/ShellAppBar.svelte'; import ShellSideBar from '$lib/shell/ShellSideBar.svelte'; import ShellDrawer from '$lib/shell/ShellDrawer.svelte'; @@ -72,6 +74,7 @@ + diff --git a/src/routes/identity/projects/+page.svelte b/src/routes/identity/projects/+page.svelte index 16f04aa..780b7f4 100644 --- a/src/routes/identity/projects/+page.svelte +++ b/src/routes/identity/projects/+page.svelte @@ -8,6 +8,76 @@ name: 'Projects', description: 'Manage your projects.' }; + + import { getModalStore, ModalSettings } from '@skeletonlabs/skeleton'; + const modalStore = getModalStore(); + + /* Client setup */ + import { client, error } from '$lib/client.ts'; + import { token } from '$lib/credentials.js'; + import * as Models from '$lib/openapi/server/models'; + import * as Api from '$lib/openapi/server/apis'; + + let at: string; + + let projects: Models.Projects; + + function update(): void { + client(at) + .apiV1ProjectsGet() + .then((v) => (projects = v)) + .catch((e: Error) => error(e)); + } + + token.subscribe(async (token: string) => { + /* Setup the token on load */ + if (!token) return; + at = token; + + update(); + setInterval(update, 5000); + }); + + function remove(name: string): void { + const modal: ModalSettings = { + type: 'confirm', + title: `Are you sure?`, + body: `Removing project "${name}" will also remove all resources owned by it.`, + response: (ok: boolean) => { + if (!ok) return; + + const parameters: Api.ApiV1ProjectsProjectNameDeleteRequest = { + projectName: name + }; + + client(at) + .apiV1ProjectsProjectNameDelete(parameters) + .catch((e: Error) => error(e)); + } + }; + + modalStore.trigger(modal); + } + + import StatusIcon from '$lib/StatusIcon.svelte'; - + + + + Create + + + {#each projects || [] as project} +
+
+ +
{project.name}
+
+ + +
+ {/each} +
diff --git a/src/routes/identity/projects/create/+page.svelte b/src/routes/identity/projects/create/+page.svelte new file mode 100644 index 0000000..ef689c2 --- /dev/null +++ b/src/routes/identity/projects/create/+page.svelte @@ -0,0 +1,77 @@ + + + + + + Let's Get Started! + +

Project Name

+ + +
+ + Confirmation + +

Create project "{project}"?

+
+
+
diff --git a/src/routes/infrastructure/clusters/create/+page.svelte b/src/routes/infrastructure/clusters/create/+page.svelte index 435fced..246c1e1 100644 --- a/src/routes/infrastructure/clusters/create/+page.svelte +++ b/src/routes/infrastructure/clusters/create/+page.svelte @@ -35,17 +35,15 @@ /* Get top-level resources required for the first step */ /* TODO: parallelize with Promise.all */ - try { - regions = await client(at).apiV1RegionsGet(); - } catch (e: Error) { - return error(e); - } - - try { - controlplanes = await client(at).apiV1ControlplanesGet(); - } catch (e: Error) { - return error(e); - } + client(at) + .apiV1RegionsGet() + .then((v) => (regions = v)) + .catch((e: Error) => error(e)); + + client(at) + .apiV1ControlplanesGet() + .then((v) => (controlplanes = v)) + .catch((e: Error) => error(e)); /* We always need a region */ region = regions[0].name; @@ -159,22 +157,10 @@ workloadPools.length > 0 && workloadPools.every((x) => x.valid) && [...new Set(workloadPools.map((x) => x.model.name))].length == workloadPools.length; - - $: console.log('length', workloadPools.length); - $: console.log( - 'valid', - workloadPools.every((x) => x.valid) - ); - $: console.log( - 'names', - [...new Set(workloadPools.map((x) => x.model.name))].length, - workloadPools.length - ); - Let's Get Started!