1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge branch 'master' into export-kube-json-api-class

This commit is contained in:
Alex Andreev 2021-12-09 14:20:43 +03:00
commit 82c27c10ee
43 changed files with 1760 additions and 260 deletions

6
.idea/jsLibraryMappings.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="PROJECT" libraries="{ogre-tools}" />
</component>
</project>

1
.idea/lens.iml generated
View File

@ -15,5 +15,6 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="ogre-tools" level="project" />
</component>
</module>

16
.idea/libraries/ogre_tools.xml generated Normal file
View File

@ -0,0 +1,16 @@
<component name="libraryTable">
<library name="ogre-tools" type="javaScript">
<properties>
<sourceFilesUrls>
<item url="file://$PROJECT_DIR$/node_modules/@ogre-tools/injectable-react/ogre-tools-injectable-react.d.ts" />
<item url="file://$PROJECT_DIR$/node_modules/@ogre-tools/injectable/ogre-tools-injectable.d.ts" />
</sourceFilesUrls>
</properties>
<CLASSES>
<root url="file://$PROJECT_DIR$/node_modules/@ogre-tools/injectable-react/ogre-tools-injectable-react.d.ts" />
<root url="file://$PROJECT_DIR$/node_modules/@ogre-tools/injectable/ogre-tools-injectable.d.ts" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -72,7 +72,12 @@
"setupFiles": [
"<rootDir>/src/jest.setup.ts",
"jest-canvas-mock"
]
],
"globals": {
"ts-jest": {
"isolatedModules": true
}
}
},
"build": {
"generateUpdatesFilesForAllChannels": true,
@ -193,8 +198,8 @@
"@kubernetes/client-node": "^0.16.1",
"@sentry/electron": "^2.5.4",
"@sentry/integrations": "^6.15.0",
"@ogre-tools/injectable": "^1.2.1",
"@ogre-tools/injectable-react": "^1.2.1",
"@ogre-tools/injectable": "^1.3.0",
"@ogre-tools/injectable-react": "^1.3.1",
"abort-controller": "^3.0.0",
"auto-bind": "^4.0.0",
"autobind-decorator": "^2.4.0",
@ -227,7 +232,7 @@
"mobx-react": "^7.2.1",
"mock-fs": "^4.14.0",
"moment": "^2.29.1",
"moment-timezone": "^0.5.33",
"moment-timezone": "^0.5.34",
"monaco-editor": "^0.29.1",
"monaco-editor-webpack-plugin": "^5.0.0",
"node-fetch": "lensapp/node-fetch#2.x",
@ -238,7 +243,7 @@
"proper-lockfile": "^4.1.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-material-ui-carousel": "^2.3.5",
"react-material-ui-carousel": "^2.3.8",
"react-router": "^5.2.0",
"react-virtualized-auto-sizer": "^1.0.6",
"readable-stream": "^3.6.0",
@ -260,12 +265,13 @@
"ws": "^7.5.5"
},
"devDependencies": {
"@async-fn/jest": "^1.5.0",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"@sentry/types": "^6.14.1",
"@testing-library/jest-dom": "^5.15.0",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^13.5.0",
"@types/byline": "^4.2.33",
@ -280,7 +286,7 @@
"@types/html-webpack-plugin": "^3.2.6",
"@types/http-proxy": "^1.17.7",
"@types/jest": "^26.0.24",
"@types/js-yaml": "^4.0.4",
"@types/js-yaml": "^4.0.5",
"@types/jsdom": "^16.2.13",
"@types/jsonpath": "^0.2.0",
"@types/lodash": "^4.14.177",
@ -312,9 +318,9 @@
"@types/tcp-port-used": "^1.0.0",
"@types/tempy": "^0.3.0",
"@types/triple-beam": "^1.3.2",
"@types/url-parse": "^1.4.4",
"@types/url-parse": "^1.4.5",
"@types/uuid": "^8.3.3",
"@types/webpack": "^4.41.31",
"@types/webpack": "^4.41.32",
"@types/webpack-dev-server": "^3.11.6",
"@types/webpack-env": "^1.16.3",
"@types/webpack-node-externals": "^1.7.1",
@ -353,7 +359,7 @@
"node-gyp": "7.1.2",
"node-loader": "^1.0.3",
"nodemon": "^2.0.14",
"playwright": "^1.15.2",
"playwright": "^1.17.1",
"postcss": "^8.3.11",
"postcss-loader": "4.3.0",
"postinstall-postinstall": "^2.1.0",
@ -367,9 +373,9 @@
"react-select-event": "^5.1.0",
"react-table": "^7.7.0",
"react-window": "^1.8.6",
"sass": "^1.43.4",
"sass": "^1.44.0",
"sass-loader": "^8.0.2",
"sharp": "^0.29.2",
"sharp": "^0.29.3",
"style-loader": "^2.0.0",
"tailwindcss": "^2.2.19",
"ts-jest": "26.5.6",
@ -385,9 +391,9 @@
"url-loader": "^4.1.1",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2",
"webpack-dev-server": "^3.11.3",
"webpack-node-externals": "^1.7.2",
"xterm": "^4.14.1",
"xterm": "^4.15.0",
"xterm-addon-fit": "^0.5.0"
}
}

View File

@ -26,6 +26,12 @@ import { KubeObject } from "../kube-object";
import AbortController from "abort-controller";
import { delay } from "../../utils/delay";
import { PassThrough } from "stream";
import { ApiManager, apiManager } from "../api-manager";
import { Ingress, Pod } from "../endpoints";
jest.mock("../api-manager");
const mockApiManager = apiManager as jest.Mocked<ApiManager>;
class TestKubeObject extends KubeObject {
static kind = "Pod";
@ -33,7 +39,11 @@ class TestKubeObject extends KubeObject {
static apiBase = "/api/v1/pods";
}
class TestKubeApi extends KubeApi<TestKubeObject> { }
class TestKubeApi extends KubeApi<TestKubeObject> {
public async checkPreferredVersion() {
return super.checkPreferredVersion();
}
}
describe("forRemoteCluster", () => {
it("builds api client for KubeObject", async () => {
@ -184,6 +194,94 @@ describe("KubeApi", () => {
expect(kubeApi.apiGroup).toEqual("extensions");
});
describe("checkPreferredVersion", () => {
it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred", async () => {
expect.hasAssertions();
const api = new TestKubeApi({
objectConstructor: Ingress,
checkPreferredVersion: true,
fallbackApiBases: ["/apis/extensions/v1beta1/ingresses"],
request: {
get: jest.fn()
.mockImplementationOnce((path: string) => {
expect(path).toBe("/apis/networking.k8s.io/v1");
throw new Error("no");
})
.mockImplementationOnce((path: string) => {
expect(path).toBe("/apis/extensions/v1beta1");
return {
resources: [
{
name: "ingresses",
},
],
};
})
.mockImplementationOnce((path: string) => {
expect(path).toBe("/apis/extensions");
return {
preferredVersion: {
version: "v1beta1",
},
};
}),
} as any,
});
await api.checkPreferredVersion();
expect(api.apiVersionPreferred).toBe("v1beta1");
expect(mockApiManager.registerApi).toBeCalledWith("/apis/extensions/v1beta1/ingresses", expect.anything());
});
it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred with non-grouped apis", async () => {
expect.hasAssertions();
const api = new TestKubeApi({
objectConstructor: Pod,
checkPreferredVersion: true,
fallbackApiBases: ["/api/v1beta1/pods"],
request: {
get: jest.fn()
.mockImplementationOnce((path: string) => {
expect(path).toBe("/api/v1");
throw new Error("no");
})
.mockImplementationOnce((path: string) => {
expect(path).toBe("/api/v1beta1");
return {
resources: [
{
name: "pods",
},
],
};
})
.mockImplementationOnce((path: string) => {
expect(path).toBe("/api");
return {
preferredVersion: {
version: "v1beta1",
},
};
}),
} as any,
});
await api.checkPreferredVersion();
expect(api.apiVersionPreferred).toBe("v1beta1");
expect(mockApiManager.registerApi).toBeCalledWith("/api/v1beta1/pods", expect.anything());
});
});
describe("patch", () => {
let api: TestKubeApi;

View File

@ -37,7 +37,7 @@ export interface IKubeApiLinkRef {
apiPrefix?: string;
apiVersion: string;
resource: string;
name: string;
name?: string;
namespace?: string;
}
@ -145,15 +145,18 @@ function _parseKubeApi(path: string): IKubeApiParsed {
};
}
export function createKubeApiURL(ref: IKubeApiLinkRef): string {
const { apiPrefix = "/apis", resource, apiVersion, name } = ref;
let { namespace } = ref;
export function createKubeApiURL({ apiPrefix = "/apis", resource, apiVersion, name, namespace }: IKubeApiLinkRef): string {
const parts = [apiPrefix, apiVersion];
if (namespace) {
namespace = `namespaces/${namespace}`;
parts.push("namespaces", namespace);
}
return [apiPrefix, apiVersion, namespace, resource, name]
.filter(v => v)
.join("/");
parts.push(resource);
if (name) {
parts.push(name);
}
return parts.join("/");
}

View File

@ -38,9 +38,14 @@ import AbortController from "abort-controller";
import { Agent, AgentOptions } from "https";
import type { Patch } from "rfc6902";
/**
* The options used for creating a `KubeApi`
*/
export interface IKubeApiOptions<T extends KubeObject> {
/**
* base api-path for listing all resources, e.g. "/api/v1/pods"
*
* If not specified then will be the one on the `objectConstructor`
*/
apiBase?: string;
@ -52,11 +57,33 @@ export interface IKubeApiOptions<T extends KubeObject> {
*/
fallbackApiBases?: string[];
objectConstructor: KubeObjectConstructor<T>;
request?: KubeJsonApi;
isNamespaced?: boolean;
kind?: string;
/**
* If `true` then will check all declared apiBases against the kube api server
* for the first accepted one.
*/
checkPreferredVersion?: boolean;
/**
* The constructor for the kube objects returned from the API
*/
objectConstructor: KubeObjectConstructor<T>;
/**
* The api instance to use for making requests
*
* @default apiKube
*/
request?: KubeJsonApi;
/**
* @deprecated should be specified by `objectConstructor`
*/
isNamespaced?: boolean;
/**
* @deprecated should be specified by `objectConstructor`
*/
kind?: string;
}
export interface IKubeApiQueryParams {
@ -249,11 +276,11 @@ export interface DeleteResourceDescriptor extends ResourceDescriptor {
export class KubeApi<T extends KubeObject> {
readonly kind: string;
readonly apiBase: string;
readonly apiPrefix: string;
readonly apiGroup: string;
readonly apiVersion: string;
readonly apiVersionPreferred?: string;
apiBase: string;
apiPrefix: string;
apiGroup: string;
apiVersionPreferred?: string;
readonly apiResource: string;
readonly isNamespaced: boolean;
@ -264,23 +291,18 @@ export class KubeApi<T extends KubeObject> {
private watchId = 1;
constructor(protected options: IKubeApiOptions<T>) {
const {
objectConstructor,
request = apiKube,
kind = options.objectConstructor?.kind,
isNamespaced = options.objectConstructor?.namespaced,
} = options || {};
const { objectConstructor, request, kind, isNamespaced } = options;
const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parseKubeApi(options.apiBase || objectConstructor.apiBase);
this.kind = kind;
this.isNamespaced = isNamespaced;
this.options = options;
this.kind = kind ?? objectConstructor.kind;
this.isNamespaced = isNamespaced ?? objectConstructor.namespaced ?? false;
this.apiBase = apiBase;
this.apiPrefix = apiPrefix;
this.apiGroup = apiGroup;
this.apiVersion = apiVersion;
this.apiResource = resource;
this.request = request;
this.request = request ?? apiKube;
this.objectConstructor = objectConstructor;
this.parseResponse = this.parseResponse.bind(this);
@ -353,21 +375,16 @@ export class KubeApi<T extends KubeObject> {
const { apiPrefix, apiGroup } = await this.getPreferredVersionPrefixGroup();
// The apiPrefix and apiGroup might change due to fallbackApiBases, so we must override them
Object.defineProperty(this, "apiPrefix", {
value: apiPrefix,
});
Object.defineProperty(this, "apiGroup", {
value: apiGroup,
});
this.apiPrefix = apiPrefix;
this.apiGroup = apiGroup;
const res = await this.request.get<IKubePreferredVersion>(`${this.apiPrefix}/${this.apiGroup}`);
const url = [apiPrefix, apiGroup].filter(Boolean).join("/");
const res = await this.request.get<IKubePreferredVersion>(url);
Object.defineProperty(this, "apiVersionPreferred", {
value: res?.preferredVersion?.version ?? null,
});
this.apiVersionPreferred = res?.preferredVersion?.version ?? null;
if (this.apiVersionPreferred) {
Object.defineProperty(this, "apiBase", { value: this.getUrl() });
this.apiBase = this.computeApiBase();
apiManager.registerApi(this.apiBase, this);
}
}
@ -385,7 +402,15 @@ export class KubeApi<T extends KubeObject> {
return this.list(params, { limit: 1 });
}
getUrl({ name, namespace = "default" }: Partial<ResourceDescriptor> = {}, query?: Partial<IKubeApiQueryParams>) {
private computeApiBase(): string {
return createKubeApiURL({
apiPrefix: this.apiPrefix,
apiVersion: this.apiVersionWithGroup,
resource: this.apiResource,
});
}
getUrl({ name, namespace }: Partial<ResourceDescriptor> = {}, query?: Partial<IKubeApiQueryParams>) {
const resourcePath = createKubeApiURL({
apiPrefix: this.apiPrefix,
apiVersion: this.apiVersionWithGroup,

View File

@ -30,13 +30,13 @@ export enum RoutingErrorType {
MISSING_EXTENSION = "missing-ext",
}
export class RoutingError extends Error {
export class RoutingError<Query> extends Error {
/**
* Will be set if the routing error originated in an extension route table
*/
public extensionName?: string;
constructor(public type: RoutingErrorType, public url: Url) {
constructor(public type: RoutingErrorType, public url: Url<Query>) {
super("routing error");
}

View File

@ -91,7 +91,7 @@ export abstract class LensProtocolRouter extends Singleton {
* @param url the parsed URL that initiated the `lens://` protocol
* @returns true if a route has been found
*/
protected _routeToInternal(url: Url): RouteAttempt {
protected _routeToInternal(url: Url<Record<string, string>>): RouteAttempt {
return this._route(this.internalRoutes.entries(), url);
}
@ -101,7 +101,7 @@ export abstract class LensProtocolRouter extends Singleton {
* @param routes the array of path schemas, handler pairs to match against
* @param url the url (in its current state)
*/
protected _findMatchingRoute(routes: Iterable<[string, RouteHandler]>, url: Url): null | [match<Record<string, string>>, RouteHandler] {
protected _findMatchingRoute(routes: Iterable<[string, RouteHandler]>, url: Url<Record<string, string>>): null | [match<Record<string, string>>, RouteHandler] {
const matches: [match<Record<string, string>>, RouteHandler][] = [];
for (const [schema, handler] of routes) {
@ -128,7 +128,7 @@ export abstract class LensProtocolRouter extends Singleton {
* @param routes the array of (path schemas, handler) pairs to match against
* @param url the url (in its current state)
*/
protected _route(routes: Iterable<[string, RouteHandler]>, url: Url, extensionName?: string): RouteAttempt {
protected _route(routes: Iterable<[string, RouteHandler]>, url: Url<Record<string, string>>, extensionName?: string): RouteAttempt {
const route = this._findMatchingRoute(routes, url);
if (!route) {
@ -166,7 +166,7 @@ export abstract class LensProtocolRouter extends Singleton {
* @param url the protocol request URI that was "open"-ed
* @returns either the found name or the instance of `LensExtension`
*/
protected async _findMatchingExtensionByName(url: Url): Promise<LensExtension | string> {
protected async _findMatchingExtensionByName(url: Url<Record<string, string>>): Promise<LensExtension | string> {
interface ExtensionUrlMatch {
[EXTENSION_PUBLISHER_MATCH]: string;
[EXTENSION_NAME_MATCH]: string;
@ -222,7 +222,7 @@ export abstract class LensProtocolRouter extends Singleton {
* Note: this function modifies its argument, do not reuse
* @param url the protocol request URI that was "open"-ed
*/
protected async _routeToExtension(url: Url): Promise<RouteAttempt> {
protected async _routeToExtension(url: Url<Record<string, string>>): Promise<RouteAttempt> {
const extension = await this._findMatchingExtensionByName(url);
if (typeof extension === "string") {

View File

@ -26,6 +26,12 @@ import { BaseRegistry } from "./base-registry";
interface StatusBarComponents {
Item?: React.ComponentType;
/**
* The side of the bottom bar to place this component.
*
* @default "right"
*/
position?: "left" | "right";
}
interface StatusBarRegistrationV2 {

View File

@ -39,7 +39,7 @@ export interface FallbackHandler {
* @returns `true` if it should be routed internally to Lens, `false` if to an extension
* @throws if `host` is not valid
*/
function checkHost(url: URLParse): boolean {
function checkHost<Query>(url: URLParse<Query>): boolean {
switch (url.host) {
case "app":
return true;
@ -112,7 +112,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
return false;
}
protected async _findMatchingExtensionByName(url: URLParse): Promise<LensExtension | string> {
protected async _findMatchingExtensionByName(url: URLParse<Record<string, string>>): Promise<LensExtension | string> {
const firstAttempt = await super._findMatchingExtensionByName(url);
if (typeof firstAttempt !== "string") {
@ -126,7 +126,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
return "";
}
protected _routeToInternal(url: URLParse): RouteAttempt {
protected _routeToInternal(url: URLParse<Record<string, string>>): RouteAttempt {
const rawUrl = url.toString(); // for sending to renderer
const attempt = super._routeToInternal(url);
@ -135,7 +135,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
return attempt;
}
protected async _routeToExtension(url: URLParse): Promise<RouteAttempt> {
protected async _routeToExtension(url: URLParse<Record<string, string>>): Promise<RouteAttempt> {
const rawUrl = url.toString(); // for sending to renderer
/**

View File

@ -104,13 +104,13 @@ export async function bootstrap(comp: () => Promise<AppComponent>, di: Dependenc
logger.info(`${logPrefix} initializing WelcomeMenuRegistry`);
initializers.initWelcomeMenuRegistry();
logger.info(`${logPrefix} initializing WorkloadsOverviewDetailRegist`);
logger.info(`${logPrefix} initializing WorkloadsOverviewDetailRegistry`);
initializers.initWorkloadsOverviewDetailRegistry();
logger.info(`${logPrefix} initializing CatalogEntityDetailRegistry`);
initializers.initCatalogEntityDetailRegistry();
logger.info(`${logPrefix} initializing CatalogCategoryRegistryEntrie`);
logger.info(`${logPrefix} initializing CatalogCategoryRegistryEntries`);
initializers.initCatalogCategoryRegistryEntries();
logger.info(`${logPrefix} initializing Catalog`);
@ -119,6 +119,9 @@ export async function bootstrap(comp: () => Promise<AppComponent>, di: Dependenc
logger.info(`${logPrefix} initializing IpcRendererListeners`);
initializers.initIpcRendererListeners();
logger.info(`${logPrefix} initializing StatusBarRegistry`);
initializers.initStatusBarRegistry();
ExtensionLoader.createInstance().init();
ExtensionDiscovery.createInstance().init();

View File

@ -57,9 +57,7 @@ export class CrdResources extends React.Component<Props> {
}
@computed get store() {
if (!this.crd) return null;
return apiManager.getStore(this.crd.getResourceApiBase());
return apiManager.getStore(this.crd?.getResourceApiBase());
}
render() {

View File

@ -29,15 +29,14 @@ import { CRDResourceStore } from "./crd-resource.store";
import { KubeObject } from "../../../common/k8s-api/kube-object";
function initStore(crd: CustomResourceDefinition) {
const apiBase = crd.getResourceApiBase();
const kind = crd.getResourceKind();
const isNamespaced = crd.isNamespaced();
const api = apiManager.getApi(apiBase) ?? new KubeApi({
objectConstructor: KubeObject,
apiBase,
kind,
isNamespaced,
});
const objectConstructor = class extends KubeObject {
static readonly kind = crd.getResourceKind();
static readonly namespaced = crd.isNamespaced();
static readonly apiBase = crd.getResourceApiBase();
};
const api = apiManager.getApi(objectConstructor.apiBase)
?? new KubeApi({ objectConstructor });
if (!apiManager.getStore(api)) {
apiManager.registerStore(new CRDResourceStore(api));

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import React from "react";
import { observer } from "mobx-react";
import { Icon } from "../icon";
import { HotbarStore } from "../../../common/hotbar-store";
import { CommandOverlay } from "../command-palette";
import { HotbarSwitchCommand } from "../hotbar/hotbar-switch-command";
export const ActiveHotbarName = observer(() => {
return (
<div
className="flex items-center"
data-testid="current-hotbar-name"
onClick={() => CommandOverlay.open(<HotbarSwitchCommand />)}
>
<Icon material="bookmarks" className="mr-2" size={14}/>
{HotbarStore.getInstance().getActive()?.name}
</div>
);
});

View File

@ -20,22 +20,26 @@
*/
.BottomBar {
$spacing: $padding * 0.5;
--flex-gap: #{$spacing};
@apply flex px-2 text-white;
grid-area: bottom-bar;
background-color: var(--blue);
padding: 0 2px;
height: var(--bottom-bar-height);
font-size: var(--font-size-small);
}
.extensions {
font-size: var(--font-size-small);
color: white;
.item {
padding: $padding * 0.25 $padding * 0.5;
&:hover {
background-color: #ffffff33;
cursor: pointer;
}
}
.item {
@apply flex items-center mr-2 h-full px-2 last:mr-0;
&:hover {
@apply cursor-pointer;
background-color: #ffffff33;
}
}
.onLeft + .onRight {
@apply ml-auto;
}
.onRight {}

View File

@ -20,25 +20,58 @@
*/
import React from "react";
import { render } from "@testing-library/react";
import mockFs from "mock-fs";
import { render, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import { BottomBar } from "./bottom-bar";
import { StatusBarRegistry } from "../../../extensions/registries";
import { HotbarStore } from "../../../common/hotbar-store";
import { AppPaths } from "../../../common/app-paths";
import { CommandOverlay } from "../command-palette";
import { HotbarSwitchCommand } from "../hotbar/hotbar-switch-command";
import { ActiveHotbarName } from "./active-hotbar-name";
jest.mock("electron", () => ({
app: {
getPath: () => "/foo",
jest.mock("../command-palette", () => ({
CommandOverlay: {
open: jest.fn(),
},
}));
import { BottomBar } from "./bottom-bar";
import { StatusBarRegistry } from "../../../extensions/registries";
AppPaths.init();
jest.mock("electron", () => ({
app: {
getName: () => "lens",
setName: jest.fn(),
setPath: jest.fn(),
getPath: () => "tmp",
},
ipcMain: {
handle: jest.fn(),
on: jest.fn(),
removeAllListeners: jest.fn(),
off: jest.fn(),
send: jest.fn(),
},
}));
describe("<BottomBar />", () => {
beforeEach(() => {
const mockOpts = {
"tmp": {
"test-store.json": JSON.stringify({}),
},
};
mockFs(mockOpts);
StatusBarRegistry.createInstance();
HotbarStore.createInstance();
});
afterEach(() => {
StatusBarRegistry.resetInstance();
HotbarStore.resetInstance();
mockFs.restore();
});
it("renders w/o errors", () => {
@ -87,4 +120,73 @@ describe("<BottomBar />", () => {
expect(await getByTestId(testId)).toHaveTextContent(text);
});
it("show default hotbar name", () => {
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
{ item: () => <ActiveHotbarName/> },
]);
const { getByTestId } = render(<BottomBar />);
expect(getByTestId("current-hotbar-name")).toHaveTextContent("default");
});
it("show active hotbar name", () => {
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
{ item: () => <ActiveHotbarName/> },
]);
const { getByTestId } = render(<BottomBar />);
HotbarStore.getInstance().add({
id: "new",
name: "new",
}, { setActive: true });
expect(getByTestId("current-hotbar-name")).toHaveTextContent("new");
});
it("opens command palette on click", () => {
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
{ item: () => <ActiveHotbarName/> },
]);
const { getByTestId } = render(<BottomBar />);
const activeHotbar = getByTestId("current-hotbar-name");
fireEvent.click(activeHotbar);
expect(CommandOverlay.open).toHaveBeenCalledWith(<HotbarSwitchCommand />);
});
it("sort positioned items properly", () => {
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
{
components: {
Item: () => <div data-testid="sortedElem">right</div>,
},
},
{
components: {
Item: () => <div data-testid="sortedElem">right</div>,
position: "right",
},
},
{
components: {
Item: () => <div data-testid="sortedElem">left</div>,
position: "left",
},
},
{
components: {
Item: () => <div data-testid="sortedElem">left</div>,
position: "left",
},
},
]);
const { getAllByTestId } = render(<BottomBar />);
const elems = getAllByTestId("sortedElem");
const positions = elems.map(elem => elem.textContent);
expect(positions).toEqual(["left", "left", "right", "right"]);
});
});

View File

@ -19,11 +19,12 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import "./bottom-bar.scss";
import styles from "./bottom-bar.module.css";
import React from "react";
import { observer } from "mobx-react";
import { StatusBarRegistration, StatusBarRegistry } from "../../../extensions/registries";
import { cssNames } from "../../utils";
@observer
export class BottomBar extends React.Component {
@ -44,26 +45,36 @@ export class BottomBar extends React.Component {
return null;
}
items.sort(function sortLeftPositionFirst(a, b) {
return a.components?.position?.localeCompare(b.components?.position);
});
return (
<div className="extensions box grow flex gaps justify-flex-end">
<>
{items.map((registration, index) => {
if (!registration?.item && !registration?.components?.Item) {
return null;
}
return (
<div className="flex align-center gaps item" key={index}>
<div
className={cssNames(styles.item, {
[styles.onLeft]: registration.components?.position == "left",
[styles.onRight]: registration.components?.position != "left",
})}
key={index}
>
{this.renderRegisteredItem(registration)}
</div>
);
})}
</div>
</>
);
}
render() {
return (
<div className="BottomBar flex gaps">
<div className={styles.BottomBar}>
{this.renderRegisteredItems()}
</div>
);

View File

@ -20,7 +20,7 @@
*/
.ClusterManager {
--bottom-bar-height: 22px;
--bottom-bar-height: 21px;
--hotbar-width: 75px;
display: grid;

View File

@ -140,6 +140,7 @@ export class ConfirmDialog extends React.Component<ConfirmDialogProps> {
isOpen={dialogState.isOpen}
onClose={this.onClose}
close={this.close}
{...(dialogState.isOpen ? { "data-testid":"confirmation-dialog" } : {})}
>
<div className="confirm-content">
{icon} {message}
@ -158,6 +159,7 @@ export class ConfirmDialog extends React.Component<ConfirmDialogProps> {
label={labelOk}
onClick={prevDefault(this.ok)}
waiting={this.isSaving}
data-testid="confirm"
{...okButtonProps}
/>
</div>

View File

@ -41,6 +41,7 @@ export interface DialogProps {
modal?: boolean;
pinned?: boolean;
animated?: boolean;
"data-testid"?: string;
}
interface DialogState {
@ -149,7 +150,7 @@ export class Dialog extends React.PureComponent<DialogProps, DialogState> {
};
render() {
const { modal, animated, pinned } = this.props;
const { modal, animated, pinned, "data-testid": testId } = this.props;
let { className } = this.props;
className = cssNames("Dialog flex center", className, { modal, pinned });
@ -158,6 +159,7 @@ export class Dialog extends React.PureComponent<DialogProps, DialogState> {
className={className}
onClick={stopPropagation}
ref={this.ref}
data-testid={testId}
>
<div className="box" ref={e => this.contentElem = e}>
{this.props.children}

View File

@ -35,7 +35,7 @@ import { InfoPanel } from "./info-panel";
import * as resourceApplierApi from "../../../common/k8s-api/endpoints/resource-applier.api";
import { Notifications } from "../notifications";
import logger from "../../../common/logger";
import type { KubeJsonApiData, KubeJsonApiError } from "../../../common/k8s-api/kube-json-api";
import type { KubeJsonApiData } from "../../../common/k8s-api/kube-json-api";
import { getDetailsUrl } from "../kube-detail-params";
import { apiManager } from "../../../common/k8s-api/api-manager";
import { prevDefault } from "../../utils";
@ -130,9 +130,7 @@ export class CreateResource extends React.Component<Props> {
</p>,
);
} catch (error) {
const failureReason = (error as KubeJsonApiError).reason.toString();
Notifications.error(failureReason);
Notifications.error(error?.toString() ?? "Unknown error occured");
}
});

View File

@ -43,7 +43,8 @@
.Icon {
--size: 16px;
padding: 0 4px;
padding: 0 4px 0 0px;
margin: 0px 4px;
&:hover {
box-shadow: none;

View File

@ -0,0 +1,371 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`kube-object-menu given kube object renders 1`] = `
<body>
<div>
<div>
<ul
class="Animate opacity Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<li>
Some menu item
</li>
<li
class="MenuItem"
data-testid="menu-action-remove"
tabindex="0"
>
<i
class="Icon material interactive focusable"
id="tooltip_target_4"
tabindex="0"
>
<span
class="icon"
data-icon-name="delete"
>
delete
</span>
<div />
</i>
<span
class="title"
>
Delete
</span>
</li>
</ul>
</div>
</div>
<div
class="Animate opacity-scale Dialog flex center ConfirmDialog modal"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
/>
<div
class="Tooltip narrow invisible formatter"
>
Delete
</div>
</body>
`;
exports[`kube-object-menu given kube object when removing kube object renders 1`] = `
<body>
<div>
<div>
<ul
class="Animate opacity Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<li>
Some menu item
</li>
<li
class="MenuItem"
data-testid="menu-action-remove"
tabindex="0"
>
<i
class="Icon material interactive focusable"
id="tooltip_target_8"
tabindex="0"
>
<span
class="icon"
data-icon-name="delete"
>
delete
</span>
<div />
</i>
<span
class="title"
>
Delete
</span>
</li>
</ul>
</div>
</div>
<div
class="Animate opacity-scale Dialog flex center ConfirmDialog modal"
data-testid="confirmation-dialog"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<div
class="confirm-content"
>
<i
class="Icon material focusable big"
>
<span
class="icon"
data-icon-name="warning"
>
warning
</span>
</i>
<div>
<p>
Remove
some-kind
<b>
some-namespace/some-name
</b>
from
<b>
Some name
</b>
?
</p>
</div>
</div>
<div
class="confirm-buttons"
>
<button
class="Button cancel plain"
type="button"
>
Cancel
</button>
<button
class="Button ok primary"
data-testid="confirm"
type="button"
>
Remove
</button>
</div>
</div>
</div>
<div
class="Tooltip narrow invisible formatter"
>
Delete
</div>
</body>
`;
exports[`kube-object-menu given kube object with namespace when removing kube object, renders confirmation dialog with namespace 1`] = `
<body>
<div>
<div>
<ul
class="Animate opacity Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<li>
Some menu item
</li>
<li
class="MenuItem"
data-testid="menu-action-remove"
tabindex="0"
>
<i
class="Icon material interactive focusable"
id="tooltip_target_33"
tabindex="0"
>
<span
class="icon"
data-icon-name="delete"
>
delete
</span>
<div />
</i>
<span
class="title"
>
Delete
</span>
</li>
</ul>
</div>
</div>
<div
class="Animate opacity-scale Dialog flex center ConfirmDialog modal"
data-testid="confirmation-dialog"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<div
class="confirm-content"
>
<i
class="Icon material focusable big"
>
<span
class="icon"
data-icon-name="warning"
>
warning
</span>
</i>
<div>
<p>
Remove
some-kind
<b>
some-namespace/some-name
</b>
from
<b>
Some name
</b>
?
</p>
</div>
</div>
<div
class="confirm-buttons"
>
<button
class="Button cancel plain"
type="button"
>
Cancel
</button>
<button
class="Button ok primary"
data-testid="confirm"
type="button"
>
Remove
</button>
</div>
</div>
</div>
<div
class="Tooltip narrow invisible formatter"
>
Delete
</div>
</body>
`;
exports[`kube-object-menu given kube object without namespace when removing kube object, renders confirmation dialog without namespace 1`] = `
<body>
<div>
<div>
<ul
class="Animate opacity Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<li>
Some menu item
</li>
<li
class="MenuItem"
data-testid="menu-action-remove"
tabindex="0"
>
<i
class="Icon material interactive focusable"
id="tooltip_target_41"
tabindex="0"
>
<span
class="icon"
data-icon-name="delete"
>
delete
</span>
<div />
</i>
<span
class="title"
>
Delete
</span>
</li>
</ul>
</div>
</div>
<div
class="Animate opacity-scale Dialog flex center ConfirmDialog modal"
data-testid="confirmation-dialog"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
>
<div
class="box"
>
<div
class="confirm-content"
>
<i
class="Icon material focusable big"
>
<span
class="icon"
data-icon-name="warning"
>
warning
</span>
</i>
<div>
<p>
Remove
some-kind
<b>
some-name
</b>
from
<b>
Some name
</b>
?
</p>
</div>
</div>
<div
class="confirm-buttons"
>
<button
class="Button cancel plain"
type="button"
>
Cancel
</button>
<button
class="Button ok primary"
data-testid="confirm"
type="button"
>
Remove
</button>
</div>
</div>
</div>
<div
class="Tooltip narrow invisible formatter"
>
Delete
</div>
</body>
`;
exports[`kube-object-menu given no kube object, renders 1`] = `
<body>
<div>
<ul
class="Animate opacity Menu MenuActions flex KubeObjectMenu toolbar gaps right bottom"
style="--enter-duration: 100ms; --leave-duration: 100ms;"
/>
</div>
</body>
`;

View File

@ -0,0 +1,32 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type { ApiManager } from "../../../../common/k8s-api/api-manager";
import { apiManager } from "../../../../common/k8s-api/api-manager";
import type { Injectable } from "@ogre-tools/injectable";
import { lifecycleEnum } from "@ogre-tools/injectable";
const apiManagerInjectable: Injectable<ApiManager> = {
getDependencies: () => ({}),
instantiate: () => apiManager,
lifecycle: lifecycleEnum.singleton,
};
export default apiManagerInjectable;

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type { Injectable } from "@ogre-tools/injectable";
import { lifecycleEnum } from "@ogre-tools/injectable";
import type { Cluster } from "../../../../main/cluster";
import clusterInjectable from "./cluster.injectable";
interface Dependencies {
cluster: Cluster;
}
const clusterNameInjectable: Injectable<string | undefined, Dependencies> = {
getDependencies: di => ({
cluster: di.inject(clusterInjectable),
}),
instantiate: ({ cluster }) => cluster?.name,
lifecycle: lifecycleEnum.transient,
};
export default clusterNameInjectable;

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getActiveClusterEntity } from "../../../api/catalog-entity-registry";
import type { Injectable } from "@ogre-tools/injectable";
import { lifecycleEnum } from "@ogre-tools/injectable";
import type { Cluster } from "../../../../main/cluster";
const clusterInjectable: Injectable<Cluster | null> = {
getDependencies: () => ({}),
instantiate: () => getActiveClusterEntity(),
lifecycle: lifecycleEnum.transient,
};
export default clusterInjectable;

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { editResourceTab } from "../../dock/edit-resource.store";
import type { Injectable } from "@ogre-tools/injectable";
import { lifecycleEnum } from "@ogre-tools/injectable";
const editResourceTabInjectable: Injectable<typeof editResourceTab> = {
getDependencies: () => ({}),
instantiate: () => editResourceTab,
lifecycle: lifecycleEnum.singleton,
};
export default editResourceTabInjectable;

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { hideDetails } from "../../kube-detail-params";
import type { Injectable } from "@ogre-tools/injectable";
import { lifecycleEnum } from "@ogre-tools/injectable";
export const hideDetailsInjectable: Injectable<typeof hideDetails> = {
getDependencies: () => ({}),
instantiate: () => hideDetails,
lifecycle: lifecycleEnum.singleton,
};
export default hideDetailsInjectable;

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type { KubeObjectMenuRegistry } from "../../../../../extensions/registries";
import type { KubeObject } from "../../../../../common/k8s-api/kube-object";
export interface Dependencies {
kubeObjectMenuRegistry: KubeObjectMenuRegistry;
}
export interface InstantiationParameter {
kubeObject: KubeObject;
}
export const getKubeObjectMenuItems = (
{ kubeObjectMenuRegistry }: Dependencies,
{ kubeObject }: InstantiationParameter,
) => {
if (!kubeObject) {
return [];
}
return kubeObjectMenuRegistry
.getItemsForKind(kubeObject.kind, kubeObject.apiVersion)
.map(item => item.components.MenuItem);
};

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { Injectable, lifecycleEnum } from "@ogre-tools/injectable";
import kubeObjectMenuRegistryInjectable from "./kube-object-menu-registry.injectable";
import {
InstantiationParameter,
Dependencies,
getKubeObjectMenuItems,
} from "./get-kube-object-menu-items";
const kubeObjectMenuItemsInjectable: Injectable<
ReturnType<typeof getKubeObjectMenuItems>,
Dependencies,
InstantiationParameter
> = {
getDependencies: di => ({
kubeObjectMenuRegistry: di.inject(kubeObjectMenuRegistryInjectable),
}),
instantiate: getKubeObjectMenuItems,
lifecycle: lifecycleEnum.transient,
};
export default kubeObjectMenuItemsInjectable;

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { KubeObjectMenuRegistry } from "../../../../../extensions/registries";
import type { Injectable } from "@ogre-tools/injectable";
import { lifecycleEnum } from "@ogre-tools/injectable";
const kubeObjectMenuRegistryInjectable: Injectable<KubeObjectMenuRegistry> = {
getDependencies: () => ({}),
instantiate: () => KubeObjectMenuRegistry.getInstance(),
lifecycle: lifecycleEnum.singleton,
};
export default kubeObjectMenuRegistryInjectable;

View File

@ -19,4 +19,5 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export * from "./kube-object-menu";
export type { KubeObjectMenuProps } from "./kube-object-menu";
export { KubeObjectMenu } from "./kube-object-menu-container";

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getInjectedComponent } from "@ogre-tools/injectable-react";
import KubeObjectMenuInjectable from "./kube-object-menu.injectable";
export const KubeObjectMenu = getInjectedComponent(KubeObjectMenuInjectable);

View File

@ -0,0 +1,60 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import React from "react";
import {
KubeObjectMenu,
KubeObjectMenuDependencies,
KubeObjectMenuProps,
} from "./kube-object-menu";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { lifecycleEnum, Injectable } from "@ogre-tools/injectable";
import apiManagerInjectable from "./dependencies/api-manager.injectable";
import clusterNameInjectable from "./dependencies/cluster-name.injectable";
import editResourceTabInjectable from "./dependencies/edit-resource-tab.injectable";
import hideDetailsInjectable from "./dependencies/hide-details.injectable";
import kubeObjectMenuItemsInjectable from "./dependencies/kube-object-menu-items/kube-object-menu-items.injectable";
const KubeObjectMenuInjectable: Injectable<
JSX.Element,
KubeObjectMenuDependencies<KubeObject>,
KubeObjectMenuProps<KubeObject>
> = {
getDependencies: (di, props) => ({
clusterName: di.inject(clusterNameInjectable),
apiManager: di.inject(apiManagerInjectable),
editResourceTab: di.inject(editResourceTabInjectable),
hideDetails: di.inject(hideDetailsInjectable),
kubeObjectMenuItems: di.inject(kubeObjectMenuItemsInjectable, {
kubeObject: props.object,
}),
}),
instantiate: (dependencies, props) => (
<KubeObjectMenu {...dependencies} {...props} />
),
lifecycle: lifecycleEnum.transient,
};
export default KubeObjectMenuInjectable;

View File

@ -0,0 +1,284 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import React from "react";
import { screen } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import { KubeObject } from "../../../common/k8s-api/kube-object";
import userEvent from "@testing-library/user-event";
import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
import type { KubeObjectMenuRegistration } from "../../../extensions/registries";
import { KubeObjectMenuRegistry } from "../../../extensions/registries";
import { ConfirmDialog } from "../confirm-dialog";
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
import clusterInjectable from "./dependencies/cluster.injectable";
import hideDetailsInjectable from "./dependencies/hide-details.injectable";
import editResourceTabInjectable from "./dependencies/edit-resource-tab.injectable";
import { TabKind } from "../dock/dock.store";
import kubeObjectMenuRegistryInjectable from "./dependencies/kube-object-menu-items/kube-object-menu-registry.injectable";
import { DiRender, renderFor } from "../test-utils/renderFor";
import type { Cluster } from "../../../main/cluster";
import type { ApiManager } from "../../../common/k8s-api/api-manager";
import apiManagerInjectable from "./dependencies/api-manager.injectable";
import { KubeObjectMenu } from "./index";
describe("kube-object-menu", () => {
let di: ConfigurableDependencyInjectionContainer;
let render: DiRender;
beforeEach(() => {
di = getDiForUnitTesting();
// TODO: Remove global shared state
KubeObjectMenuRegistry.resetInstance();
KubeObjectMenuRegistry.createInstance();
render = renderFor(di);
di.override(clusterInjectable, {
name: "Some name",
} as Cluster);
di.override(apiManagerInjectable, {
// eslint-disable-next-line unused-imports/no-unused-vars-ts
getStore: api => undefined,
} as ApiManager);
di.override(hideDetailsInjectable, () => {});
di.override(editResourceTabInjectable, () => ({
id: "irrelevant",
kind: TabKind.TERMINAL,
pinned: false,
title: "irrelevant",
}));
addDynamicMenuItem({
di,
apiVersions: ["some-api-version"],
kind: "some-kind",
});
addDynamicMenuItem({
di,
apiVersions: ["some-unrelated-api-version"],
kind: "some-kind",
});
addDynamicMenuItem({
di,
apiVersions: ["some-api-version"],
kind: "some-unrelated-kind",
});
});
it("given no cluster, does not crash", () => {
di.override(clusterInjectable, null);
expect(() => {
render(<KubeObjectMenu object={null} toolbar={true} />);
}).not.toThrow();
});
it("given no kube object, renders", () => {
const { baseElement } = render(
<KubeObjectMenu object={null} toolbar={true} />,
);
expect(baseElement).toMatchSnapshot();
});
describe("given kube object", () => {
let baseElement: Element;
let removeActionMock: AsyncFnMock<Function>;
beforeEach(async () => {
const objectStub = KubeObject.create({
apiVersion: "some-api-version",
kind: "some-kind",
metadata: {
uid: "some-uid",
name: "some-name",
resourceVersion: "some-resource-version",
namespace: "some-namespace",
},
});
removeActionMock = asyncFn();
({ baseElement } = render(
<div>
<ConfirmDialog />
<KubeObjectMenu
object={objectStub}
toolbar={true}
removeAction={removeActionMock}
/>
</div>,
));
});
it("renders", () => {
expect(baseElement).toMatchSnapshot();
});
it("does not open a confirmation dialog yet", () => {
expect(screen.queryByTestId("confirmation-dialog")).toBeNull();
});
describe("when removing kube object", () => {
beforeEach(() => {
const menuItem = screen.getByTestId("menu-action-remove");
userEvent.click(menuItem);
});
it("renders", () => {
expect(baseElement).toMatchSnapshot();
});
it("opens a confirmation dialog", () => {
screen.getByTestId("confirmation-dialog");
});
describe("when remove is confirmed", () => {
beforeEach(() => {
const confirmRemovalButton = screen.getByTestId("confirm");
userEvent.click(confirmRemovalButton);
});
it("calls for removal of the kube object", () => {
expect(removeActionMock).toHaveBeenCalledWith();
});
it("does not close the confirmation dialog yet", () => {
screen.getByTestId("confirmation-dialog");
});
it("when removal resolves, closes the confirmation dialog", async () => {
await removeActionMock.resolve();
expect(screen.queryByTestId("confirmation-dialog")).toBeNull();
});
});
});
});
describe("given kube object with namespace", () => {
let baseElement: Element;
beforeEach(async () => {
const objectStub = KubeObject.create({
apiVersion: "some-api-version",
kind: "some-kind",
metadata: {
uid: "some-uid",
name: "some-name",
resourceVersion: "some-resource-version",
namespace: "some-namespace",
},
});
({ baseElement } = render(
<div>
<ConfirmDialog />
<KubeObjectMenu
object={objectStub}
toolbar={true}
removeAction={() => {}}
/>
</div>,
));
});
it("when removing kube object, renders confirmation dialog with namespace", () => {
const menuItem = screen.getByTestId("menu-action-remove");
userEvent.click(menuItem);
expect(baseElement).toMatchSnapshot();
});
});
describe("given kube object without namespace", () => {
let baseElement: Element;
beforeEach(async () => {
const objectStub = KubeObject.create({
apiVersion: "some-api-version",
kind: "some-kind",
metadata: {
uid: "some-uid",
name: "some-name",
resourceVersion: "some-resource-version",
namespace: undefined,
},
});
({ baseElement } = render(
<div>
<ConfirmDialog />
<KubeObjectMenu
object={objectStub}
toolbar={true}
removeAction={() => {}}
/>
</div>,
));
});
it("when removing kube object, renders confirmation dialog without namespace", () => {
const menuItem = screen.getByTestId("menu-action-remove");
userEvent.click(menuItem);
expect(baseElement).toMatchSnapshot();
});
});
});
const addDynamicMenuItem = ({
di,
apiVersions,
kind,
}: {
di: ConfigurableDependencyInjectionContainer;
apiVersions: string[];
kind: string;
}) => {
const MenuItemComponent: React.FC = () => <li>Some menu item</li>;
const dynamicMenuItemStub: KubeObjectMenuRegistration = {
apiVersions,
kind,
components: { MenuItem: MenuItemComponent },
};
const kubeObjectMenuRegistry = di.inject(kubeObjectMenuRegistryInjectable);
kubeObjectMenuRegistry.add(dynamicMenuItemStub);
};

View File

@ -22,25 +22,37 @@
import React from "react";
import { boundMethod, cssNames } from "../../utils";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { editResourceTab } from "../dock/edit-resource.store";
import { MenuActions, MenuActionsProps } from "../menu/menu-actions";
import { hideDetails } from "../kube-detail-params";
import { apiManager } from "../../../common/k8s-api/api-manager";
import { KubeObjectMenuRegistry } from "../../../extensions/registries/kube-object-menu-registry";
import { MenuActions, MenuActionsProps } from "../menu";
import identity from "lodash/identity";
import type { ApiManager } from "../../../common/k8s-api/api-manager";
export interface KubeObjectMenuProps<T> extends MenuActionsProps {
object: T | null | undefined;
export interface KubeObjectMenuDependencies<TKubeObject> {
apiManager: ApiManager;
kubeObjectMenuItems: React.ElementType[];
clusterName: string;
hideDetails: () => void;
editResourceTab: (kubeObject: TKubeObject) => void;
}
export interface KubeObjectMenuProps<TKubeObject> extends MenuActionsProps {
object: TKubeObject | null | undefined;
editable?: boolean;
removable?: boolean;
}
export class KubeObjectMenu<T extends KubeObject> extends React.Component<KubeObjectMenuProps<T>> {
export interface KubeObjectMenuPropsAndDependencies<TKubeObject>
extends KubeObjectMenuProps<TKubeObject>,
KubeObjectMenuDependencies<TKubeObject> {}
export class KubeObjectMenu<
TKubeObject extends KubeObject,
> extends React.Component<KubeObjectMenuPropsAndDependencies<TKubeObject>> {
get store() {
const { object } = this.props;
if (!object) return null;
return apiManager.getStore(object.selfLink);
return this.props.apiManager.getStore(object.selfLink);
}
get isEditable() {
@ -53,13 +65,13 @@ export class KubeObjectMenu<T extends KubeObject> extends React.Component<KubeOb
@boundMethod
async update() {
hideDetails();
editResourceTab(this.props.object);
this.props.hideDetails();
this.props.editResourceTab(this.props.object);
}
@boundMethod
async remove() {
hideDetails();
this.props.hideDetails();
const { object, removeAction } = this.props;
if (removeAction) await removeAction();
@ -74,28 +86,27 @@ export class KubeObjectMenu<T extends KubeObject> extends React.Component<KubeOb
return null;
}
const breadcrumbParts = [object.getNs(), object.getName()];
const breadcrumb = breadcrumbParts.filter(identity).join("/");
return (
<p>Remove {object.kind} <b>{object.getName()}</b>?</p>
<p>
Remove {object.kind} <b>{breadcrumb}</b> from <b>{this.props.clusterName}</b>?
</p>
);
}
getMenuItems(): React.ReactChild[] {
const { object, toolbar } = this.props;
if (!object) {
return [];
}
return KubeObjectMenuRegistry
.getInstance()
.getItemsForKind(object.kind, object.apiVersion)
.map(({ components: { MenuItem }}, index) => (
<MenuItem
object={object}
key={`menu-item-${index}`}
toolbar={toolbar}
/>
));
return this.props.kubeObjectMenuItems.map((MenuItem, index) => (
<MenuItem
object={object}
toolbar={toolbar}
key={`menu-item-${index}`}
/>
));
}
render() {

View File

@ -30,7 +30,6 @@ import { Icon, IconProps } from "../icon";
import { Menu, MenuItem, MenuProps } from "../menu";
import uniqueId from "lodash/uniqueId";
import isString from "lodash/isString";
import { RenderDelay } from "../render-delay/render-delay";
export interface MenuActionsProps extends Partial<MenuProps> {
className?: string;
@ -125,32 +124,31 @@ export class MenuActions extends React.Component<MenuActionsProps> {
return (
<>
{this.renderTriggerIcon()}
<RenderDelay>
<Menu
htmlFor={this.id}
isOpen={this.isOpen} open={this.toggle} close={this.toggle}
className={menuClassName}
usePortal={autoClose}
closeOnScroll={autoClose}
closeOnClickItem={autoCloseOnSelect ?? autoClose }
closeOnClickOutside={autoClose}
{...menuProps}
>
{children}
{updateAction && (
<MenuItem onClick={updateAction}>
<Icon material="edit" interactive={toolbar} tooltip="Edit"/>
<span className="title">Edit</span>
</MenuItem>
)}
{removeAction && (
<MenuItem onClick={this.remove}>
<Icon material="delete" interactive={toolbar} tooltip="Delete"/>
<span className="title">Delete</span>
</MenuItem>
)}
</Menu>
</RenderDelay>
<Menu
htmlFor={this.id}
isOpen={this.isOpen} open={this.toggle} close={this.toggle}
className={menuClassName}
usePortal={autoClose}
closeOnScroll={autoClose}
closeOnClickItem={autoCloseOnSelect ?? autoClose }
closeOnClickOutside={autoClose}
{...menuProps}
>
{children}
{updateAction && (
<MenuItem onClick={updateAction}>
<Icon material="edit" interactive={toolbar} tooltip="Edit"/>
<span className="title">Edit</span>
</MenuItem>
)}
{removeAction && (
<MenuItem onClick={this.remove} data-testid="menu-action-remove">
<Icon material="delete" interactive={toolbar} tooltip="Delete"/>
<span className="title">Delete</span>
</MenuItem>
)}
</Menu>
</>
);
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import React from "react";
import {
render as testingLibraryRender,
RenderResult,
} from "@testing-library/react";
import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
import { DiContextProvider } from "@ogre-tools/injectable-react";
export type DiRender = (ui: React.ReactElement) => RenderResult;
type DiRenderFor = (
di: ConfigurableDependencyInjectionContainer,
) => DiRender;
export const renderFor: DiRenderFor = di => ui =>
testingLibraryRender(
<DiContextProvider value={{ di }}>{ui}</DiContextProvider>,
);

View File

@ -61,11 +61,11 @@ export function initCatalogCategoryRegistryEntries() {
ctx.menuItems.push(
{
icon: "create_new_folder",
title: "Sync kubeconfig folders(s)",
title: "Sync kubeconfig folder(s)",
defaultAction: true,
onClick: async () => {
await PathPicker.pick({
label: "Sync folders(s)",
label: "Sync folder(s)",
buttonLabel: "Sync",
properties: ["showHiddenFiles", "multiSelections", "openDirectory"],
onPick: addSyncEntries,

View File

@ -30,3 +30,4 @@ export * from "./registries";
export * from "./welcome-menu-registry";
export * from "./workloads-overview-detail-registry";
export * from "./catalog-category-registry";
export * from "./status-bar-registry";

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import React from "react";
import { StatusBarRegistry } from "../../extensions/registries";
import { ActiveHotbarName } from "../components/cluster-manager/active-hotbar-name";
export function initStatusBarRegistry() {
StatusBarRegistry.getInstance().add([
{
components: {
Item: () => <ActiveHotbarName/>,
position: "left",
},
},
]);
}

254
yarn.lock
View File

@ -7,6 +7,11 @@
resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.1.1.tgz#9274ec7460652f9c632c59addf24efb1684ef876"
integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ==
"@async-fn/jest@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@async-fn/jest/-/jest-1.5.0.tgz#dda820de3e48eca6d5b17b00a01bdb060dfcc233"
integrity sha512-IeuTZj1TdoBS64fpNfHG9efw6dW3UQuIXfEv0qhxyNro3368kKz9E/eO46zvPmQBxbBCox3RCbKexAJH1WAY+Q==
"@babel/code-frame@7.12.11":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
@ -14,13 +19,20 @@
dependencies:
"@babel/highlight" "^7.10.4"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.1", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.8.3":
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.1", "@babel/code-frame@^7.10.4":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb"
integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==
dependencies:
"@babel/highlight" "^7.14.5"
"@babel/code-frame@^7.8.3":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431"
integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==
dependencies:
"@babel/highlight" "^7.16.0"
"@babel/core@^7.1.0", "@babel/core@^7.7.5":
version "7.10.2"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.2.tgz#bd6786046668a925ac2bd2fd95b579b92a23b36a"
@ -148,6 +160,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==
"@babel/helper-validator-identifier@^7.15.7":
version "7.15.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389"
integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==
"@babel/helpers@^7.10.1":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.1.tgz#a6827b7cb975c9d9cef5fd61d919f60d8844a973"
@ -175,6 +192,15 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/highlight@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a"
integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==
dependencies:
"@babel/helper-validator-identifier" "^7.15.7"
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.10.1", "@babel/parser@^7.10.2":
version "7.10.2"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.2.tgz#871807f10442b92ff97e4783b9b54f6a0ca812d0"
@ -953,19 +979,19 @@
dependencies:
lodash "^4.17.21"
"@ogre-tools/injectable-react@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@ogre-tools/injectable-react/-/injectable-react-1.2.1.tgz#886fbb9f9816d68daf41b6fd7ff5def6eae833b4"
integrity sha512-kr9Q2T/VyhtUG8EbfzpFPk2ndwKQl9WHzqEfp8fasAXMNmUfUnyWs6iPNoJiuy2gh4/CNBvlFB8c647ls6/jUA==
"@ogre-tools/injectable-react@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@ogre-tools/injectable-react/-/injectable-react-1.3.1.tgz#dec3829ac8cf295c32cfe636ca2cd39a495d56ce"
integrity sha512-5jHL9Zcb3QkrttdzqJpN6iCXaV2+fEuDNigwH6NJ3uyV1iQWuRIctnlXxfa9qtZESwaAz7o0hAwkyqEl7YSA4g==
dependencies:
"@ogre-tools/fp" "^1.0.2"
"@ogre-tools/injectable" "^1.2.1"
"@ogre-tools/injectable" "^1.3.0"
lodash "^4.17.21"
"@ogre-tools/injectable@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@ogre-tools/injectable/-/injectable-1.2.1.tgz#f3eb481806dd6e53af8d9d37f8b20f3c0d875a60"
integrity sha512-bfTlnT08uDydE0i5GxJ9SIoRKfNYVabQRrZfBraZi2rs3zx+DOpcZrJjhjDoSCzIr6C2azySuyxn1h8x8CMUPw==
"@ogre-tools/injectable@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@ogre-tools/injectable/-/injectable-1.3.0.tgz#87d329a81575c9345b3af5c1afb0b45537f8f70e"
integrity sha512-rBy8HSExUy1r53ATvk823GXevwultKuSn3mmyRlIj7opJDVRp7Usx0bvOPs+X169jmAZNzsT6HBXbDLXt4Jl4A==
dependencies:
"@ogre-tools/fp" "^1.0.2"
lodash "^4.17.21"
@ -1184,14 +1210,14 @@
lz-string "^1.4.4"
pretty-format "^26.6.2"
"@testing-library/jest-dom@^5.15.0":
version "5.15.0"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.15.0.tgz#4f5295dbc476a14aec3b07176434b3d51aae5da7"
integrity sha512-lOMuQidnL1tWHLEWIhL6UvSZC1Qt3OkNe1khvi2h6xFiqpe5O8arYs46OU0qyUGq0cSTbroQyMktYNXu3a7sAA==
"@testing-library/jest-dom@^5.16.1":
version "5.16.1"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.1.tgz#3db7df5ae97596264a7da9696fe14695ba02e51f"
integrity sha512-ajUJdfDIuTCadB79ukO+0l8O+QwN0LiSxDaYUTI4LndbbUsGi6rWU1SCexXzBA2NSjlVB9/vbkasQIL3tmPBjw==
dependencies:
"@babel/runtime" "^7.9.2"
"@types/testing-library__jest-dom" "^5.9.1"
aria-query "^4.2.2"
aria-query "^5.0.0"
chalk "^3.0.0"
css "^3.0.0"
css.escape "^1.5.1"
@ -1518,10 +1544,10 @@
jest-diff "^26.0.0"
pretty-format "^26.0.0"
"@types/js-yaml@^4.0.1", "@types/js-yaml@^4.0.4":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.4.tgz#cc38781257612581a1a0eb25f1709d2b06812fce"
integrity sha512-AuHubXUmg0AzkXH0Mx6sIxeY/1C110mm/EkE/gB1sTRz3h2dao2W/63q42SlVST+lICxz5Oki2hzYA6+KnnieQ==
"@types/js-yaml@^4.0.1", "@types/js-yaml@^4.0.5":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138"
integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==
"@types/jsdom@^16.2.13":
version "16.2.13"
@ -1532,7 +1558,12 @@
"@types/parse5" "*"
"@types/tough-cookie" "*"
"@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6":
"@types/json-schema@^7.0.4":
version "7.0.9"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6":
version "7.0.7"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
@ -1951,10 +1982,10 @@
resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.10.0.tgz#5cb0dff2a5f616fc8e0c61b482bf01fa20a03cec"
integrity sha512-ZAbqul7QAKpM2h1PFGa5ETN27ulmqtj0QviYHasw9LffvXZvVHuraOx/FOsIPPDNGZN0Qo1nASxxSfMYOtSoCw==
"@types/url-parse@^1.4.4":
version "1.4.4"
resolved "https://registry.yarnpkg.com/@types/url-parse/-/url-parse-1.4.4.tgz#ebeb0ec8b581318739cf73e9f9b186f610764255"
integrity sha512-KtQLad12+4T/NfSxpoDhmr22+fig3T7/08QCgmutYA6QSznSRmEtuL95GrhVV40/0otTEdFc+etRcCTqhh1q5Q==
"@types/url-parse@^1.4.5":
version "1.4.5"
resolved "https://registry.yarnpkg.com/@types/url-parse/-/url-parse-1.4.5.tgz#bda0b813cc8f79d767dc8ca645fdabc238c6ae77"
integrity sha512-8Wje3itJpk/FX+QItca9vjNLjGx5jlEYBw/CpMi03Fphk2DSVeZDUqWTE81BeCI5Bl6Z+zmA1O9L/8e3ZUSeLg==
"@types/uuid@^8.3.3":
version "8.3.3"
@ -1998,10 +2029,10 @@
"@types/source-list-map" "*"
source-map "^0.7.3"
"@types/webpack@*", "@types/webpack@^4", "@types/webpack@^4.41.31", "@types/webpack@^4.41.8":
version "4.41.31"
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.31.tgz#c35f252a3559ddf9c85c0d8b0b42019025e581aa"
integrity sha512-/i0J7sepXFIp1ZT7FjUGi1eXMCg8HCCzLJEQkKsOtbJFontsJLolBcDC+3qxn5pPwiCt1G0ZdRmYRzNBtvpuGQ==
"@types/webpack@*", "@types/webpack@^4", "@types/webpack@^4.41.32", "@types/webpack@^4.41.8":
version "4.41.32"
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.32.tgz#a7bab03b72904070162b2f169415492209e94212"
integrity sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg==
dependencies:
"@types/node" "*"
"@types/tapable" "^1"
@ -2362,7 +2393,7 @@ agent-base@4, agent-base@^4.3.0:
dependencies:
es6-promisify "^5.0.0"
agent-base@6:
agent-base@6, agent-base@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
@ -2447,7 +2478,12 @@ ansi-escapes@^4.2.1:
dependencies:
type-fest "^0.11.0"
ansi-html@0.0.7, ansi-html@^0.0.7:
ansi-html-community@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41"
integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==
ansi-html@^0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4=
@ -2617,6 +2653,11 @@ aria-query@^4.2.2:
"@babel/runtime" "^7.10.2"
"@babel/runtime-corejs3" "^7.10.2"
aria-query@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c"
integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
@ -3879,11 +3920,16 @@ commander@^5.0.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
commander@^6.0.0, commander@^6.1.0:
commander@^6.0.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
commander@^8.2.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@ -4462,13 +4508,6 @@ decompress-response@^3.3.0:
dependencies:
mimic-response "^1.0.0"
decompress-response@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986"
integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
dependencies:
mimic-response "^2.0.0"
decompress-response@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
@ -7160,6 +7199,11 @@ immer@^9.0.6:
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.6.tgz#7a96bf2674d06c8143e327cbf73539388ddf1a73"
integrity sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==
immutable@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
import-cwd@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-3.0.0.tgz#20845547718015126ea9b3676b7592fb8bd4cf92"
@ -9246,9 +9290,9 @@ mem@^1.1.0:
mimic-fn "^1.0.0"
memfs@^3.1.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.2.2.tgz#5de461389d596e3f23d48bb7c2afb6161f4df40e"
integrity sha512-RE0CwmIM3CEvpcdK3rZ19BC4E6hv9kADkMN5rPduRak58cNArWLi/9jFLsa4rhsjfVxMP3v0jO7FHXq7SvFY5Q==
version "3.3.0"
resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.3.0.tgz#4da2d1fc40a04b170a56622c7164c6be2c4cbef2"
integrity sha512-BEE62uMfKOavX3iG7GYX43QJ+hAeeWnwIAuJ/R6q96jaMtiLzhsxHJC8B1L7fK7Pt/vXDRwb3SG/yBpNGDPqzg==
dependencies:
fs-monkey "1.0.3"
@ -9383,11 +9427,6 @@ mimic-response@^1.0.0, mimic-response@^1.0.1:
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
mimic-response@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
mimic-response@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
@ -9549,10 +9588,10 @@ modern-normalize@^1.1.0:
resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.1.0.tgz#da8e80140d9221426bd4f725c6e11283d34f90b7"
integrity sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==
moment-timezone@^0.5.33:
version "0.5.33"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c"
integrity sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==
moment-timezone@^0.5.34:
version "0.5.34"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c"
integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==
dependencies:
moment ">= 2.9.0"
@ -9701,12 +9740,12 @@ no-case@^3.0.3:
lower-case "^2.0.1"
tslib "^1.10.0"
node-abi@^2.21.0:
version "2.30.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.30.0.tgz#8be53bf3e7945a34eea10e0fc9a5982776cf550b"
integrity sha512-g6bZh3YCKQRdwuO/tSZZYJAw622SjsRfJ2X0Iy4sSOHZ34/sPPdVBn8fev2tj7njzLwuqPw9uMtGsGkO5kIQvg==
node-abi@^3.3.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.5.0.tgz#26e8b7b251c3260a5ac5ba5aef3b4345a0229248"
integrity sha512-LtHvNIBgOy5mO8mPEUtkCW/YCRWYEKshIvqhe1GHHyXEHEB5mgICyYnAcl4qan3uFeRROErKGzatFHPf6kDxWw==
dependencies:
semver "^5.4.1"
semver "^7.3.5"
node-addon-api@^1.6.3:
version "1.7.2"
@ -10873,12 +10912,12 @@ pkg-up@^3.1.0:
dependencies:
find-up "^3.0.0"
playwright@^1.15.2:
version "1.15.2"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.15.2.tgz#b350056d1fffbe5de5b1bdaca6ab73e35758baad"
integrity sha512-+Z+7ckihyxR6rK5q8DWC6eUbKARfXpyxpjNcoJfgwSr64lAOzjhyFQiPC/JkdIqhsLgZjxpWfl1S7fLb+wPkgA==
playwright-core@=1.17.1:
version "1.17.1"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.17.1.tgz#a16e0f89284a0ed8ae6d77e1c905c84b8a2ba022"
integrity sha512-C3c8RpPiC3qr15fRDN6dx6WnUkPLFmST37gms2aoHPDRvp7EaGDPMMZPpqIm/QWB5J40xDrQCD4YYHz2nBTojQ==
dependencies:
commander "^6.1.0"
commander "^8.2.0"
debug "^4.1.1"
extract-zip "^2.0.1"
https-proxy-agent "^5.0.0"
@ -10889,10 +10928,19 @@ playwright@^1.15.2:
proper-lockfile "^4.1.1"
proxy-from-env "^1.1.0"
rimraf "^3.0.2"
socks-proxy-agent "^6.1.0"
stack-utils "^2.0.3"
ws "^7.4.6"
yauzl "^2.10.0"
yazl "^2.5.1"
playwright@^1.17.1:
version "1.17.1"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.17.1.tgz#a6d63302ee40f41283c4bf869de261c4743a787c"
integrity sha512-DisCkW9MblDJNS3rG61p8LiLA2WA7IY/4A4W7DX4BphWe/HuWjKmGQptuk4NVIh5UuSwXpW/jaH2+ZgjHs3GMA==
dependencies:
playwright-core "=1.17.1"
plist@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.2.tgz#74bbf011124b90421c22d15779cee60060ba95bc"
@ -11057,10 +11105,10 @@ postinstall-postinstall@^2.1.0:
resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3"
integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==
prebuild-install@^6.1.4:
version "6.1.4"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.1.4.tgz#ae3c0142ad611d58570b89af4986088a4937e00f"
integrity sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==
prebuild-install@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.0.tgz#3c5ce3902f1cb9d6de5ae94ca53575e4af0c1574"
integrity sha512-IvSenf33K7JcgddNz2D5w521EgO+4aMMjFt73Uk9FRzQ7P+QZPKrp7qPsDydsSwjGt3T5xRNnM1bj1zMTD5fTA==
dependencies:
detect-libc "^1.0.3"
expand-template "^2.0.3"
@ -11068,11 +11116,11 @@ prebuild-install@^6.1.4:
minimist "^1.2.3"
mkdirp-classic "^0.5.3"
napi-build-utils "^1.0.1"
node-abi "^2.21.0"
node-abi "^3.3.0"
npmlog "^4.0.1"
pump "^3.0.0"
rc "^1.2.7"
simple-get "^3.0.3"
simple-get "^4.0.0"
tar-fs "^2.0.0"
tunnel-agent "^0.6.0"
@ -11474,10 +11522,10 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
react-material-ui-carousel@^2.3.5:
version "2.3.5"
resolved "https://registry.yarnpkg.com/react-material-ui-carousel/-/react-material-ui-carousel-2.3.5.tgz#c6b4514333564ff4d49d2447f6d67f6c7651e813"
integrity sha512-laiysIaA47rX0/lIVjprX5MHrDyjLxgzAsyBnG8OKgO2ad5o3Pg88PAmu6RlMy/L/H6PKFYQ8H5bAqpeZETeYw==
react-material-ui-carousel@^2.3.8:
version "2.3.8"
resolved "https://registry.yarnpkg.com/react-material-ui-carousel/-/react-material-ui-carousel-2.3.8.tgz#6d6c088be22301fa91863383f35795d4e917a2a5"
integrity sha512-0qAWiKBQI4GCjuCWZANdDuHPW8k8N3qZo3tCn9OxpVNO+M9lq7Xzak63AyPwmRDRw93l97LYhbwQIMohvpaMeg==
dependencies:
auto-bind "^2.1.1"
react-swipeable "^6.1.0"
@ -12183,12 +12231,13 @@ sass-loader@^8.0.2:
schema-utils "^2.6.1"
semver "^6.3.0"
sass@^1.32.13, sass@^1.43.4:
version "1.43.4"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.43.4.tgz#68c7d6a1b004bef49af0d9caf750e9b252105d1f"
integrity sha512-/ptG7KE9lxpGSYiXn7Ar+lKOv37xfWsZRtFYal2QHNigyVQDx685VFT/h7ejVr+R8w7H4tmUgtulsKl5YpveOg==
sass@^1.32.13, sass@^1.44.0:
version "1.44.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.44.0.tgz#619aa0a2275c097f9af5e6b8fe8a95e3056430fb"
integrity sha512-0hLREbHFXGQqls/K8X+koeP+ogFRPF4ZqetVB19b7Cst9Er8cOR0rc6RU7MaI4W1JmUShd1BPgPoeqmmgMMYFw==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
sax@^1.2.4, sax@~1.2.4:
version "1.2.4"
@ -12407,17 +12456,17 @@ shallow-clone@^3.0.0:
dependencies:
kind-of "^6.0.2"
sharp@^0.29.2:
version "0.29.2"
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.2.tgz#e8c003cd9cb321585b32dbda6eed3baa7d6f2308"
integrity sha512-XWRdiYLIJ3tDUejRyG24KERnJzMfIoyiJBntd2S6/uj3NEeNgRFRLgiBlvPxMa8aml14dKKD98yHinSNKp1xzQ==
sharp@^0.29.3:
version "0.29.3"
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.3.tgz#0da183d626094c974516a48fab9b3e4ba92eb5c2"
integrity sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA==
dependencies:
color "^4.0.1"
detect-libc "^1.0.3"
node-addon-api "^4.2.0"
prebuild-install "^6.1.4"
prebuild-install "^7.0.0"
semver "^7.3.5"
simple-get "^3.1.0"
simple-get "^4.0.0"
tar-fs "^2.1.1"
tunnel-agent "^0.6.0"
@ -12496,12 +12545,12 @@ simple-concat@^1.0.0:
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
simple-get@^3.0.3, simple-get@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3"
integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==
simple-get@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675"
integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==
dependencies:
decompress-response "^4.2.0"
decompress-response "^6.0.0"
once "^1.3.1"
simple-concat "^1.0.0"
@ -12607,6 +12656,23 @@ socks-proxy-agent@^4.0.0:
agent-base "~4.2.1"
socks "~2.3.2"
socks-proxy-agent@^6.1.0:
version "6.1.1"
resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz#e664e8f1aaf4e1fb3df945f09e3d94f911137f87"
integrity sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==
dependencies:
agent-base "^6.0.2"
debug "^4.3.1"
socks "^2.6.1"
socks@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e"
integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==
dependencies:
ip "^1.1.5"
smart-buffer "^4.1.0"
socks@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.3.tgz#01129f0a5d534d2b897712ed8aceab7ee65d78e3"
@ -14231,12 +14297,12 @@ webpack-dev-middleware@^3.7.2:
range-parser "^1.2.1"
webpack-log "^2.0.0"
webpack-dev-server@^3.11.2:
version "3.11.2"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz#695ebced76a4929f0d5de7fd73fafe185fe33708"
integrity sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ==
webpack-dev-server@^3.11.3:
version "3.11.3"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.3.tgz#8c86b9d2812bf135d3c9bce6f07b718e30f7c3d3"
integrity sha512-3x31rjbEQWKMNzacUZRE6wXvUFuGpH7vr0lIEbYpMAG9BOxi0928QU1BBswOAP3kg3H1O4hiS+sq4YyAn6ANnA==
dependencies:
ansi-html "0.0.7"
ansi-html-community "0.0.8"
bonjour "^3.5.0"
chokidar "^2.1.8"
compression "^1.7.4"
@ -14605,10 +14671,10 @@ xterm-addon-fit@^0.5.0:
resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz#2d51b983b786a97dcd6cde805e700c7f913bc596"
integrity sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ==
xterm@^4.14.1:
version "4.14.1"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.14.1.tgz#6884cb8fb3b83353b1a98139ea23daedf8e35796"
integrity sha512-jgzNg5BuGPwq5/M4dGnmbghZvHx2jaj+9crSEt15bV34Za49VziBmCu7zIy88zUKKiGTxeo7aVzirFSJArIMFw==
xterm@^4.15.0:
version "4.15.0"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.15.0.tgz#e52038507eba7e0d36d47f81e29fe548c82b9561"
integrity sha512-Ik1GoSq1yqKZQ2LF37RPS01kX9t4TP8gpamUYblD09yvWX5mEYuMK4CcqH6+plgiNEZduhTz/UrcaWs97gOlOw==
y18n@^3.2.1:
version "3.2.2"