Skip to content

Commit

Permalink
Preapare the field for AppLogBar in my services
Browse files Browse the repository at this point in the history
  • Loading branch information
garronej committed Oct 6, 2023
1 parent e6e78a6 commit 634ce78
Show file tree
Hide file tree
Showing 23 changed files with 1,011 additions and 555 deletions.
44 changes: 44 additions & 0 deletions web/src/core/tools/nestObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Transforms a flat object with dot-separated keys into a nested object.
*
* @param {Record<string, any>} input - The flat object to be nested.
* @returns {Record<string, any>} A new object with nested properties.
*
* @example
* const flatObject = {
* "foo.bar": 1,
* "foo.baz": "okay",
* "cool": "yes"
* };
* const nestedObject = nestObject(flatObject);
* // Output will be:
* // {
* // "foo": {
* // "bar": 1,
* // "baz": "okay"
* // },
* // "cool": "yes"
* // }
*/
export function nestObject(input: Record<string, any>): Record<string, any> {
const output: Record<string, any> = {};

for (const [key, value] of Object.entries(input)) {
let parts = key.split(".");
let target = output;

for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (i === parts.length - 1) {
target[part] = value;
} else {
if (!target[part] || typeof target[part] !== "object") {
target[part] = {};
}
target = target[part];
}
}
}

return output;
}
30 changes: 25 additions & 5 deletions web/src/core/usecases/serviceManager/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,33 @@ import { createSelector } from "@reduxjs/toolkit";

import { name, type RunningService } from "./state";

const runningServices = (rootState: RootState): RunningService[] | undefined => {
const { runningServices } = rootState[name];
const readyState = (rootState: RootState) => {
const state = rootState[name];

if (runningServices === undefined) {
if (state.stateDescription !== "ready") {
return undefined;
}

return [...runningServices].sort((a, b) => b.startedAt - a.startedAt);
return state;
};

const runningServices = createSelector(
readyState,
(state): RunningService[] | undefined => {
if (state === undefined) {
return undefined;
}

const { runningServices } = state;

if (runningServices === undefined) {
return undefined;
}

return [...runningServices].sort((a, b) => b.startedAt - a.startedAt);
}
);

const isUpdating = (rootState: RootState): boolean => {
const { isUpdating } = rootState[name];
return isUpdating;
Expand All @@ -35,10 +52,13 @@ const isThereOwnedSharedServices = createSelector(
undefined
);

const apiLogsEntries = createSelector(readyState, state => state?.apiLogsEntries ?? []);

export const selectors = {
runningServices,
deletableRunningServices,
isUpdating,
isThereNonOwnedServices,
isThereOwnedSharedServices
isThereOwnedSharedServices,
apiLogsEntries
};
117 changes: 102 additions & 15 deletions web/src/core/usecases/serviceManager/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,33 @@ import { createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";
import { id } from "tsafe/id";

type State = {
isUpdating: boolean;
runningServices: undefined | RunningService[];
};
import { nestObject } from "core/tools/nestObject";
import * as yaml from "yaml";

export type State = State.NotInitialized | State.Ready;

export namespace State {
export type Common = {
isUpdating: boolean;
};

export type NotInitialized = Common & {
stateDescription: "not initialized";
};

export type Ready = Common & {
stateDescription: "ready";
runningServices: RunningService[];
envByServiceId: Record<string, Record<string, string>>;
postInstallInstructionsByServiceId: Record<string, string>;
kubernetesNamespace: string;
apiLogsEntries: {
cmdId: number;
cmd: string;
resp: string | undefined;
}[];
};
}

export type RunningService = RunningService.Owned | RunningService.NotOwned;

Expand All @@ -23,8 +46,7 @@ export declare namespace RunningService {
vaultTokenExpirationTime: number | undefined;
s3TokenExpirationTime: number | undefined;
urls: string[];
postInstallInstructions: string | undefined;
env: Record<string, string>;
hasPostInstallInstructions: boolean;
};

export type Owned = Common & {
Expand All @@ -43,23 +65,49 @@ export const name = "serviceManager";

export const { reducer, actions } = createSlice({
name,
"initialState": id<State>({
"isUpdating": false,
"runningServices": undefined
}),
"initialState": id<State>(
id<State.NotInitialized>({
"stateDescription": "not initialized",
"isUpdating": false
})
),
"reducers": {
"updateStarted": state => {
state.isUpdating = true;
},
"updateCompleted": (
_state,
{ payload }: PayloadAction<{ runningServices: RunningService[] }>
state,
{
payload
}: PayloadAction<{
runningServices: RunningService[];
envByServiceId: Record<string, Record<string, string>>;
postInstallInstructionsByServiceId: Record<string, string>;
kubernetesNamespace: string;
}>
) => {
const { runningServices } = payload;
const {
runningServices,
envByServiceId,
postInstallInstructionsByServiceId,
kubernetesNamespace
} = payload;

return id<State>({
return id<State.Ready>({
"stateDescription": "ready",
"isUpdating": false,
runningServices
runningServices,
envByServiceId,
postInstallInstructionsByServiceId,
kubernetesNamespace,
"apiLogsEntries": (() => {
switch (state.stateDescription) {
case "ready":
return state.apiLogsEntries;
case "not initialized":
return [];
}
})()
});
},
"serviceStarted": (
Expand All @@ -72,6 +120,9 @@ export const { reducer, actions } = createSlice({
}>
) => {
const { serviceId, doOverwriteStaredAtToNow } = payload;

assert(state.stateDescription === "ready");

const { runningServices } = state;

assert(runningServices !== undefined);
Expand All @@ -92,13 +143,49 @@ export const { reducer, actions } = createSlice({
"serviceStopped": (state, { payload }: PayloadAction<{ serviceId: string }>) => {
const { serviceId } = payload;

assert(state.stateDescription === "ready");

const { runningServices } = state;
assert(runningServices !== undefined);

runningServices.splice(
runningServices.findIndex(({ id }) => id === serviceId),
1
);
},
"postInstallInstructionsRequested": (
state,
{ payload }: { payload: { serviceId: string } }
) => {
const { serviceId } = payload;

assert(state.stateDescription === "ready");

const postInstallInstructions =
state.postInstallInstructionsByServiceId[serviceId];

assert(postInstallInstructions !== undefined);

state.apiLogsEntries.push({
"cmdId": Date.now(),
"cmd": `helm get notes ${serviceId} --namespace ${state.kubernetesNamespace}`,
"resp": postInstallInstructions
});
},
"envRequested": (state, { payload }: { payload: { serviceId: string } }) => {
const { serviceId } = payload;

assert(state.stateDescription === "ready");

const env = state.envByServiceId[serviceId];

state.apiLogsEntries.push({
"cmdId": Date.now(),
"cmd": `helm get values ${serviceId} --namespace ${state.kubernetesNamespace}`,
"resp": ["USER-SUPPLIED VALUES:", yaml.stringify(nestObject(env))].join(
"\n"
)
});
}
}
});
71 changes: 61 additions & 10 deletions web/src/core/usecases/serviceManager/thunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as projectConfigs from "../projectConfigs";
import type { Thunks } from "core/core";
import { exclude } from "tsafe/exclude";
import { createUsecaseContextApi } from "redux-clean-architecture";
import { assert } from "tsafe/assert";
import { Evt } from "evt";
import { name, actions } from "./state";
import type { RunningService } from "./state";
Expand Down Expand Up @@ -75,16 +76,18 @@ export const thunks = {
return { getLogoUrl };
})();

const { namespace: kubernetesNamespace } =
projectConfigs.selectors.selectedProject(getState());

const getMonitoringUrl = (params: { serviceId: string }) => {
const { serviceId } = params;

const project = projectConfigs.selectors.selectedProject(getState());

const selectedDeploymentRegion =
deploymentRegion.selectors.selectedDeploymentRegion(getState());
const region = deploymentRegion.selectors.selectedDeploymentRegion(
getState()
);

return selectedDeploymentRegion.servicesMonitoringUrlPattern
?.replace("$NAMESPACE", project.namespace)
return region.servicesMonitoringUrlPattern
?.replace("$NAMESPACE", kubernetesNamespace)
.replace("$INSTANCE", serviceId.replace(/^\//, ""));
};

Expand All @@ -96,6 +99,19 @@ export const thunks = {

dispatch(
actions.updateCompleted({
kubernetesNamespace,
"envByServiceId": Object.fromEntries(
runningServicesRaw.map(({ id, env }) => [id, env])
),
"postInstallInstructionsByServiceId": Object.fromEntries(
runningServicesRaw
.map(({ id, postInstallInstructions }) =>
postInstallInstructions === undefined
? undefined
: [id, postInstallInstructions]
)
.filter(exclude(undefined))
),
"runningServices": runningServicesRaw
.map(
({
Expand All @@ -104,10 +120,10 @@ export const thunks = {
packageName,
urls,
startedAt,
postInstallInstructions,
isShared,
env,
ownerUsername,
env,
postInstallInstructions,
...rest
}) => {
const common: RunningService.Common = {
Expand Down Expand Up @@ -141,8 +157,8 @@ export const thunks = {
)
),
true),
postInstallInstructions,
env
"hasPostInstallInstructions":
postInstallInstructions !== undefined
};

const isOwned = ownerUsername === username;
Expand Down Expand Up @@ -181,6 +197,41 @@ export const thunks = {
dispatch(actions.serviceStopped({ serviceId }));

await onyxiaApi.stopService({ serviceId });
},
"getPostInstallInstructions":
(params: { serviceId: string }) =>
(...args): string => {
const { serviceId } = params;

const [dispatch, getState] = args;

const state = getState()[name];

assert(state.stateDescription === "ready");

const postInstallInstructions =
state.postInstallInstructionsByServiceId[serviceId];

assert(postInstallInstructions !== undefined);

dispatch(actions.postInstallInstructionsRequested({ serviceId }));

return postInstallInstructions;
},
"getEnv":
(params: { serviceId: string }) =>
(...args): Record<string, string> => {
const { serviceId } = params;

const [dispatch, getState] = args;

dispatch(actions.envRequested({ serviceId }));

const state = getState()[name];

assert(state.stateDescription === "ready");

return state.envByServiceId[serviceId];
}
} satisfies Thunks;

Expand Down
Loading

0 comments on commit 634ce78

Please sign in to comment.