diff --git a/src/lib/clients/index.ts b/src/lib/clients/index.ts index f313a7d..ec4c14d 100644 --- a/src/lib/clients/index.ts +++ b/src/lib/clients/index.ts @@ -63,14 +63,14 @@ function traceContextMiddleware(): Identity.Middleware { // accessToken is a callback to get the Authorization header. Crucially this // also has a reference to the full token, so san see when the access token // has expired and refresh it using the refresh token. -async function accessToken(tokens: InternalToken): Promise { +async function accessToken(tokens: InternalToken, fetchImpl?: typeof fetch): Promise { if (!tokens) return ''; // TODO: we could get multiple API calls concurrently, at which point // we are repeating the operation, be nice if we could handle this // somehow. if (new Date(Date.now()).toJSON() > tokens.expiry) { - const discovery = await OIDC.discovery(); + const discovery = await OIDC.discovery(fetchImpl || fetch); const form = new URLSearchParams({ grant_type: 'refresh_token', @@ -119,26 +119,23 @@ async function accessToken(tokens: InternalToken): Promise { } // client gets a new initialized client with auth and any additional middlewares. -export function kubernetes( - tokens: InternalToken, - fetch?: Kubernetes.FetchAPI -): Kubernetes.DefaultApi { +export function kubernetes(tokens: InternalToken, fetchImpl?: typeof fetch): Kubernetes.DefaultApi { const config = new Kubernetes.Configuration({ basePath: env.PUBLIC_KUBERNETES_HOST, - accessToken: async () => accessToken(tokens), + accessToken: async () => accessToken(tokens, fetchImpl), middleware: [authenticationMiddleware(), traceContextMiddleware()], - fetchApi: fetch + fetchApi: fetchImpl }); return new Kubernetes.DefaultApi(config); } -export function compute(tokens: InternalToken, fetch?: Compute.FetchAPI): Compute.DefaultApi { +export function compute(tokens: InternalToken, fetchImpl?: typeof fetch): Compute.DefaultApi { const config = new Compute.Configuration({ basePath: env.PUBLIC_COMPUTE_HOST, - accessToken: async () => accessToken(tokens), + accessToken: async () => accessToken(tokens, fetchImpl), middleware: [authenticationMiddleware(), traceContextMiddleware()], - fetchApi: fetch + fetchApi: fetchImpl }); return new Compute.DefaultApi(config); @@ -146,35 +143,35 @@ export function compute(tokens: InternalToken, fetch?: Compute.FetchAPI): Comput export function application( tokens: InternalToken, - fetch?: Application.FetchAPI + fetchImpl?: typeof fetch ): Application.DefaultApi { const config = new Application.Configuration({ basePath: env.PUBLIC_APPLICATION_HOST, - accessToken: async () => accessToken(tokens), + accessToken: async () => accessToken(tokens, fetchImpl), middleware: [authenticationMiddleware(), traceContextMiddleware()], - fetchApi: fetch + fetchApi: fetchImpl }); return new Application.DefaultApi(config); } -export function identity(tokens: InternalToken, fetch?: Identity.FetchAPI): Identity.DefaultApi { +export function identity(tokens: InternalToken, fetchImpl?: typeof fetch): Identity.DefaultApi { const config = new Identity.Configuration({ basePath: env.PUBLIC_OAUTH2_ISSUER, - accessToken: async () => accessToken(tokens), + accessToken: async () => accessToken(tokens, fetchImpl), middleware: [authenticationMiddleware(), traceContextMiddleware()], - fetchApi: fetch + fetchApi: fetchImpl }); return new Identity.DefaultApi(config); } -export function region(tokens: InternalToken, fetch?: Region.FetchAPI): Region.DefaultApi { +export function region(tokens: InternalToken, fetchImpl?: typeof fetch): Region.DefaultApi { const config = new Region.Configuration({ basePath: env.PUBLIC_REGION_HOST, - accessToken: async () => accessToken(tokens), + accessToken: async () => accessToken(tokens, fetchImpl), middleware: [authenticationMiddleware(), traceContextMiddleware()], - fetchApi: fetch + fetchApi: fetchImpl }); return new Region.DefaultApi(config); diff --git a/src/lib/forms/NumberInput.svelte b/src/lib/forms/NumberInput.svelte new file mode 100644 index 0000000..2981b00 --- /dev/null +++ b/src/lib/forms/NumberInput.svelte @@ -0,0 +1,28 @@ + + +
+
+ + + {#if hint} +
{hint}
+ {/if} +
+ + +
diff --git a/src/lib/layouts/ShellList.svelte b/src/lib/layouts/ShellList.svelte index 6b3c0a1..13b613f 100644 --- a/src/lib/layouts/ShellList.svelte +++ b/src/lib/layouts/ShellList.svelte @@ -7,6 +7,6 @@ -
+
{@render children?.()}
diff --git a/src/lib/layouts/ShellListItem.svelte b/src/lib/layouts/ShellListItem.svelte index 6dd48c2..22bb0f6 100644 --- a/src/lib/layouts/ShellListItem.svelte +++ b/src/lib/layouts/ShellListItem.svelte @@ -12,11 +12,11 @@
- +
{@render main()} diff --git a/src/lib/layouts/ShellListItemHeader.svelte b/src/lib/layouts/ShellListItemHeader.svelte index 82957a9..a8c278f 100644 --- a/src/lib/layouts/ShellListItemHeader.svelte +++ b/src/lib/layouts/ShellListItemHeader.svelte @@ -30,7 +30,7 @@ {#snippet header()} -
+
{#if project}
@@ -47,7 +47,7 @@
{#if metadata?.description} -
+
{metadata.description}
{/if} diff --git a/src/lib/login/index.ts b/src/lib/login/index.ts deleted file mode 100644 index 3047b94..0000000 --- a/src/lib/login/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { browser } from '$app/environment'; - -import * as OIDC from '$lib/oidc'; -import type { InternalToken } from '$lib/oauth2'; -import { token, profile } from '$lib/credentials'; - -import Base64url from 'crypto-js/enc-base64url'; -import SHA256 from 'crypto-js/sha256'; - -export function login() { - let claims: OIDC.IDToken; - - // Get the ID token first, as we can use it, if it exists to aid login below... - profile.subscribe((value: OIDC.IDToken) => { - claims = value; - }); - - token.subscribe(async (at: InternalToken) => { - if (!browser) { - return; - } - - // Don't login when we're in the UI and we already have a token. - if (at) { - return; - } - - const oidc = await OIDC.discovery(); - - const nonceBytes = new Uint8Array(16); - crypto.getRandomValues(nonceBytes); - - const nonce = btoa(nonceBytes.toString()); - const nonceHash = SHA256(nonce).toString(Base64url); - - window.sessionStorage.setItem('oidc_nonce', nonce); - - /* Kck off the oauth2/oidc authentication code flow */ - /* TODO: it may be better to use Passport */ - let codeChallengeVerifierBytes = new Uint8Array(32); - crypto.getRandomValues(codeChallengeVerifierBytes); - - const codeChallengeVerifier = btoa(codeChallengeVerifierBytes.toString()); - const codeChallenge = SHA256(codeChallengeVerifier).toString(Base64url); - - window.sessionStorage.setItem('oauth2_code_challenge_verifier', codeChallengeVerifier); - window.sessionStorage.setItem('oauth2_location', window.location.pathname); - - // TODO: set a nonce - const query = new URLSearchParams({ - response_type: 'code', - client_id: OIDC.clientID, - redirect_uri: `${window.location.protocol}//${window.location.host}/oauth2/callback`, - code_challenge_method: 'S256', - code_challenge: codeChallenge, - scope: 'openid email profile', - nonce: nonceHash - }); - - // Set the login hint if we can as that avoids the login prompt. - if (claims && claims.email) { - query.set('login_hint', claims.email); - } - - const url = new URL(oidc.authorization_endpoint); - url.search = query.toString(); - - document.location = url.toString(); - }); -} diff --git a/src/lib/oidc/index.ts b/src/lib/oidc/index.ts index 0a330fc..54522b2 100644 --- a/src/lib/oidc/index.ts +++ b/src/lib/oidc/index.ts @@ -43,13 +43,13 @@ export type DiscoveryInfo = { }; // Return something that promises to return discovery data. -export function discovery(): Promise { +export function discovery(fetchImpl: typeof fetch): Promise { const discoveryURL = `${issuer}/.well-known/openid-configuration`; const discoveryOptions = { method: 'GET' }; - return fetch(discoveryURL, discoveryOptions) + return fetchImpl(discoveryURL, discoveryOptions) .then((response) => response.json()) .catch((error) => console.log(error)); } diff --git a/src/lib/openapi/identity/.openapi-generator/FILES b/src/lib/openapi/identity/.openapi-generator/FILES index a7e66e4..1e1dd03 100644 --- a/src/lib/openapi/identity/.openapi-generator/FILES +++ b/src/lib/openapi/identity/.openapi-generator/FILES @@ -5,6 +5,9 @@ models/Acl.ts models/AclEndpoint.ts models/AclOperation.ts models/AclScopedEndpoints.ts +models/AllocationRead.ts +models/AllocationSpec.ts +models/AllocationWrite.ts models/AuthMethod.ts models/Claim.ts models/CodeChallengeMethod.ts @@ -26,9 +29,14 @@ models/OrganizationSpec.ts models/OrganizationType.ts models/OrganizationWrite.ts models/ProjectRead.ts +models/ProjectScopedResourceReadMetadata.ts models/ProjectSpec.ts models/ProjectWrite.ts models/ProviderScope.ts +models/Quota.ts +models/QuotaDetailed.ts +models/QuotasRead.ts +models/QuotasWrite.ts models/ResourceMetadata.ts models/ResourceProvisioningStatus.ts models/ResourceReadMetadata.ts diff --git a/src/lib/openapi/identity/apis/DefaultApi.ts b/src/lib/openapi/identity/apis/DefaultApi.ts index ae17fdd..eaa533b 100644 --- a/src/lib/openapi/identity/apis/DefaultApi.ts +++ b/src/lib/openapi/identity/apis/DefaultApi.ts @@ -16,6 +16,8 @@ import * as runtime from '../runtime'; import type { Acl, + AllocationRead, + AllocationWrite, GroupRead, GroupWrite, JsonWebKeySet, @@ -28,6 +30,8 @@ import type { OrganizationWrite, ProjectRead, ProjectWrite, + QuotasRead, + QuotasWrite, RoleRead, ServiceAccountCreate, ServiceAccountRead, @@ -41,6 +45,10 @@ import type { import { AclFromJSON, AclToJSON, + AllocationReadFromJSON, + AllocationReadToJSON, + AllocationWriteFromJSON, + AllocationWriteToJSON, GroupReadFromJSON, GroupReadToJSON, GroupWriteFromJSON, @@ -65,6 +73,10 @@ import { ProjectReadToJSON, ProjectWriteFromJSON, ProjectWriteToJSON, + QuotasReadFromJSON, + QuotasReadToJSON, + QuotasWriteFromJSON, + QuotasWriteToJSON, RoleReadFromJSON, RoleReadToJSON, ServiceAccountCreateFromJSON, @@ -89,6 +101,10 @@ export interface ApiV1OrganizationsOrganizationIDAclGetRequest { organizationID: string; } +export interface ApiV1OrganizationsOrganizationIDAllocationsGetRequest { + organizationID: string; +} + export interface ApiV1OrganizationsOrganizationIDGetRequest { organizationID: string; } @@ -147,6 +163,31 @@ export interface ApiV1OrganizationsOrganizationIDProjectsPostRequest { projectWrite: ProjectWrite; } +export interface ApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDDeleteRequest { + organizationID: string; + projectID: string; + allocationID: string; +} + +export interface ApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDGetRequest { + organizationID: string; + projectID: string; + allocationID: string; +} + +export interface ApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDPutRequest { + organizationID: string; + projectID: string; + allocationID: string; + allocationWrite: AllocationWrite; +} + +export interface ApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsPostRequest { + organizationID: string; + projectID: string; + allocationWrite: AllocationWrite; +} + export interface ApiV1OrganizationsOrganizationIDProjectsProjectIDDeleteRequest { organizationID: string; projectID: string; @@ -168,6 +209,15 @@ export interface ApiV1OrganizationsOrganizationIDPutRequest { organizationWrite: OrganizationWrite; } +export interface ApiV1OrganizationsOrganizationIDQuotasGetRequest { + organizationID: string; +} + +export interface ApiV1OrganizationsOrganizationIDQuotasPutRequest { + organizationID: string; + quotasWrite: QuotasWrite; +} + export interface ApiV1OrganizationsOrganizationIDRolesGetRequest { organizationID: string; } @@ -362,6 +412,41 @@ export class DefaultApi extends runtime.BaseAPI { return await response.value(); } + /** + * Lists all allocation. + */ + async apiV1OrganizationsOrganizationIDAllocationsGetRaw(requestParameters: ApiV1OrganizationsOrganizationIDAllocationsGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { + if (requestParameters.organizationID === null || requestParameters.organizationID === undefined) { + throw new runtime.RequiredError('organizationID','Required parameter requestParameters.organizationID was null or undefined when calling apiV1OrganizationsOrganizationIDAllocationsGet.'); + } + + 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/organizations/{organizationID}/allocations`.replace(`{${"organizationID"}}`, encodeURIComponent(String(requestParameters.organizationID))), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(AllocationReadFromJSON)); + } + + /** + * Lists all allocation. + */ + async apiV1OrganizationsOrganizationIDAllocationsGet(requestParameters: ApiV1OrganizationsOrganizationIDAllocationsGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const response = await this.apiV1OrganizationsOrganizationIDAllocationsGetRaw(requestParameters, initOverrides); + return await response.value(); + } + /** * Get an organization. */ @@ -833,6 +918,187 @@ export class DefaultApi extends runtime.BaseAPI { return await response.value(); } + /** + * Updates an allocation. + */ + async apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDDeleteRaw(requestParameters: ApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.organizationID === null || requestParameters.organizationID === undefined) { + throw new runtime.RequiredError('organizationID','Required parameter requestParameters.organizationID was null or undefined when calling apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDDelete.'); + } + + if (requestParameters.projectID === null || requestParameters.projectID === undefined) { + throw new runtime.RequiredError('projectID','Required parameter requestParameters.projectID was null or undefined when calling apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDDelete.'); + } + + if (requestParameters.allocationID === null || requestParameters.allocationID === undefined) { + throw new runtime.RequiredError('allocationID','Required parameter requestParameters.allocationID was null or undefined when calling apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDDelete.'); + } + + 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/organizations/{organizationID}/projects/{projectID}/allocations/{allocationID}`.replace(`{${"organizationID"}}`, encodeURIComponent(String(requestParameters.organizationID))).replace(`{${"projectID"}}`, encodeURIComponent(String(requestParameters.projectID))).replace(`{${"allocationID"}}`, encodeURIComponent(String(requestParameters.allocationID))), + method: 'DELETE', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.VoidApiResponse(response); + } + + /** + * Updates an allocation. + */ + async apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDDelete(requestParameters: ApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDDeleteRaw(requestParameters, initOverrides); + } + + /** + * Gets an allocation. + */ + async apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDGetRaw(requestParameters: ApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.organizationID === null || requestParameters.organizationID === undefined) { + throw new runtime.RequiredError('organizationID','Required parameter requestParameters.organizationID was null or undefined when calling apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDGet.'); + } + + if (requestParameters.projectID === null || requestParameters.projectID === undefined) { + throw new runtime.RequiredError('projectID','Required parameter requestParameters.projectID was null or undefined when calling apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDGet.'); + } + + if (requestParameters.allocationID === null || requestParameters.allocationID === undefined) { + throw new runtime.RequiredError('allocationID','Required parameter requestParameters.allocationID was null or undefined when calling apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDGet.'); + } + + 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/organizations/{organizationID}/projects/{projectID}/allocations/{allocationID}`.replace(`{${"organizationID"}}`, encodeURIComponent(String(requestParameters.organizationID))).replace(`{${"projectID"}}`, encodeURIComponent(String(requestParameters.projectID))).replace(`{${"allocationID"}}`, encodeURIComponent(String(requestParameters.allocationID))), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => AllocationReadFromJSON(jsonValue)); + } + + /** + * Gets an allocation. + */ + async apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDGet(requestParameters: ApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDGetRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Updates an allocation. + */ + async apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDPutRaw(requestParameters: ApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDPutRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.organizationID === null || requestParameters.organizationID === undefined) { + throw new runtime.RequiredError('organizationID','Required parameter requestParameters.organizationID was null or undefined when calling apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDPut.'); + } + + if (requestParameters.projectID === null || requestParameters.projectID === undefined) { + throw new runtime.RequiredError('projectID','Required parameter requestParameters.projectID was null or undefined when calling apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDPut.'); + } + + if (requestParameters.allocationID === null || requestParameters.allocationID === undefined) { + throw new runtime.RequiredError('allocationID','Required parameter requestParameters.allocationID was null or undefined when calling apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDPut.'); + } + + if (requestParameters.allocationWrite === null || requestParameters.allocationWrite === undefined) { + throw new runtime.RequiredError('allocationWrite','Required parameter requestParameters.allocationWrite was null or undefined when calling apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDPut.'); + } + + 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/organizations/{organizationID}/projects/{projectID}/allocations/{allocationID}`.replace(`{${"organizationID"}}`, encodeURIComponent(String(requestParameters.organizationID))).replace(`{${"projectID"}}`, encodeURIComponent(String(requestParameters.projectID))).replace(`{${"allocationID"}}`, encodeURIComponent(String(requestParameters.allocationID))), + method: 'PUT', + headers: headerParameters, + query: queryParameters, + body: AllocationWriteToJSON(requestParameters.allocationWrite), + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => AllocationReadFromJSON(jsonValue)); + } + + /** + * Updates an allocation. + */ + async apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDPut(requestParameters: ApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDPutRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDPutRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Creates an allocation. + */ + async apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsPostRaw(requestParameters: ApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.organizationID === null || requestParameters.organizationID === undefined) { + throw new runtime.RequiredError('organizationID','Required parameter requestParameters.organizationID was null or undefined when calling apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsPost.'); + } + + if (requestParameters.projectID === null || requestParameters.projectID === undefined) { + throw new runtime.RequiredError('projectID','Required parameter requestParameters.projectID was null or undefined when calling apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsPost.'); + } + + if (requestParameters.allocationWrite === null || requestParameters.allocationWrite === undefined) { + throw new runtime.RequiredError('allocationWrite','Required parameter requestParameters.allocationWrite was null or undefined when calling apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsPost.'); + } + + 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/organizations/{organizationID}/projects/{projectID}/allocations`.replace(`{${"organizationID"}}`, encodeURIComponent(String(requestParameters.organizationID))).replace(`{${"projectID"}}`, encodeURIComponent(String(requestParameters.projectID))), + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: AllocationWriteToJSON(requestParameters.allocationWrite), + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => AllocationReadFromJSON(jsonValue)); + } + + /** + * Creates an allocation. + */ + async apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsPost(requestParameters: ApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.apiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsPostRaw(requestParameters, initOverrides); + return await response.value(); + } + /** * Deletes the project associated with the authenticated user\'s scoped authorisation token. This is a cascading operation and will delete all contained cluster managers and clusters. */ @@ -996,6 +1262,83 @@ export class DefaultApi extends runtime.BaseAPI { await this.apiV1OrganizationsOrganizationIDPutRaw(requestParameters, initOverrides); } + /** + * Gets quotas for the organization. + */ + async apiV1OrganizationsOrganizationIDQuotasGetRaw(requestParameters: ApiV1OrganizationsOrganizationIDQuotasGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.organizationID === null || requestParameters.organizationID === undefined) { + throw new runtime.RequiredError('organizationID','Required parameter requestParameters.organizationID was null or undefined when calling apiV1OrganizationsOrganizationIDQuotasGet.'); + } + + 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/organizations/{organizationID}/quotas`.replace(`{${"organizationID"}}`, encodeURIComponent(String(requestParameters.organizationID))), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => QuotasReadFromJSON(jsonValue)); + } + + /** + * Gets quotas for the organization. + */ + async apiV1OrganizationsOrganizationIDQuotasGet(requestParameters: ApiV1OrganizationsOrganizationIDQuotasGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.apiV1OrganizationsOrganizationIDQuotasGetRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Sets quotas for the organization. + */ + async apiV1OrganizationsOrganizationIDQuotasPutRaw(requestParameters: ApiV1OrganizationsOrganizationIDQuotasPutRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.organizationID === null || requestParameters.organizationID === undefined) { + throw new runtime.RequiredError('organizationID','Required parameter requestParameters.organizationID was null or undefined when calling apiV1OrganizationsOrganizationIDQuotasPut.'); + } + + if (requestParameters.quotasWrite === null || requestParameters.quotasWrite === undefined) { + throw new runtime.RequiredError('quotasWrite','Required parameter requestParameters.quotasWrite was null or undefined when calling apiV1OrganizationsOrganizationIDQuotasPut.'); + } + + 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/organizations/{organizationID}/quotas`.replace(`{${"organizationID"}}`, encodeURIComponent(String(requestParameters.organizationID))), + method: 'PUT', + headers: headerParameters, + query: queryParameters, + body: QuotasWriteToJSON(requestParameters.quotasWrite), + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => QuotasReadFromJSON(jsonValue)); + } + + /** + * Sets quotas for the organization. + */ + async apiV1OrganizationsOrganizationIDQuotasPut(requestParameters: ApiV1OrganizationsOrganizationIDQuotasPutRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.apiV1OrganizationsOrganizationIDQuotasPutRaw(requestParameters, initOverrides); + return await response.value(); + } + /** * Returns roles that can be used by the organization. */ diff --git a/src/lib/openapi/identity/models/AllocationRead.ts b/src/lib/openapi/identity/models/AllocationRead.ts new file mode 100644 index 0000000..12aacdb --- /dev/null +++ b/src/lib/openapi/identity/models/AllocationRead.ts @@ -0,0 +1,88 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Unikorn Identity API + * The Unikorn Identity API provides an OIDC compliant interface for use with all Unikorn services and proxies. As it\'s intended use is for multi-tenant cloud deployments it acts as an aggregation layer for other 3rd party OIDC services, dispatching login requests to the required OIDC backend. + * + * The version of the OpenAPI document: 0.1.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 { AllocationSpec } from './AllocationSpec'; +import { + AllocationSpecFromJSON, + AllocationSpecFromJSONTyped, + AllocationSpecToJSON, +} from './AllocationSpec'; +import type { ProjectScopedResourceReadMetadata } from './ProjectScopedResourceReadMetadata'; +import { + ProjectScopedResourceReadMetadataFromJSON, + ProjectScopedResourceReadMetadataFromJSONTyped, + ProjectScopedResourceReadMetadataToJSON, +} from './ProjectScopedResourceReadMetadata'; + +/** + * An allocation of resources. + * @export + * @interface AllocationRead + */ +export interface AllocationRead { + /** + * + * @type {ProjectScopedResourceReadMetadata} + * @memberof AllocationRead + */ + metadata: ProjectScopedResourceReadMetadata; + /** + * + * @type {AllocationSpec} + * @memberof AllocationRead + */ + spec: AllocationSpec; +} + +/** + * Check if a given object implements the AllocationRead interface. + */ +export function instanceOfAllocationRead(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "metadata" in value; + isInstance = isInstance && "spec" in value; + + return isInstance; +} + +export function AllocationReadFromJSON(json: any): AllocationRead { + return AllocationReadFromJSONTyped(json, false); +} + +export function AllocationReadFromJSONTyped(json: any, ignoreDiscriminator: boolean): AllocationRead { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'metadata': ProjectScopedResourceReadMetadataFromJSON(json['metadata']), + 'spec': AllocationSpecFromJSON(json['spec']), + }; +} + +export function AllocationReadToJSON(value?: AllocationRead | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'metadata': ProjectScopedResourceReadMetadataToJSON(value.metadata), + 'spec': AllocationSpecToJSON(value.spec), + }; +} + diff --git a/src/lib/openapi/identity/models/AllocationSpec.ts b/src/lib/openapi/identity/models/AllocationSpec.ts new file mode 100644 index 0000000..19879f8 --- /dev/null +++ b/src/lib/openapi/identity/models/AllocationSpec.ts @@ -0,0 +1,91 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Unikorn Identity API + * The Unikorn Identity API provides an OIDC compliant interface for use with all Unikorn services and proxies. As it\'s intended use is for multi-tenant cloud deployments it acts as an aggregation layer for other 3rd party OIDC services, dispatching login requests to the required OIDC backend. + * + * The version of the OpenAPI document: 0.1.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 { QuotaDetailed } from './QuotaDetailed'; +import { + QuotaDetailedFromJSON, + QuotaDetailedFromJSONTyped, + QuotaDetailedToJSON, +} from './QuotaDetailed'; + +/** + * A set of resource allocations. + * @export + * @interface AllocationSpec + */ +export interface AllocationSpec { + /** + * The resource kind that owns this allocation. + * @type {string} + * @memberof AllocationSpec + */ + kind: string; + /** + * The resource ID that owns this allocation. + * @type {string} + * @memberof AllocationSpec + */ + id: string; + /** + * A list of quotas. + * @type {Array} + * @memberof AllocationSpec + */ + allocations: Array; +} + +/** + * Check if a given object implements the AllocationSpec interface. + */ +export function instanceOfAllocationSpec(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "kind" in value; + isInstance = isInstance && "id" in value; + isInstance = isInstance && "allocations" in value; + + return isInstance; +} + +export function AllocationSpecFromJSON(json: any): AllocationSpec { + return AllocationSpecFromJSONTyped(json, false); +} + +export function AllocationSpecFromJSONTyped(json: any, ignoreDiscriminator: boolean): AllocationSpec { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'kind': json['kind'], + 'id': json['id'], + 'allocations': ((json['allocations'] as Array).map(QuotaDetailedFromJSON)), + }; +} + +export function AllocationSpecToJSON(value?: AllocationSpec | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'kind': value.kind, + 'id': value.id, + 'allocations': ((value.allocations as Array).map(QuotaDetailedToJSON)), + }; +} + diff --git a/src/lib/openapi/identity/models/AllocationWrite.ts b/src/lib/openapi/identity/models/AllocationWrite.ts new file mode 100644 index 0000000..3268a82 --- /dev/null +++ b/src/lib/openapi/identity/models/AllocationWrite.ts @@ -0,0 +1,88 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Unikorn Identity API + * The Unikorn Identity API provides an OIDC compliant interface for use with all Unikorn services and proxies. As it\'s intended use is for multi-tenant cloud deployments it acts as an aggregation layer for other 3rd party OIDC services, dispatching login requests to the required OIDC backend. + * + * The version of the OpenAPI document: 0.1.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 { AllocationSpec } from './AllocationSpec'; +import { + AllocationSpecFromJSON, + AllocationSpecFromJSONTyped, + AllocationSpecToJSON, +} from './AllocationSpec'; +import type { ResourceWriteMetadata } from './ResourceWriteMetadata'; +import { + ResourceWriteMetadataFromJSON, + ResourceWriteMetadataFromJSONTyped, + ResourceWriteMetadataToJSON, +} from './ResourceWriteMetadata'; + +/** + * An allocation of resources. + * @export + * @interface AllocationWrite + */ +export interface AllocationWrite { + /** + * + * @type {ResourceWriteMetadata} + * @memberof AllocationWrite + */ + metadata: ResourceWriteMetadata; + /** + * + * @type {AllocationSpec} + * @memberof AllocationWrite + */ + spec: AllocationSpec; +} + +/** + * Check if a given object implements the AllocationWrite interface. + */ +export function instanceOfAllocationWrite(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "metadata" in value; + isInstance = isInstance && "spec" in value; + + return isInstance; +} + +export function AllocationWriteFromJSON(json: any): AllocationWrite { + return AllocationWriteFromJSONTyped(json, false); +} + +export function AllocationWriteFromJSONTyped(json: any, ignoreDiscriminator: boolean): AllocationWrite { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'metadata': ResourceWriteMetadataFromJSON(json['metadata']), + 'spec': AllocationSpecFromJSON(json['spec']), + }; +} + +export function AllocationWriteToJSON(value?: AllocationWrite | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'metadata': ResourceWriteMetadataToJSON(value.metadata), + 'spec': AllocationSpecToJSON(value.spec), + }; +} + diff --git a/src/lib/openapi/identity/models/GroupSpec.ts b/src/lib/openapi/identity/models/GroupSpec.ts index 8c66e28..f43010d 100644 --- a/src/lib/openapi/identity/models/GroupSpec.ts +++ b/src/lib/openapi/identity/models/GroupSpec.ts @@ -25,6 +25,12 @@ export interface GroupSpec { * @memberof GroupSpec */ userIDs?: Array; + /** + * A list of strings. + * @type {Array} + * @memberof GroupSpec + */ + serviceAccountIDs?: Array; /** * A list of strings. * @type {Array} @@ -54,6 +60,7 @@ export function GroupSpecFromJSONTyped(json: any, ignoreDiscriminator: boolean): return { 'userIDs': !exists(json, 'userIDs') ? undefined : json['userIDs'], + 'serviceAccountIDs': !exists(json, 'serviceAccountIDs') ? undefined : json['serviceAccountIDs'], 'roleIDs': json['roleIDs'], }; } @@ -68,6 +75,7 @@ export function GroupSpecToJSON(value?: GroupSpec | null): any { return { 'userIDs': value.userIDs, + 'serviceAccountIDs': value.serviceAccountIDs, 'roleIDs': value.roleIDs, }; } diff --git a/src/lib/openapi/identity/models/ProjectScopedResourceReadMetadata.ts b/src/lib/openapi/identity/models/ProjectScopedResourceReadMetadata.ts new file mode 100644 index 0000000..c228654 --- /dev/null +++ b/src/lib/openapi/identity/models/ProjectScopedResourceReadMetadata.ts @@ -0,0 +1,173 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Unikorn Identity API + * The Unikorn Identity API provides an OIDC compliant interface for use with all Unikorn services and proxies. As it\'s intended use is for multi-tenant cloud deployments it acts as an aggregation layer for other 3rd party OIDC services, dispatching login requests to the required OIDC backend. + * + * The version of the OpenAPI document: 0.1.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 { ResourceProvisioningStatus } from './ResourceProvisioningStatus'; +import { + ResourceProvisioningStatusFromJSON, + ResourceProvisioningStatusFromJSONTyped, + ResourceProvisioningStatusToJSON, +} from './ResourceProvisioningStatus'; +import type { Tag } from './Tag'; +import { + TagFromJSON, + TagFromJSONTyped, + TagToJSON, +} from './Tag'; + +/** + * + * @export + * @interface ProjectScopedResourceReadMetadata + */ +export interface ProjectScopedResourceReadMetadata { + /** + * A valid Kubenetes label value, typically used for resource names that can be + * indexed in the database. + * @type {string} + * @memberof ProjectScopedResourceReadMetadata + */ + name: string; + /** + * The resource description, this optionally augments the name with more context. + * @type {string} + * @memberof ProjectScopedResourceReadMetadata + */ + description?: string; + /** + * A list of tags. + * @type {Array} + * @memberof ProjectScopedResourceReadMetadata + */ + tags?: Array; + /** + * The unique resource ID. + * @type {string} + * @memberof ProjectScopedResourceReadMetadata + */ + id: string; + /** + * The time the resource was created. + * @type {Date} + * @memberof ProjectScopedResourceReadMetadata + */ + creationTime: Date; + /** + * The user who created the resource. + * @type {string} + * @memberof ProjectScopedResourceReadMetadata + */ + createdBy?: string; + /** + * The time a resource was updated. + * @type {Date} + * @memberof ProjectScopedResourceReadMetadata + */ + modifiedTime?: Date; + /** + * The user who updated the resource. + * @type {string} + * @memberof ProjectScopedResourceReadMetadata + */ + modifiedBy?: string; + /** + * The time the resource was deleted. + * @type {Date} + * @memberof ProjectScopedResourceReadMetadata + */ + deletionTime?: Date; + /** + * + * @type {ResourceProvisioningStatus} + * @memberof ProjectScopedResourceReadMetadata + */ + provisioningStatus: ResourceProvisioningStatus; + /** + * The organization identifier the resource belongs to. + * @type {string} + * @memberof ProjectScopedResourceReadMetadata + */ + organizationId: string; + /** + * The project identifier the resource belongs to. + * @type {string} + * @memberof ProjectScopedResourceReadMetadata + */ + projectId: string; +} + +/** + * Check if a given object implements the ProjectScopedResourceReadMetadata interface. + */ +export function instanceOfProjectScopedResourceReadMetadata(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "name" in value; + isInstance = isInstance && "id" in value; + isInstance = isInstance && "creationTime" in value; + isInstance = isInstance && "provisioningStatus" in value; + isInstance = isInstance && "organizationId" in value; + isInstance = isInstance && "projectId" in value; + + return isInstance; +} + +export function ProjectScopedResourceReadMetadataFromJSON(json: any): ProjectScopedResourceReadMetadata { + return ProjectScopedResourceReadMetadataFromJSONTyped(json, false); +} + +export function ProjectScopedResourceReadMetadataFromJSONTyped(json: any, ignoreDiscriminator: boolean): ProjectScopedResourceReadMetadata { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'name': json['name'], + 'description': !exists(json, 'description') ? undefined : json['description'], + 'tags': !exists(json, 'tags') ? undefined : ((json['tags'] as Array).map(TagFromJSON)), + 'id': json['id'], + 'creationTime': (new Date(json['creationTime'])), + 'createdBy': !exists(json, 'createdBy') ? undefined : json['createdBy'], + 'modifiedTime': !exists(json, 'modifiedTime') ? undefined : (new Date(json['modifiedTime'])), + 'modifiedBy': !exists(json, 'modifiedBy') ? undefined : json['modifiedBy'], + 'deletionTime': !exists(json, 'deletionTime') ? undefined : (new Date(json['deletionTime'])), + 'provisioningStatus': ResourceProvisioningStatusFromJSON(json['provisioningStatus']), + 'organizationId': json['organizationId'], + 'projectId': json['projectId'], + }; +} + +export function ProjectScopedResourceReadMetadataToJSON(value?: ProjectScopedResourceReadMetadata | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'name': value.name, + 'description': value.description, + 'tags': value.tags === undefined ? undefined : ((value.tags as Array).map(TagToJSON)), + 'id': value.id, + 'creationTime': (value.creationTime.toISOString()), + 'createdBy': value.createdBy, + 'modifiedTime': value.modifiedTime === undefined ? undefined : (value.modifiedTime.toISOString()), + 'modifiedBy': value.modifiedBy, + 'deletionTime': value.deletionTime === undefined ? undefined : (value.deletionTime.toISOString()), + 'provisioningStatus': ResourceProvisioningStatusToJSON(value.provisioningStatus), + 'organizationId': value.organizationId, + 'projectId': value.projectId, + }; +} + diff --git a/src/lib/openapi/identity/models/Quota.ts b/src/lib/openapi/identity/models/Quota.ts new file mode 100644 index 0000000..5cf14e9 --- /dev/null +++ b/src/lib/openapi/identity/models/Quota.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Unikorn Identity API + * The Unikorn Identity API provides an OIDC compliant interface for use with all Unikorn services and proxies. As it\'s intended use is for multi-tenant cloud deployments it acts as an aggregation layer for other 3rd party OIDC services, dispatching login requests to the required OIDC backend. + * + * The version of the OpenAPI document: 0.1.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'; +/** + * A single quota. + * @export + * @interface Quota + */ +export interface Quota { + /** + * The kind of resource. + * @type {string} + * @memberof Quota + */ + kind: string; + /** + * Tha amount of that resource. + * @type {number} + * @memberof Quota + */ + quantity: number; +} + +/** + * Check if a given object implements the Quota interface. + */ +export function instanceOfQuota(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "kind" in value; + isInstance = isInstance && "quantity" in value; + + return isInstance; +} + +export function QuotaFromJSON(json: any): Quota { + return QuotaFromJSONTyped(json, false); +} + +export function QuotaFromJSONTyped(json: any, ignoreDiscriminator: boolean): Quota { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'kind': json['kind'], + 'quantity': json['quantity'], + }; +} + +export function QuotaToJSON(value?: Quota | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'kind': value.kind, + 'quantity': value.quantity, + }; +} + diff --git a/src/lib/openapi/identity/models/QuotaDetailed.ts b/src/lib/openapi/identity/models/QuotaDetailed.ts new file mode 100644 index 0000000..78e78ee --- /dev/null +++ b/src/lib/openapi/identity/models/QuotaDetailed.ts @@ -0,0 +1,84 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Unikorn Identity API + * The Unikorn Identity API provides an OIDC compliant interface for use with all Unikorn services and proxies. As it\'s intended use is for multi-tenant cloud deployments it acts as an aggregation layer for other 3rd party OIDC services, dispatching login requests to the required OIDC backend. + * + * The version of the OpenAPI document: 0.1.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'; +/** + * A single quota but taking into account dynamic allocation. + * @export + * @interface QuotaDetailed + */ +export interface QuotaDetailed { + /** + * The kind of resource. + * @type {string} + * @memberof QuotaDetailed + */ + kind: string; + /** + * Tha amount of that resource always in use. + * @type {number} + * @memberof QuotaDetailed + */ + committed: number; + /** + * The amount of that resource that may be used e.g. autoscaled. + * @type {number} + * @memberof QuotaDetailed + */ + reserved: number; +} + +/** + * Check if a given object implements the QuotaDetailed interface. + */ +export function instanceOfQuotaDetailed(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "kind" in value; + isInstance = isInstance && "committed" in value; + isInstance = isInstance && "reserved" in value; + + return isInstance; +} + +export function QuotaDetailedFromJSON(json: any): QuotaDetailed { + return QuotaDetailedFromJSONTyped(json, false); +} + +export function QuotaDetailedFromJSONTyped(json: any, ignoreDiscriminator: boolean): QuotaDetailed { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'kind': json['kind'], + 'committed': json['committed'], + 'reserved': json['reserved'], + }; +} + +export function QuotaDetailedToJSON(value?: QuotaDetailed | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'kind': value.kind, + 'committed': value.committed, + 'reserved': value.reserved, + }; +} + diff --git a/src/lib/openapi/identity/models/QuotasRead.ts b/src/lib/openapi/identity/models/QuotasRead.ts new file mode 100644 index 0000000..cb5e6fe --- /dev/null +++ b/src/lib/openapi/identity/models/QuotasRead.ts @@ -0,0 +1,97 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Unikorn Identity API + * The Unikorn Identity API provides an OIDC compliant interface for use with all Unikorn services and proxies. As it\'s intended use is for multi-tenant cloud deployments it acts as an aggregation layer for other 3rd party OIDC services, dispatching login requests to the required OIDC backend. + * + * The version of the OpenAPI document: 0.1.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 { Quota } from './Quota'; +import { + QuotaFromJSON, + QuotaFromJSONTyped, + QuotaToJSON, +} from './Quota'; +import type { QuotaDetailed } from './QuotaDetailed'; +import { + QuotaDetailedFromJSON, + QuotaDetailedFromJSONTyped, + QuotaDetailedToJSON, +} from './QuotaDetailed'; + +/** + * A list of quotas, free resources and a detailed view of allocated ones. + * @export + * @interface QuotasRead + */ +export interface QuotasRead { + /** + * A list of quotas. + * @type {Array} + * @memberof QuotasRead + */ + capacity: Array; + /** + * A list of quotas. + * @type {Array} + * @memberof QuotasRead + */ + free: Array; + /** + * A list of quotas. + * @type {Array} + * @memberof QuotasRead + */ + allocated: Array; +} + +/** + * Check if a given object implements the QuotasRead interface. + */ +export function instanceOfQuotasRead(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "capacity" in value; + isInstance = isInstance && "free" in value; + isInstance = isInstance && "allocated" in value; + + return isInstance; +} + +export function QuotasReadFromJSON(json: any): QuotasRead { + return QuotasReadFromJSONTyped(json, false); +} + +export function QuotasReadFromJSONTyped(json: any, ignoreDiscriminator: boolean): QuotasRead { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'capacity': ((json['capacity'] as Array).map(QuotaFromJSON)), + 'free': ((json['free'] as Array).map(QuotaFromJSON)), + 'allocated': ((json['allocated'] as Array).map(QuotaDetailedFromJSON)), + }; +} + +export function QuotasReadToJSON(value?: QuotasRead | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'capacity': ((value.capacity as Array).map(QuotaToJSON)), + 'free': ((value.free as Array).map(QuotaToJSON)), + 'allocated': ((value.allocated as Array).map(QuotaDetailedToJSON)), + }; +} + diff --git a/src/lib/openapi/identity/models/QuotasWrite.ts b/src/lib/openapi/identity/models/QuotasWrite.ts new file mode 100644 index 0000000..80a350f --- /dev/null +++ b/src/lib/openapi/identity/models/QuotasWrite.ts @@ -0,0 +1,73 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Unikorn Identity API + * The Unikorn Identity API provides an OIDC compliant interface for use with all Unikorn services and proxies. As it\'s intended use is for multi-tenant cloud deployments it acts as an aggregation layer for other 3rd party OIDC services, dispatching login requests to the required OIDC backend. + * + * The version of the OpenAPI document: 0.1.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 { Quota } from './Quota'; +import { + QuotaFromJSON, + QuotaFromJSONTyped, + QuotaToJSON, +} from './Quota'; + +/** + * A set of quotas to apply. + * @export + * @interface QuotasWrite + */ +export interface QuotasWrite { + /** + * A list of quotas. + * @type {Array} + * @memberof QuotasWrite + */ + capacity: Array; +} + +/** + * Check if a given object implements the QuotasWrite interface. + */ +export function instanceOfQuotasWrite(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "capacity" in value; + + return isInstance; +} + +export function QuotasWriteFromJSON(json: any): QuotasWrite { + return QuotasWriteFromJSONTyped(json, false); +} + +export function QuotasWriteFromJSONTyped(json: any, ignoreDiscriminator: boolean): QuotasWrite { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'capacity': ((json['capacity'] as Array).map(QuotaFromJSON)), + }; +} + +export function QuotasWriteToJSON(value?: QuotasWrite | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'capacity': ((value.capacity as Array).map(QuotaToJSON)), + }; +} + diff --git a/src/lib/openapi/identity/models/index.ts b/src/lib/openapi/identity/models/index.ts index 1e909f7..344f666 100644 --- a/src/lib/openapi/identity/models/index.ts +++ b/src/lib/openapi/identity/models/index.ts @@ -4,6 +4,9 @@ export * from './Acl'; export * from './AclEndpoint'; export * from './AclOperation'; export * from './AclScopedEndpoints'; +export * from './AllocationRead'; +export * from './AllocationSpec'; +export * from './AllocationWrite'; export * from './AuthMethod'; export * from './Claim'; export * from './CodeChallengeMethod'; @@ -25,9 +28,14 @@ export * from './OrganizationSpec'; export * from './OrganizationType'; export * from './OrganizationWrite'; export * from './ProjectRead'; +export * from './ProjectScopedResourceReadMetadata'; export * from './ProjectSpec'; export * from './ProjectWrite'; export * from './ProviderScope'; +export * from './Quota'; +export * from './QuotaDetailed'; +export * from './QuotasRead'; +export * from './QuotasWrite'; export * from './ResourceMetadata'; export * from './ResourceProvisioningStatus'; export * from './ResourceReadMetadata'; diff --git a/src/lib/shell/Shell.svelte b/src/lib/shell/Shell.svelte index 4234f85..832481d 100644 --- a/src/lib/shell/Shell.svelte +++ b/src/lib/shell/Shell.svelte @@ -16,7 +16,7 @@
-
+
{@render sidebarLeft()}
diff --git a/src/lib/shell/SideBar.svelte b/src/lib/shell/SideBar.svelte index 7a73b44..f6c8713 100644 --- a/src/lib/shell/SideBar.svelte +++ b/src/lib/shell/SideBar.svelte @@ -28,6 +28,14 @@ type NavItems = Array<{ label: string; href: string }>; + const navStatic: Array<{ href: string; title: string; icon: string }> = [ + { + href: '/', + title: 'Dashboard', + icon: 'mdi:gauge' + } + ]; + const nav: Array<{ base: string; title: string; icon: string; items: NavItems }> = [ { base: '/identity', @@ -36,6 +44,7 @@ items: [ { label: 'Organization', href: 'organizations' }, { label: 'OAuth2 Providers', href: 'oauth2providers' }, + { label: 'Quotas', href: 'quotas' }, { label: 'Users', href: 'users' }, { label: 'Service Accounts', href: 'serviceaccounts' }, { label: 'Groups', href: 'groups' }, @@ -70,7 +79,7 @@ let activeCategory = $derived(nav.find((x) => $page.url.pathname.startsWith(x.base))); let activeItem = $derived( - activeCategory?.items.find((x) => + activeCategory?.items?.find((x) => $page.url.pathname.startsWith(activeCategory.base + '/' + x.href) ) ); @@ -89,7 +98,7 @@
-
+
Organization
@@ -107,8 +116,16 @@
-
-
Main Menu
+
+
Main Menu
+ + {#each navStatic as entry} + + + {entry.title} + + {/each} {#each nav as entry} diff --git a/src/routes/(shell)/+layout.ts b/src/routes/(shell)/+layout.ts index be95a29..11aea9f 100644 --- a/src/routes/(shell)/+layout.ts +++ b/src/routes/(shell)/+layout.ts @@ -24,7 +24,7 @@ export const load: LayoutLoad = async ({ fetch, depends }) => { const profile = getSessionData('profile'); if (!token) { - const oidc = await OIDC.discovery(); + const oidc = await OIDC.discovery(fetch); const nonceBytes = new Uint8Array(16); crypto.getRandomValues(nonceBytes); diff --git a/src/routes/(shell)/+page.svelte b/src/routes/(shell)/+page.svelte index 1f788a8..4b6bbe5 100644 --- a/src/routes/(shell)/+page.svelte +++ b/src/routes/(shell)/+page.svelte @@ -3,15 +3,79 @@ let { data }: { data: PageData } = $props(); - import type { ShellPageSettings } from '$lib/layouts/types.ts'; + import { ProgressRadial } from '@skeletonlabs/skeleton'; + //import { Chart, Svg, Axis, Bars, Group, LinearGradient, Arc, Text } from 'layerchart'; + import type { ShellPageSettings } from '$lib/layouts/types.ts'; import ShellPage from '$lib/layouts/ShellPage.svelte'; + import ShellSection from '$lib/layouts/ShellSection.svelte'; const settings: ShellPageSettings = { feature: 'None', name: 'Dashboard', description: 'Summary of your resources.' }; + + // TODO: move into the API. + type QuotaMetadata = { + title: string; + description: string; + }; + + const metadata: Record = { + clusters: { + title: 'Clusters', + description: 'All cluster types e.g. Kubernetes, compute, etc.' + }, + servers: { + title: 'Servers', + description: 'All servers and virtual machines.' + }, + gpus: { + title: 'GPUs', + description: 'General purpose GPUs for AI workloads.' + } + }; - + + +
+ {#each data.quotas.capacity as quota, i} + {@const used = data.quotas.allocated[i].committed + data.quotas.allocated[i].reserved} + {@const utilization = (used * 100) / quota.quantity} + +
+
+
{metadata[quota.kind].title}
+
{metadata[quota.kind].description}
+
+
+ + {Math.round(utilization)}% + + +
+
Total
+
{quota.quantity}
+
Free
+
{data.quotas.free[i].quantity}
+
+
Committed
+
{data.quotas.allocated[i].committed}
+
Reserved
+
{data.quotas.allocated[i].reserved}
+
+
+
+
+ {/each} +
+
+
diff --git a/src/routes/(shell)/+page.ts b/src/routes/(shell)/+page.ts new file mode 100644 index 0000000..c70608b --- /dev/null +++ b/src/routes/(shell)/+page.ts @@ -0,0 +1,34 @@ +export const ssr = false; + +import type { PageLoad } from './$types'; + +import * as RBAC from '$lib/rbac'; +import * as Identity from '$lib/openapi/identity'; +import * as Clients from '$lib/clients'; + +export const load: PageLoad = async ({ fetch, parent }) => { + const scopes: Array = [ + { + endpoint: 'identity:quotas', + operation: Identity.AclOperation.Read + } + ]; + + const { token, organizationID, acl, allowed } = await parent(); + + if (!allowed || !RBAC.organizationScopesAllowed(acl, organizationID, scopes)) { + return { + quotas: {} as Identity.QuotasRead, + allowed: false + }; + } + + const quotas = Clients.identity(token, fetch).apiV1OrganizationsOrganizationIDQuotasGet({ + organizationID: organizationID + }); + + return { + quotas: await quotas, + allowed: true + }; +}; diff --git a/src/routes/(shell)/identity/groups/create/+page.svelte b/src/routes/(shell)/identity/groups/create/+page.svelte index 772a879..c894b17 100644 --- a/src/routes/(shell)/identity/groups/create/+page.svelte +++ b/src/routes/(shell)/identity/groups/create/+page.svelte @@ -27,7 +27,8 @@ }, spec: { roleIDs: [], - userIDs: [] + userIDs: [], + serviceAccountIDs: [] } }); @@ -75,6 +76,20 @@ {/if} + + {#if resource.spec.serviceAccountIDs} + + {#each data.serviceAccounts as serviceAccount} + + {/each} + + {/if} + +
- {/if} + {/if} + + +
+
diff --git a/src/routes/(shell)/identity/groups/view/[id]/+page.ts b/src/routes/(shell)/identity/groups/view/[id]/+page.ts index 0cca60f..606e692 100644 --- a/src/routes/(shell)/identity/groups/view/[id]/+page.ts +++ b/src/routes/(shell)/identity/groups/view/[id]/+page.ts @@ -1,12 +1,13 @@ export const ssr = false; import type { PageLoad } from './$types'; +import { error } from '@sveltejs/kit'; import * as RBAC from '$lib/rbac'; import * as Identity from '$lib/openapi/identity'; import * as Clients from '$lib/clients'; -export const load: PageLoad = async ({ fetch, parent }) => { +export const load: PageLoad = async ({ fetch, parent, params }) => { const scopes: Array = [ { endpoint: 'identity:groups', @@ -23,16 +24,23 @@ export const load: PageLoad = async ({ fetch, parent }) => { } ]; - const { token, organizationID, acl, allowed } = await parent(); + const { token, organizationID, acl, allowed, groups } = await parent(); if (!allowed || !RBAC.organizationScopesAllowed(acl, organizationID, scopes)) { return { + group: {} as Identity.GroupRead, roles: [] as Array, users: [] as Array, + serviceAccounts: [] as Array, allowed: false }; } + const group = groups.find((x) => x.metadata.id == params['id']); + if (!group) { + error(404, 'group not found'); + } + const roles = Clients.identity(token, fetch).apiV1OrganizationsOrganizationIDRolesGet({ organizationID: organizationID }); @@ -41,9 +49,18 @@ export const load: PageLoad = async ({ fetch, parent }) => { organizationID: organizationID }); + const serviceAccounts = Clients.identity( + token, + fetch + ).apiV1OrganizationsOrganizationIDServiceaccountsGet({ + organizationID: organizationID + }); + return { + group: group, roles: await roles, users: await users, + serviceAccounts: await serviceAccounts, allowed: true }; }; diff --git a/src/routes/(shell)/identity/organizations/+page.svelte b/src/routes/(shell)/identity/organizations/+page.svelte index 13100c8..c34876e 100644 --- a/src/routes/(shell)/identity/organizations/+page.svelte +++ b/src/routes/(shell)/identity/organizations/+page.svelte @@ -23,10 +23,12 @@ description: 'Manage your organization.' }; - let organization = $derived(data.organizations.find((x) => x.metadata.id == data.organizationID)); - function submit() { - if (!organization) return; + let organization = $derived.by(() => { + let organization = $state(data.organization); + return organization; + }); + function submit() { // Update the organization. const parameters = { organizationID: data.organizationID, @@ -39,8 +41,6 @@ } let providerType = $derived.by(() => { - if (!organization) return; - const provider = data.oauth2providers.find( (x) => x.metadata.id == organization.spec.providerID ); @@ -53,7 +53,6 @@ let domainValid: boolean = $state(false); let valid: boolean = $derived.by(() => { - if (!organization) return false; if (!metadataValid) return false; if (organization.spec.organizationType == Identity.OrganizationType.Domain) { if (!domainValid) return false; @@ -63,96 +62,94 @@ - {#if organization} - - - - - + {#each Object.entries(Identity.OrganizationType) as [symbol, value]} + + {/each} + + + + {#if organization.spec.organizationType == Identity.OrganizationType.Domain} + + + + + + - - {#if organization.spec.organizationType == Identity.OrganizationType.Domain} - - - - - + {#if organization.spec.providerScope == Identity.ProviderScope.Global} - {#if organization.spec.providerScope == Identity.ProviderScope.Global} - - - {#if providerType == Identity.Oauth2ProviderType.Google} - - {/if} - {:else} - - - + {#if providerType == Identity.Oauth2ProviderType.Google} + {/if} - - {/if} - -
-
+ {:else} + + + + {/if} + {/if} + +
+
diff --git a/src/routes/(shell)/identity/organizations/+page.ts b/src/routes/(shell)/identity/organizations/+page.ts index f0b846c..e341094 100644 --- a/src/routes/(shell)/identity/organizations/+page.ts +++ b/src/routes/(shell)/identity/organizations/+page.ts @@ -1,6 +1,7 @@ export const ssr = false; import type { PageLoad } from './$types'; +import { error } from '@sveltejs/kit'; import * as RBAC from '$lib/rbac'; import * as Identity from '$lib/openapi/identity'; @@ -18,10 +19,16 @@ export const load: PageLoad = async ({ fetch, parent }) => { } ]; - const { token, organizationID, acl, allowed } = await parent(); + const { token, organizationID, organizations, acl, allowed } = await parent(); + + const organization = organizations.find((x) => x.metadata.id == organizationID); + if (!organization) { + error(404, 'organization not found'); + } if (!allowed || !RBAC.organizationScopesAllowed(acl, organizationID, scopes)) { return { + organization: organization, oauth2providers: [] as Array, orgOauth2providers: [] as Array, allowed: false @@ -36,6 +43,7 @@ export const load: PageLoad = async ({ fetch, parent }) => { ).apiV1OrganizationsOrganizationIDOauth2providersGet({ organizationID: organizationID }); return { + organization: organization, oauth2providers: await oauth2providers, orgOauth2providers: await orgOauth2providers, allowed: true diff --git a/src/routes/(shell)/identity/projects/view/[id]/+page.svelte b/src/routes/(shell)/identity/projects/view/[id]/+page.svelte index d055f5a..711904e 100644 --- a/src/routes/(shell)/identity/projects/view/[id]/+page.svelte +++ b/src/routes/(shell)/identity/projects/view/[id]/+page.svelte @@ -21,11 +21,11 @@ }; let project = $derived.by(() => { - return data.projects.find((x) => x.metadata.id == $page.params.id); + let project = $state(data.project); + return project; }); $effect.pre(() => { - if (!project) return; if (!project.spec.groupIDs) project.spec.groupIDs = []; }); @@ -40,8 +40,6 @@ ); function submit() { - if (!project) return; - const parameters = { organizationID: data.organizationID, projectID: $page.params.id, @@ -56,39 +54,37 @@ - {#if project} - - + + - - {#if project.spec.groupIDs} - - {#each data.groups as group} - - {/each} - - {/if} - + + {#if project.spec.groupIDs} + + {#each data.groups as group} + + {/each} + + {/if} + -
-
- {/if} +
+
diff --git a/src/routes/(shell)/identity/projects/view/[id]/+page.ts b/src/routes/(shell)/identity/projects/view/[id]/+page.ts index 919b281..4a5893e 100644 --- a/src/routes/(shell)/identity/projects/view/[id]/+page.ts +++ b/src/routes/(shell)/identity/projects/view/[id]/+page.ts @@ -1,12 +1,13 @@ export const ssr = false; import type { PageLoad } from './$types'; +import { error } from '@sveltejs/kit'; import * as RBAC from '$lib/rbac'; import * as Identity from '$lib/openapi/identity'; import * as Clients from '$lib/clients'; -export const load: PageLoad = async ({ fetch, parent }) => { +export const load: PageLoad = async ({ fetch, parent, params }) => { const scopes: Array = [ { endpoint: 'identity:projects', @@ -18,20 +19,27 @@ export const load: PageLoad = async ({ fetch, parent }) => { } ]; - const { token, organizationID, acl, allowed } = await parent(); + const { token, organizationID, acl, allowed, projects } = await parent(); if (!allowed || !RBAC.organizationScopesAllowed(acl, organizationID, scopes)) { return { + project: {} as Identity.ProjectRead, groups: [] as Array, allowed: false }; } + const project = projects.find((x) => x.metadata.id == params['id']); + if (!project) { + error(404, 'project not found'); + } + const groups = Clients.identity(token, fetch).apiV1OrganizationsOrganizationIDGroupsGet({ organizationID: organizationID }); return { + project: project, groups: await groups, allowed: true }; diff --git a/src/routes/(shell)/identity/quotas/+page.svelte b/src/routes/(shell)/identity/quotas/+page.svelte new file mode 100644 index 0000000..7a7e98e --- /dev/null +++ b/src/routes/(shell)/identity/quotas/+page.svelte @@ -0,0 +1,68 @@ + + + + + {#each quotas.capacity as quota, i} + + {/each} + + +
+
+
diff --git a/src/routes/(shell)/identity/quotas/+page.ts b/src/routes/(shell)/identity/quotas/+page.ts new file mode 100644 index 0000000..3ae94cf --- /dev/null +++ b/src/routes/(shell)/identity/quotas/+page.ts @@ -0,0 +1,34 @@ +export const ssr = false; + +import type { PageLoad } from './$types'; + +import * as RBAC from '$lib/rbac'; +import * as Identity from '$lib/openapi/identity'; +import * as Clients from '$lib/clients'; + +export const load: PageLoad = async ({ fetch, parent }) => { + const scopes: Array = [ + { + endpoint: 'identity:quotas', + operation: Identity.AclOperation.Update + } + ]; + + const { token, organizationID, acl, allowed } = await parent(); + + if (!allowed || !RBAC.organizationScopesAllowed(acl, organizationID, scopes)) { + return { + quotas: {} as Identity.QuotasRead, + allowed: false + }; + } + + const quotas = Clients.identity(token, fetch).apiV1OrganizationsOrganizationIDQuotasGet({ + organizationID: organizationID + }); + + return { + quotas: await quotas, + allowed: true + }; +}; diff --git a/src/routes/(shell)/identity/serviceaccounts/view/[id]/+page.svelte b/src/routes/(shell)/identity/serviceaccounts/view/[id]/+page.svelte index 5babf68..50b4ed8 100644 --- a/src/routes/(shell)/identity/serviceaccounts/view/[id]/+page.svelte +++ b/src/routes/(shell)/identity/serviceaccounts/view/[id]/+page.svelte @@ -23,17 +23,17 @@ description: 'Manage your service account.' }; - let serviceAccount = $derived(data.serviceAccounts.find((x) => x.metadata.id == $page.params.id)); + let serviceAccount = $derived.by(() => { + let serviceAccount = $state(data.serviceAccount); + return serviceAccount; + }); $effect.pre(() => { - if (!serviceAccount) return; if (!serviceAccount.spec) serviceAccount.spec = {}; if (!serviceAccount.spec.groupIDs) serviceAccount.spec.groupIDs = []; }); function submit() { - if (!serviceAccount) return; - const parameters = { organizationID: data.organizationID, serviceAccountID: $page.params.id, @@ -88,7 +88,7 @@ href="/identity/serviceaccounts" />
- {:else if serviceAccount} + {:else} {#snippet extraMetadata()} { +export const load: PageLoad = async ({ fetch, parent, params }) => { const scopes: Array = [ { endpoint: 'identity:serviceaccounts', @@ -18,20 +19,27 @@ export const load: PageLoad = async ({ fetch, parent }) => { } ]; - const { token, organizationID, acl, allowed } = await parent(); + const { token, organizationID, acl, allowed, serviceAccounts } = await parent(); if (!allowed || !RBAC.organizationScopesAllowed(acl, organizationID, scopes)) { return { + serviceAccount: {} as Identity.ServiceAccountRead, groups: [] as Array, allowed: false }; } + const serviceAccount = serviceAccounts.find((x) => x.metadata.id == params['id']); + if (!serviceAccount) { + error(404, 'service account not found'); + } + const groups = Clients.identity(token, fetch).apiV1OrganizationsOrganizationIDGroupsGet({ organizationID: organizationID }); return { + serviceAccount: serviceAccount, groups: await groups, allowed: true }; diff --git a/src/routes/(shell)/identity/users/view/[id]/+page.svelte b/src/routes/(shell)/identity/users/view/[id]/+page.svelte index 9a26cd7..963ecaf 100644 --- a/src/routes/(shell)/identity/users/view/[id]/+page.svelte +++ b/src/routes/(shell)/identity/users/view/[id]/+page.svelte @@ -22,16 +22,16 @@ }; // Once we know the users, select the one we are viewing. - let user = $derived(data.users.find((x) => x.metadata.id == $page.params.id)); + let user = $derived.by(() => { + let user = $state(data.user); + return user; + }); $effect.pre(() => { - if (!user) return; if (!user.spec.groupIDs) user.spec.groupIDs = []; }); function submit() { - if (!user) return; - const parameters = { organizationID: data.organizationID, userID: $page.params.id, @@ -46,48 +46,46 @@ - {#if user} - - - - + - {#if user.spec.groupIDs} - - {#each data.groups as group} - - {/each} - + + + + {#if user.spec.groupIDs} + + {#each data.groups as group} + + {/each} + + {/if} + -
-
- {/if} +
+
diff --git a/src/routes/(shell)/identity/users/view/[id]/+page.ts b/src/routes/(shell)/identity/users/view/[id]/+page.ts index 3409b1d..9716f5a 100644 --- a/src/routes/(shell)/identity/users/view/[id]/+page.ts +++ b/src/routes/(shell)/identity/users/view/[id]/+page.ts @@ -1,12 +1,13 @@ export const ssr = false; import type { PageLoad } from './$types'; +import { error } from '@sveltejs/kit'; import * as RBAC from '$lib/rbac'; import * as Identity from '$lib/openapi/identity'; import * as Clients from '$lib/clients'; -export const load: PageLoad = async ({ fetch, parent }) => { +export const load: PageLoad = async ({ fetch, parent, params }) => { const scopes: Array = [ { endpoint: 'identity:users', @@ -18,20 +19,27 @@ export const load: PageLoad = async ({ fetch, parent }) => { } ]; - const { token, organizationID, acl, allowed } = await parent(); + const { token, organizationID, acl, allowed, users } = await parent(); if (!allowed || !RBAC.organizationScopesAllowed(acl, organizationID, scopes)) { return { + user: {} as Identity.UserRead, groups: [] as Array, allowed: false }; } + const user = users.find((x) => x.metadata.id == params['id']); + if (!user) { + error(404, 'user not found'); + } + const groups = Clients.identity(token, fetch).apiV1OrganizationsOrganizationIDGroupsGet({ organizationID: organizationID }); return { + user: user, groups: await groups, allowed: true }; diff --git a/src/routes/(shell)/kubernetes/clusters/view/[id]/+page.svelte b/src/routes/(shell)/kubernetes/clusters/view/[id]/+page.svelte index 675d33c..17f844f 100644 --- a/src/routes/(shell)/kubernetes/clusters/view/[id]/+page.svelte +++ b/src/routes/(shell)/kubernetes/clusters/view/[id]/+page.svelte @@ -26,6 +26,11 @@ description: 'Update an existing Kubernetes cluster.' }; + let cluster = $derived.by(() => { + let cluster = $state(data.cluster); + return cluster; + }); + let clusters = $derived( data.clusters.filter((x) => x.metadata.projectId == data.cluster.metadata.projectId) ); @@ -55,13 +60,13 @@ } }; - data.cluster.spec.workloadPools.push(pool); + cluster.spec.workloadPools.push(pool); - return data.cluster.spec.workloadPools.length - 1; + return cluster.spec.workloadPools.length - 1; } function workloadPoolRemove(index: number) { - data.cluster.spec.workloadPools.splice(index, 1); + cluster.spec.workloadPools.splice(index, 1); } // A workload pool is valid if all the fields in the pool are valid and @@ -69,7 +74,7 @@ let workloadPoolValidFull: boolean = $derived.by(() => { if (!workloadPoolValid) return false; - const names = data.cluster.spec.workloadPools.map((x) => x.name); + const names = cluster.spec.workloadPools.map((x) => x.name); const uniqueNames = new Set(names); if (names.length != uniqueNames.size) return false; @@ -95,9 +100,9 @@ let step2valid: boolean = $derived.by(() => { if (step != 1) return true; - if (data.cluster.spec.workloadPools.length == 0) return false; + if (cluster.spec.workloadPools.length == 0) return false; - const names = data.cluster.spec.workloadPools.map((x) => x.name); + const names = cluster.spec.workloadPools.map((x) => x.name); const uniqueNames = new Set(names); if (names.length != uniqueNames.size) return false; @@ -129,9 +134,9 @@ function complete() { const parameters = { organizationID: data.organizationID, - projectID: data.cluster.metadata.projectId, - clusterID: data.cluster.metadata.id, - kubernetesClusterWrite: data.cluster + projectID: cluster.metadata.projectId, + clusterID: cluster.metadata.id, + kubernetesClusterWrite: cluster }; Clients.kubernetes(data.token) @@ -142,10 +147,10 @@ - + {#snippet badges()} - - {RegionUtil.name(data.regions, data.cluster.spec.regionId)} + + {RegionUtil.name(data.regions, cluster.spec.regionId)} {/snippet} @@ -155,7 +160,7 @@ {#if index === 0}

Basic Cluster Setup

- +