Migrating Vue components to React and stores refactoring (#585)

Signed-off-by: Roman <ixrock@gmail.com>

Co-authored-by: Sebastian Malton <sebastian@malton.name>
Co-authored-by: Sebastian Malton <smalton@mirantis.com>
Co-authored-by: Lauri Nevala <lauri.nevala@gmail.com>
Co-authored-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
authored and GitHub committed 2020-08-20 08:53:07 +03:00
1 parent 905bbe9d3f
commit 5670312c47
297 files changed
+8186 -9103

No files matched your search

-9
View File
@@ -1,9 +0,0 @@
// App configuration api
import type { IConfigRoutePayload } from "../../../main/routes/config";
import { apiBase } from "../index";
export const configApi = {
getConfig() {
return apiBase.get<IConfigRoutePayload>("/config")
},
};
@@ -1,5 +1,5 @@
import { compile } from "path-to-regexp";
import { apiHelm } from "../index";
import { apiBase } from "../index";
import { stringify } from "querystring";
import { autobind } from "../../utils";
@@ -21,7 +21,7 @@ const endpoint = compile(`/v2/charts/:repo?/:name?`) as (params?: {
export const helmChartsApi = {
list() {
return apiHelm
return apiBase
.get<IHelmChartList>(endpoint())
.then(data => {
return Object
@@ -33,7 +33,7 @@ export const helmChartsApi = {
get(repo: string, name: string, readmeVersion?: string) {
const path = endpoint({ repo, name });
return apiHelm
return apiBase
.get<IHelmChartDetails>(path + "?" + stringify({ version: readmeVersion }))
.then(data => {
const versions = data.versions.map(HelmChart.create);
@@ -46,7 +46,7 @@ export const helmChartsApi = {
},
getValues(repo: string, name: string, version: string) {
return apiHelm
return apiBase
.get<string>(`/v2/charts/${repo}/${name}/values?` + stringify({ version }));
}
};
@@ -2,7 +2,7 @@ import jsYaml from "js-yaml";
import { compile } from "path-to-regexp";
import { autobind, formatDuration } from "../../utils";
import capitalize from "lodash/capitalize";
import { apiHelm } from "../index";
import { apiBase } from "../index";
import { helmChartStore } from "../../components/+apps-helm-charts/helm-chart.store";
import { ItemObject } from "../../item.store";
import { KubeObject } from "../kube-object";
@@ -69,14 +69,14 @@ const endpoint = compile(`/v2/releases/:namespace?/:name?`) as (
export const helmReleasesApi = {
list(namespace?: string) {
return apiHelm
return apiBase
.get<HelmRelease[]>(endpoint({ namespace }))
.then(releases => releases.map(HelmRelease.create));
},
get(name: string, namespace: string) {
const path = endpoint({ name, namespace });
return apiHelm.get<IReleaseRawDetails>(path).then(details => {
return apiBase.get<IReleaseRawDetails>(path).then(details => {
const items: KubeObject[] = JSON.parse(details.resources).items;
const resources = items.map(item => KubeObject.create(item));
return {
@@ -90,34 +90,34 @@ export const helmReleasesApi = {
const { repo, ...data } = payload;
data.chart = `${repo}/${data.chart}`;
data.values = jsYaml.safeLoad(data.values);
return apiHelm.post(endpoint(), { data });
return apiBase.post(endpoint(), { data });
},
update(name: string, namespace: string, payload: IReleaseUpdatePayload): Promise<IReleaseUpdateDetails> {
const { repo, ...data } = payload;
data.chart = `${repo}/${data.chart}`;
data.values = jsYaml.safeLoad(data.values);
return apiHelm.put(endpoint({ name, namespace }), { data });
return apiBase.put(endpoint({ name, namespace }), { data });
},
async delete(name: string, namespace: string) {
const path = endpoint({ name, namespace });
return apiHelm.del(path);
return apiBase.del(path);
},
getValues(name: string, namespace: string) {
const path = endpoint({ name, namespace }) + "/values";
return apiHelm.get<string>(path);
return apiBase.get<string>(path);
},
getHistory(name: string, namespace: string): Promise<IReleaseRevision[]> {
const path = endpoint({ name, namespace }) + "/history";
return apiHelm.get(path);
return apiBase.get(path);
},
rollback(name: string, namespace: string, revision: number) {
const path = endpoint({ name, namespace }) + "/rollback";
return apiHelm.put(path, {
return apiBase.put(path, {
data: {
revision: revision
}
+3 -6
View File
@@ -1,10 +1,7 @@
// Local express.js endpoints
export * from "./config.api"
export * from "./cluster.api"
export * from "./kubeconfig.api"
// Kubernetes endpoints
// Kubernetes apis
// Docs: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/
export * from "./cluster.api"
export * from "./namespaces.api"
export * from "./cluster-role.api"
export * from "./cluster-role-binding.api"
@@ -1,12 +0,0 @@
// Kubeconfig api
import { apiBase } from "../index";
export const kubeConfigApi = {
getUserConfig() {
return apiBase.get("/kubeconfig/user");
},
getServiceAccountConfig(account: string, namespace: string) {
return apiBase.get(`/kubeconfig/service-account/${namespace}/${account}`);
},
};
+1 -1
View File
@@ -2,7 +2,7 @@
import moment from "moment";
import { apiBase } from "../index";
import type { IMetricsQuery } from "../../../main/routes/metrics";
import type { IMetricsQuery } from "../../../main/routes/metrics-route";
export interface IMetrics {
status: string;
@@ -1,7 +1,7 @@
import jsYaml from "js-yaml"
import { KubeObject } from "../kube-object";
import { KubeJsonApiData } from "../kube-json-api";
import { apiResourceApplier } from "../index";
import { apiBase } from "../index";
import { apiManager } from "../api-manager";
export const resourceApplierApi = {
@@ -13,7 +13,7 @@ export const resourceApplierApi = {
if (typeof resource === "string") {
resource = jsYaml.safeLoad(resource);
}
return apiResourceApplier
return apiBase
.post<KubeJsonApiData[]>("/stack", { data: resource })
.then(data => {
const items = data.map(obj => {
@@ -24,7 +24,7 @@ export class SelfSubjectRulesReview extends KubeObject {
static kind = "SelfSubjectRulesReview"
spec: {
// fixme: add more types from api docs
// todo: add more types from api docs
namespace?: string;
}
+4 -16
View File
@@ -1,29 +1,19 @@
import { JsonApi, JsonApiErrorParsed } from "./json-api";
import { KubeJsonApi } from "./kube-json-api";
import { Notifications } from "../components/notifications";
import { apiPrefix, isDevelopment } from "../../common/vars";
//-- JSON HTTP APIS
import { apiKubePrefix, apiPrefix, isDevelopment } from "../../common/vars";
export const apiBase = new JsonApi({
apiBase: apiPrefix,
debug: isDevelopment,
apiPrefix: apiPrefix.BASE,
});
export const apiKube = new KubeJsonApi({
apiBase: apiKubePrefix,
debug: isDevelopment,
apiPrefix: apiPrefix.KUBE_BASE,
});
export const apiHelm = new KubeJsonApi({
debug: isDevelopment,
apiPrefix: apiPrefix.KUBE_HELM,
});
export const apiResourceApplier = new KubeJsonApi({
debug: isDevelopment,
apiPrefix: apiPrefix.KUBE_RESOURCE_APPLIER,
});
// Common handler for HTTP api errors
function onApiError(error: JsonApiErrorParsed, res: Response) {
export function onApiError(error: JsonApiErrorParsed, res: Response) {
switch (res.status) {
case 403:
error.isUsedForNotification = true;
@@ -34,5 +24,3 @@ function onApiError(error: JsonApiErrorParsed, res: Response) {
apiBase.onError.addListener(onApiError);
apiKube.onError.addListener(onApiError);
apiHelm.onError.addListener(onApiError);
apiResourceApplier.onError.addListener(onApiError);
+2 -2
View File
@@ -27,7 +27,7 @@ export interface JsonApiLog {
}
export interface JsonApiConfig {
apiPrefix: string;
apiBase: string;
debug?: boolean;
}
@@ -72,7 +72,7 @@ export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
}
protected request<D>(path: string, params?: P, init: RequestInit = {}) {
let reqUrl = this.config.apiPrefix + path;
let reqUrl = this.config.apiBase + path;
const reqInit: RequestInit = { ...this.reqInit, ...init };
const { data, query } = params || {} as P;
if (data && !reqInit.body) {
+46 -3
View File
@@ -1,5 +1,15 @@
// Parse kube-api path and get api-version, group, etc.
import type { KubeObject } from "./kube-object";
import { splitArray } from "../../common/utils";
import { apiManager } from "./api-manager";
export interface IKubeObjectRef {
kind: string;
apiVersion: string;
name: string;
namespace?: string;
}
export interface IKubeApiLinkRef {
apiPrefix?: string;
@@ -9,13 +19,13 @@ export interface IKubeApiLinkRef {
namespace?: string;
}
export interface IKubeApiLinkBase extends IKubeApiLinkRef {
export interface IKubeApiParsed extends IKubeApiLinkRef {
apiBase: string;
apiGroup: string;
apiVersionWithGroup: string;
}
export function parseApi(path: string): IKubeApiLinkBase {
export function parseKubeApi(path: string): IKubeApiParsed {
path = new URL(path, location.origin).pathname;
const [, prefix, ...parts] = path.split("/");
const apiPrefix = `/${prefix}`;
@@ -94,7 +104,7 @@ export function parseApi(path: string): IKubeApiLinkBase {
};
}
export function createApiLink(ref: IKubeApiLinkRef): string {
export function createKubeApiURL(ref: IKubeApiLinkRef): string {
const { apiPrefix = "/apis", resource, apiVersion, name } = ref;
let { namespace } = ref;
if (namespace) {
@@ -104,3 +114,36 @@ export function createApiLink(ref: IKubeApiLinkRef): string {
.filter(v => v)
.join("/")
}
export function lookupApiLink(ref: IKubeObjectRef, parentObject: KubeObject): string {
const {
kind, apiVersion, name,
namespace = parentObject.getNs()
} = ref;
// search in registered apis by 'kind' & 'apiVersion'
const api = apiManager.getApi(api => api.kind === kind && api.apiVersionWithGroup == apiVersion)
if (api) {
return api.getUrl({ namespace, name })
}
// lookup api by generated resource link
const apiPrefixes = ["/apis", "/api"];
const resource = kind.toLowerCase() + kind.endsWith("s") ? "es" : "s";
for (const apiPrefix of apiPrefixes) {
const apiLink = createKubeApiURL({ apiPrefix, apiVersion, name, namespace, resource });
if (apiManager.getApi(apiLink)) {
return apiLink;
}
}
// resolve by kind only (hpa's might use refs to older versions of resources for example)
const apiByKind = apiManager.getApi(api => api.kind === kind);
if (apiByKind) {
return apiByKind.getUrl({ name, namespace })
}
// otherwise generate link with default prefix
// resource still might exists in k8s, but api is not registered in the app
return createKubeApiURL({ apiVersion, name, namespace, resource })
}
+3 -3
View File
@@ -1,8 +1,8 @@
import { IKubeApiLinkBase, parseApi } from "./kube-api-parse";
import { IKubeApiParsed, parseKubeApi } from "./kube-api-parse";
interface KubeApi_Parse_Test {
url: string;
expected: Required<IKubeApiLinkBase>;
expected: Required<IKubeApiParsed>;
}
const tests: KubeApi_Parse_Test[] = [
@@ -129,7 +129,7 @@ describe.only("parseApi unit tests", () => {
for (const i in tests) {
const { url: tUrl, expected:tExpect} = tests[i];
test(`test #${parseInt(i)+1}`, () => {
expect(parseApi(tUrl)).toStrictEqual(tExpect);
expect(parseKubeApi(tUrl)).toStrictEqual(tExpect);
});
}
});
+5 -37
View File
@@ -3,11 +3,11 @@
import merge from "lodash/merge"
import { stringify } from "querystring";
import { IKubeObjectConstructor, KubeObject } from "./kube-object";
import { IKubeObjectRef, KubeJsonApi, KubeJsonApiData, KubeJsonApiDataList } from "./kube-json-api";
import { KubeJsonApi, KubeJsonApiData, KubeJsonApiDataList } from "./kube-json-api";
import { apiKube } from "./index";
import { kubeWatchApi } from "./kube-watch-api";
import { apiManager } from "./api-manager";
import { createApiLink, parseApi } from "./kube-api-parse";
import { createKubeApiURL, parseKubeApi } from "./kube-api-parse";
export interface IKubeApiOptions<T extends KubeObject> {
kind: string; // resource type within api-group, e.g. "Namespace"
@@ -26,8 +26,7 @@ export interface IKubeApiQueryParams {
}
export class KubeApi<T extends KubeObject = any> {
static parseApi = parseApi;
static createLink = createApiLink;
static parseApi = parseKubeApi;
static watchAll(...apis: KubeApi[]) {
const disposers = apis.map(api => api.watch());
@@ -85,7 +84,7 @@ export class KubeApi<T extends KubeObject = any> {
getUrl({ name = "", namespace = "" } = {}, query?: Partial<IKubeApiQueryParams>) {
const { apiPrefix, apiVersionWithGroup, apiResource } = this;
const resourcePath = KubeApi.createLink({
const resourcePath = createKubeApiURL({
apiPrefix: apiPrefix,
apiVersion: apiVersionWithGroup,
resource: apiResource,
@@ -175,35 +174,4 @@ export class KubeApi<T extends KubeObject = any> {
}
}
export function lookupApiLink(ref: IKubeObjectRef, parentObject: KubeObject): string {
const {
kind, apiVersion, name,
namespace = parentObject.getNs()
} = ref;
// search in registered apis by 'kind' & 'apiVersion'
const api = apiManager.getApi(api => api.kind === kind && api.apiVersionWithGroup == apiVersion)
if (api) {
return api.getUrl({ namespace, name })
}
// lookup api by generated resource link
const apiPrefixes = ["/apis", "/api"];
const resource = kind.toLowerCase() + kind.endsWith("s") ? "es" : "s";
for (const apiPrefix of apiPrefixes) {
const apiLink = KubeApi.createLink({ apiPrefix, apiVersion, name, namespace, resource });
if (apiManager.getApi(apiLink)) {
return apiLink;
}
}
// resolve by kind only (hpa's might use refs to older versions of resources for example)
const apiByKind = apiManager.getApi(api => api.kind === kind);
if (apiByKind) {
return apiByKind.getUrl({ name, namespace })
}
// otherwise generate link with default prefix
// resource still might exists in k8s, but api is not registered in the app
return KubeApi.createLink({ apiVersion, name, namespace, resource })
}
export * from "./kube-api-parse"
-15
View File
@@ -31,13 +31,6 @@ export interface KubeJsonApiData extends JsonApiData {
};
}
export interface IKubeObjectRef {
kind: string;
apiVersion: string;
name: string;
namespace?: string;
}
export interface KubeJsonApiError extends JsonApiError {
code: number;
status: string;
@@ -49,14 +42,6 @@ export interface KubeJsonApiError extends JsonApiError {
};
}
export interface IKubeJsonApiQuery {
watch?: any;
resourceVersion?: string;
timeoutSeconds?: number;
limit?: number; // doesn't work with ?watch
continue?: string; // might be used with ?limit from second request
}
export class KubeJsonApi extends JsonApi<KubeJsonApiData> {
protected parseError(error: KubeJsonApiError | any, res: Response): string[] {
const { status, reason, message } = error;
+4 -5
View File
@@ -6,9 +6,9 @@ import { autobind, EventEmitter } from "../utils";
import { KubeJsonApiData } from "./kube-json-api";
import type { KubeObjectStore } from "../kube-object.store";
import { KubeApi } from "./kube-api";
import { configStore } from "../config.store";
import { apiManager } from "./api-manager";
import { apiPrefix, isDevelopment } from "../../common/vars";
import { getHostedCluster } from "../../common/cluster-store";
export interface IKubeWatchEvent<T = any> {
type: "ADDED" | "MODIFIED" | "DELETED";
@@ -29,7 +29,6 @@ export interface IKubeWatchRouteQuery {
export class KubeWatchApi {
protected evtSource: EventSource;
protected onData = new EventEmitter<[IKubeWatchEvent]>();
protected apiUrl = apiPrefix.BASE + "/watch";
protected subscribers = observable.map<KubeApi, number>();
protected reconnectTimeoutMs = 5000;
protected maxReconnectsOnError = 10;
@@ -62,10 +61,10 @@ export class KubeWatchApi {
}
protected getQuery(): Partial<IKubeWatchRouteQuery> {
const { isClusterAdmin, allowedNamespaces } = configStore;
const { isAdmin, allowedNamespaces } = getHostedCluster()
return {
api: this.activeApis.map(api => {
if (isClusterAdmin) return api.getWatchUrl();
if (isAdmin) return api.getWatchUrl();
return allowedNamespaces.map(namespace => api.getWatchUrl(namespace))
}).flat()
}
@@ -79,7 +78,7 @@ export class KubeWatchApi {
return;
}
const query = this.getQuery();
const apiUrl = this.apiUrl + "?" + stringify(query);
const apiUrl = `${apiPrefix}/watch?` + stringify(query);
this.evtSource = new EventSource(apiUrl);
this.evtSource.onmessage = this.onMessage;
this.evtSource.onerror = this.onError;
-14
View File
@@ -1,14 +0,0 @@
import { configStore } from "../config.store";
export function isAllowedResource(resources: string | string[]) {
if (!Array.isArray(resources)) {
resources = [resources];
}
const { allowedResources } = configStore;
for (const resource of resources) {
if (!allowedResources.includes(resource)) {
return false;
}
}
return true;
}
+13 -33
View File
@@ -1,9 +1,8 @@
import { stringify } from "querystring";
import { autobind, base64, EventEmitter, interval } from "../utils";
import { autobind, base64, EventEmitter } from "../utils";
import { WebSocketApi } from "./websocket-api";
import { configStore } from "../config.store";
import isEqual from "lodash/isEqual"
import { apiPrefix, isDevelopment } from "../../common/vars";
import { isDevelopment } from "../../common/vars";
export enum TerminalChannels {
STDIN = 0,
@@ -25,21 +24,19 @@ enum TerminalColor {
NO_COLOR = "\u001b[0m",
}
export interface ITerminalApiOptions {
export type TerminalApiQuery = Record<string, string> & {
id: string;
node?: string;
colorTheme?: "light" | "dark";
type?: string | "node";
}
export class TerminalApi extends WebSocketApi {
protected size: { Width: number; Height: number };
protected currentToken: string;
protected tokenInterval = interval(60, this.sendNewToken); // refresh every minute
public onReady = new EventEmitter<[]>();
public isReady = false;
constructor(protected options: ITerminalApiOptions) {
constructor(protected options: TerminalApiQuery) {
super({
logging: isDevelopment,
flushOnOpen: false,
@@ -47,50 +44,33 @@ export class TerminalApi extends WebSocketApi {
});
}
async getUrl(token: string) {
const { hostname, protocol } = location;
async getUrl() {
let { port } = location;
const { hostname, protocol } = location;
const { id, node } = this.options;
const wss = `ws${protocol === "https:" ? "s" : ""}://`;
const queryParams = { token, id };
const query: TerminalApiQuery = { id };
if (port) {
port = `:${port}`
}
if (node) {
Object.assign(queryParams, {
node: node,
type: "node"
});
query.node = node;
query.type = "node";
}
return `${wss}${hostname}${port}/api?${stringify(queryParams)}`;
return `${wss}${hostname}${port}/api?${stringify(query)}`;
}
async connect() {
const token = await configStore.getToken();
const apiUrl = await this.getUrl(token);
const { colorTheme } = this.options;
this.emitStatus("Connecting ...", {
color: colorTheme == "light" ? TerminalColor.GRAY : TerminalColor.LIGHT_GRAY
});
const apiUrl = await this.getUrl();
this.emitStatus("Connecting ...");
this.onData.addListener(this._onReady, { prepend: true });
this.currentToken = token;
this.tokenInterval.start();
return super.connect(apiUrl);
}
@autobind()
async sendNewToken() {
const token = await configStore.getToken();
if (!this.isReady || token == this.currentToken) return;
this.sendCommand(token, TerminalChannels.TOKEN);
this.currentToken = token;
}
destroy() {
if (!this.socket) return;
const exitCode = String.fromCharCode(4); // ctrl+d
this.sendCommand(exitCode);
this.tokenInterval.stop();
setTimeout(() => super.destroy(), 2000);
}
+2 -4
View File
@@ -1,5 +1,5 @@
import get from "lodash/get";
import { IKubeObjectMetadata, KubeObject } from "./kube-object";
import { KubeObject } from "./kube-object";
interface IToleration {
key?: string;
@@ -47,9 +47,7 @@ export interface IAffinity {
}
export class WorkloadKubeObject extends KubeObject {
// fixme: add type
spec: any;
spec: any; // todo: add proper types
getSelectors(): string[] {
const selector = this.spec.selector;