mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge remote-tracking branch 'origin/master' into monaco_editor_refactoring
# Conflicts: # src/renderer/components/+workloads-pods/pod-details-affinities.tsx # src/renderer/components/dock/create-resource.tsx # src/renderer/components/dock/edit-resource.tsx # src/renderer/components/dock/editor-panel.tsx
This commit is contained in:
commit
d6b7001f72
10
package.json
10
package.json
@ -206,7 +206,7 @@
|
||||
"http-proxy": "^1.18.1",
|
||||
"immer": "^9.0.6",
|
||||
"joi": "^17.4.2",
|
||||
"js-yaml": "^3.14.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsdom": "^16.7.0",
|
||||
"jsonpath": "^1.1.1",
|
||||
"lodash": "^4.17.15",
|
||||
@ -267,7 +267,7 @@
|
||||
"@types/chart.js": "^2.9.34",
|
||||
"@types/color": "^3.0.2",
|
||||
"@types/crypto-js": "^3.1.47",
|
||||
"@types/dompurify": "^2.0.2",
|
||||
"@types/dompurify": "^2.3.1",
|
||||
"@types/electron-devtools-installer": "^2.2.0",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/glob-to-regexp": "^0.4.1",
|
||||
@ -276,7 +276,7 @@
|
||||
"@types/html-webpack-plugin": "^3.2.6",
|
||||
"@types/http-proxy": "^1.17.7",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/js-yaml": "^3.12.4",
|
||||
"@types/js-yaml": "^4.0.2",
|
||||
"@types/jsdom": "^16.2.13",
|
||||
"@types/jsonpath": "^0.2.0",
|
||||
"@types/lodash": "^4.14.155",
|
||||
@ -346,7 +346,7 @@
|
||||
"jest": "26.6.3",
|
||||
"jest-canvas-mock": "^2.3.1",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jest-mock-extended": "^1.0.16",
|
||||
"jest-mock-extended": "^1.0.18",
|
||||
"make-plural": "^6.2.2",
|
||||
"mini-css-extract-plugin": "^1.6.2",
|
||||
"node-gyp": "7.1.2",
|
||||
@ -361,7 +361,7 @@
|
||||
"raw-loader": "^4.0.2",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-refresh": "^0.9.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-select": "3.2.0",
|
||||
"react-select-event": "^5.1.0",
|
||||
"react-table": "^7.7.0",
|
||||
|
||||
@ -426,7 +426,7 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
|
||||
it("replaces array format access token and expiry into string", async () => {
|
||||
const file = ClusterStore.getInstance().clustersList[0].kubeConfigPath;
|
||||
const config = fs.readFileSync(file, "utf8");
|
||||
const kc = yaml.safeLoad(config);
|
||||
const kc = yaml.load(config) as Record<string, any>;
|
||||
|
||||
expect(kc.users[0].user["auth-provider"].config["access-token"]).toBe("should be string");
|
||||
expect(kc.users[0].user["auth-provider"].config["expiry"]).toBe("should be string");
|
||||
|
||||
@ -324,6 +324,15 @@ describe("HotbarStore", () => {
|
||||
console.error = error;
|
||||
console.warn = warn;
|
||||
});
|
||||
|
||||
it("checks if entity already pinned to hotbar", () => {
|
||||
const hotbarStore = HotbarStore.getInstance();
|
||||
|
||||
hotbarStore.addToHotbar(testCluster);
|
||||
|
||||
expect(hotbarStore.isAddedToActive(testCluster)).toBeTruthy();
|
||||
expect(hotbarStore.isAddedToActive(awsCluster)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("pre beta-5 migrations", () => {
|
||||
|
||||
@ -35,8 +35,11 @@ export type WebLinkSpec = {
|
||||
};
|
||||
|
||||
export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus, WebLinkSpec> {
|
||||
public readonly apiVersion = "entity.k8slens.dev/v1alpha1";
|
||||
public readonly kind = "WebLink";
|
||||
public static readonly apiVersion = "entity.k8slens.dev/v1alpha1";
|
||||
public static readonly kind = "WebLink";
|
||||
|
||||
public readonly apiVersion = WebLink.apiVersion;
|
||||
public readonly kind = WebLink.kind;
|
||||
|
||||
async onRun() {
|
||||
window.open(this.spec.url, "_blank");
|
||||
|
||||
@ -171,7 +171,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||
}};
|
||||
|
||||
|
||||
if (hotbar.items.find(i => i?.entity.uid === uid)) {
|
||||
if (this.isAddedToActive(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -274,6 +274,14 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||
|
||||
hotbarStore.activeHotbarId = hotbarStore.hotbars[index].id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if entity already pinned to hotbar
|
||||
* @returns boolean
|
||||
*/
|
||||
isAddedToActive(entity: CatalogEntity) {
|
||||
return !!this.getActive().items.find(item => item?.entity.uid === entity.metadata.uid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import jsYaml from "js-yaml";
|
||||
import yaml from "js-yaml";
|
||||
import { autoBind, formatDuration } from "../../utils";
|
||||
import capitalize from "lodash/capitalize";
|
||||
import { apiBase } from "../index";
|
||||
@ -113,21 +113,31 @@ export async function getRelease(name: string, namespace: string): Promise<IRele
|
||||
}
|
||||
|
||||
export async function createRelease(payload: IReleaseCreatePayload): Promise<IReleaseUpdateDetails> {
|
||||
const { repo, ...data } = payload;
|
||||
const { repo, chart: rawChart, values: rawValues, ...data } = payload;
|
||||
const chart = `${repo}/${rawChart}`;
|
||||
const values = yaml.load(rawValues);
|
||||
|
||||
data.chart = `${repo}/${data.chart}`;
|
||||
data.values = jsYaml.safeLoad(data.values);
|
||||
|
||||
return apiBase.post(endpoint(), { data });
|
||||
return apiBase.post(endpoint(), {
|
||||
data: {
|
||||
chart,
|
||||
values,
|
||||
...data,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateRelease(name: string, namespace: string, payload: IReleaseUpdatePayload): Promise<IReleaseUpdateDetails> {
|
||||
const { repo, ...data } = payload;
|
||||
const { repo, chart: rawChart, values: rawValues, ...data } = payload;
|
||||
const chart = `${repo}/${rawChart}`;
|
||||
const values = yaml.load(rawValues);
|
||||
|
||||
data.chart = `${repo}/${data.chart}`;
|
||||
data.values = jsYaml.safeLoad(data.values);
|
||||
|
||||
return apiBase.put(endpoint({ name, namespace }), { data });
|
||||
return apiBase.put(endpoint({ name, namespace }), {
|
||||
data: {
|
||||
chart,
|
||||
values,
|
||||
...data,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteRelease(name: string, namespace: string): Promise<JsonApiData> {
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import jsYaml from "js-yaml";
|
||||
import yaml from "js-yaml";
|
||||
import type { KubeJsonApiData } from "../kube-json-api";
|
||||
import { apiBase } from "../index";
|
||||
import type { Patch } from "rfc6902";
|
||||
@ -30,7 +30,13 @@ export const annotations = [
|
||||
|
||||
export async function update(resource: object | string): Promise<KubeJsonApiData> {
|
||||
if (typeof resource === "string") {
|
||||
resource = jsYaml.safeLoad(resource);
|
||||
const parsed = yaml.load(resource);
|
||||
|
||||
if (typeof parsed !== "object") {
|
||||
throw new Error("Cannot update resource to string or number");
|
||||
}
|
||||
|
||||
resource = parsed;
|
||||
}
|
||||
|
||||
return apiBase.post<KubeJsonApiData>("/stack", { data: resource });
|
||||
|
||||
@ -133,7 +133,7 @@ export class ResourceStack {
|
||||
|
||||
if (!resourceData.trim()) continue;
|
||||
|
||||
const resourceArray = yaml.safeLoadAll(resourceData.toString());
|
||||
const resourceArray = yaml.loadAll(resourceData.toString());
|
||||
|
||||
resourceArray.forEach((resource) => {
|
||||
if (resource?.metadata) {
|
||||
@ -143,7 +143,7 @@ export class ResourceStack {
|
||||
resource.metadata.labels["app.kubernetes.io/created-by"] = "resource-stack";
|
||||
}
|
||||
|
||||
resources.push(yaml.safeDump(resource));
|
||||
resources.push(yaml.dump(resource));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -111,7 +111,7 @@ interface OptionsResult {
|
||||
}
|
||||
|
||||
function loadToOptions(rawYaml: string): OptionsResult {
|
||||
const parsed = yaml.safeLoad(rawYaml);
|
||||
const parsed = yaml.load(rawYaml);
|
||||
const { error } = kubeConfigSchema.validate(parsed, {
|
||||
abortEarly: false,
|
||||
allowUnknown: true,
|
||||
@ -248,7 +248,7 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
|
||||
logger.debug("Dumping KubeConfig:", config);
|
||||
|
||||
// skipInvalid: true makes dump ignore undefined values
|
||||
return yaml.safeDump(config, { skipInvalid: true });
|
||||
return yaml.dump(config, { skipInvalid: true });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -29,6 +29,11 @@ import { helmCli } from "./helm-cli";
|
||||
import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api";
|
||||
import { sortCharts } from "../../common/utils";
|
||||
|
||||
export interface HelmCacheFile {
|
||||
apiVersion: string;
|
||||
entries: RepoHelmChartList;
|
||||
}
|
||||
|
||||
export class HelmChartManager {
|
||||
static #cache = new Map<string, Buffer>();
|
||||
|
||||
@ -82,7 +87,11 @@ export class HelmChartManager {
|
||||
protected async cachedYaml(): Promise<RepoHelmChartList> {
|
||||
if (!HelmChartManager.#cache.has(this.repo.name)) {
|
||||
const cacheFile = await fs.promises.readFile(this.repo.cacheFilePath, "utf-8");
|
||||
const { entries } = yaml.safeLoad(cacheFile) as { entries: RepoHelmChartList };
|
||||
const data = yaml.load(cacheFile) as string | number | HelmCacheFile;
|
||||
|
||||
if (typeof data !== "object" || !data) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Do some initial preprocessing on the data, so as to avoid needing to do it later
|
||||
@ -92,7 +101,7 @@ export class HelmChartManager {
|
||||
*/
|
||||
|
||||
const normalized = Object.fromEntries(
|
||||
Object.entries(entries)
|
||||
Object.entries(data.entries)
|
||||
.map(([name, charts]) => [
|
||||
name,
|
||||
sortCharts(
|
||||
|
||||
@ -53,7 +53,7 @@ export async function installChart(chart: string, values: any, name: string | un
|
||||
const helm = await helmCli.binaryPath();
|
||||
const fileName = tempy.file({ name: "values.yaml" });
|
||||
|
||||
await fse.writeFile(fileName, yaml.safeDump(values));
|
||||
await fse.writeFile(fileName, yaml.dump(values));
|
||||
|
||||
try {
|
||||
let generateName = "";
|
||||
@ -83,7 +83,7 @@ export async function upgradeRelease(name: string, chart: string, values: any, n
|
||||
const helm = await helmCli.binaryPath();
|
||||
const fileName = tempy.file({ name: "values.yaml" });
|
||||
|
||||
await fse.writeFile(fileName, yaml.safeDump(values));
|
||||
await fse.writeFile(fileName, yaml.dump(values));
|
||||
|
||||
try {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
@ -101,18 +101,28 @@ export class HelmRepoManager extends Singleton {
|
||||
return repos.find(repo => repo.name === name);
|
||||
}
|
||||
|
||||
private async readConfig(): Promise<HelmRepoConfig> {
|
||||
try {
|
||||
const rawConfig = await readFile(this.helmEnv.HELM_REPOSITORY_CONFIG, "utf8");
|
||||
const parsedConfig = yaml.load(rawConfig);
|
||||
|
||||
if (typeof parsedConfig === "object" && parsedConfig) {
|
||||
return parsedConfig as HelmRepoConfig;
|
||||
}
|
||||
} catch { }
|
||||
|
||||
return {
|
||||
repositories: []
|
||||
};
|
||||
}
|
||||
|
||||
public async repositories(): Promise<HelmRepo[]> {
|
||||
try {
|
||||
if (!this.initialized) {
|
||||
await this.init();
|
||||
}
|
||||
|
||||
const repoConfigFile = this.helmEnv.HELM_REPOSITORY_CONFIG;
|
||||
const { repositories }: HelmRepoConfig = await readFile(repoConfigFile, "utf8")
|
||||
.then((yamlContent: string) => yaml.safeLoad(yamlContent))
|
||||
.catch(() => ({
|
||||
repositories: []
|
||||
}));
|
||||
const { repositories } = await this.readConfig();
|
||||
|
||||
if (!repositories.length) {
|
||||
await HelmRepoManager.addRepo({ name: "bitnami", url: "https://charts.bitnami.com/bitnami" });
|
||||
|
||||
@ -78,7 +78,7 @@ export class ResourceApplier {
|
||||
resource = this.sanitizeObject(resource);
|
||||
appEventBus.emit({ name: "resource", action: "apply" });
|
||||
|
||||
return this.kubectlApply(yaml.safeDump(resource));
|
||||
return this.kubectlApply(yaml.dump(resource));
|
||||
}
|
||||
|
||||
protected async kubectlApply(content: string): Promise<string> {
|
||||
|
||||
@ -32,9 +32,13 @@ export default {
|
||||
const cluster = value[1];
|
||||
|
||||
if (!cluster.kubeConfig) continue;
|
||||
const kubeConfig = yaml.safeLoad(cluster.kubeConfig);
|
||||
const config = yaml.load(cluster.kubeConfig);
|
||||
|
||||
if (!kubeConfig.hasOwnProperty("users")) continue;
|
||||
if (!config || typeof config !== "object" || !config.hasOwnProperty("users")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const kubeConfig = config as Record<string, any>;
|
||||
const userObj = kubeConfig.users[0];
|
||||
|
||||
if (userObj) {
|
||||
@ -56,7 +60,7 @@ export default {
|
||||
name: userObj.name,
|
||||
user
|
||||
}];
|
||||
cluster.kubeConfig = yaml.safeDump(kubeConfig);
|
||||
cluster.kubeConfig = yaml.dump(kubeConfig);
|
||||
store.set(clusterKey, cluster);
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,9 +28,9 @@ import { makeObservable, observable } from "mobx";
|
||||
import { navigate } from "../../navigation";
|
||||
import { MenuItem } from "../menu";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { Icon } from "../icon";
|
||||
import type { CatalogEntityItem } from "./catalog-entity-item";
|
||||
import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
|
||||
|
||||
export interface CatalogEntityDrawerMenuProps<T extends CatalogEntity> extends MenuActionsProps {
|
||||
item: CatalogEntityItem<T> | null | undefined;
|
||||
@ -70,10 +70,6 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
|
||||
}
|
||||
}
|
||||
|
||||
addToHotbar(entity: CatalogEntity): void {
|
||||
HotbarStore.getInstance().addToHotbar(entity);
|
||||
}
|
||||
|
||||
getMenuItems(entity: T): React.ReactChild[] {
|
||||
if (!entity) {
|
||||
return [];
|
||||
@ -99,9 +95,12 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
|
||||
}
|
||||
|
||||
items.push(
|
||||
<MenuItem key="add-to-hotbar" onClick={() => this.addToHotbar(entity) }>
|
||||
<Icon material="playlist_add" small tooltip="Add to Hotbar" />
|
||||
</MenuItem>
|
||||
<HotbarToggleMenuItem
|
||||
key="hotbar-toggle"
|
||||
entity={entity}
|
||||
addContent={<Icon material="push_pin" small tooltip={"Add to Hotbar"}/>}
|
||||
removeContent={<Icon svg="unpin" small tooltip={"Remove from Hotbar"}/>}
|
||||
/>
|
||||
);
|
||||
|
||||
return items;
|
||||
@ -109,7 +108,7 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
|
||||
|
||||
render() {
|
||||
const { className, item: entity, ...menuProps } = this.props;
|
||||
|
||||
|
||||
if (!this.contextMenu || !entity.enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -19,10 +19,36 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
.list :global(.TableRow) {
|
||||
.entityName {
|
||||
position: relative;
|
||||
width: min-content;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 24px;
|
||||
|
||||
.pinIcon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
transition: none;
|
||||
|
||||
&:hover {
|
||||
/* Drop styles defined for <Icon/> */
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .pinIcon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.iconCell {
|
||||
max-width: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@apply flex items-center max-w-[40px];
|
||||
}
|
||||
|
||||
.iconCell > div * {
|
||||
|
||||
@ -46,6 +46,12 @@ jest.mock("@electron/remote", () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock("./hotbar-toggle-menu-item", () => {
|
||||
return {
|
||||
HotbarToggleMenuItem: () => <div>menu item</div>
|
||||
};
|
||||
});
|
||||
|
||||
class MockCatalogEntity extends CatalogEntity {
|
||||
public apiVersion = "api";
|
||||
public kind = "kind";
|
||||
|
||||
@ -37,13 +37,15 @@ import { CatalogAddButton } from "./catalog-add-button";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import { Notifications } from "../notifications";
|
||||
import { MainLayout } from "../layout/main-layout";
|
||||
import { createAppStorage, cssNames } from "../../utils";
|
||||
import { createAppStorage, cssNames, prevDefault } from "../../utils";
|
||||
import { makeCss } from "../../../common/utils/makeCss";
|
||||
import { CatalogEntityDetails } from "./catalog-entity-details";
|
||||
import { browseCatalogTab, catalogURL, CatalogViewRouteParam } from "../../../common/routes";
|
||||
import { CatalogMenu } from "./catalog-menu";
|
||||
import { HotbarIcon } from "../hotbar/hotbar-icon";
|
||||
import { RenderDelay } from "../render-delay/render-delay";
|
||||
import { Icon } from "../icon";
|
||||
import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
|
||||
|
||||
export const previousActiveTab = createAppStorage("catalog-previous-active-tab", browseCatalogTab);
|
||||
|
||||
@ -129,6 +131,10 @@ export class Catalog extends React.Component<Props> {
|
||||
HotbarStore.getInstance().addToHotbar(item.entity);
|
||||
}
|
||||
|
||||
removeFromHotbar(item: CatalogEntityItem<CatalogEntity>): void {
|
||||
HotbarStore.getInstance().removeFromHotbar(item.getId());
|
||||
}
|
||||
|
||||
onDetails = (item: CatalogEntityItem<CatalogEntity>) => {
|
||||
if (this.catalogEntityStore.selectedItemId) {
|
||||
this.catalogEntityStore.selectedItemId = null;
|
||||
@ -194,13 +200,34 @@ export class Catalog extends React.Component<Props> {
|
||||
</MenuItem>
|
||||
))
|
||||
}
|
||||
<MenuItem key="add-to-hotbar" onClick={() => this.addToHotbar(item)}>
|
||||
Pin to Hotbar
|
||||
</MenuItem>
|
||||
<HotbarToggleMenuItem
|
||||
key="hotbar-toggle"
|
||||
entity={item.entity}
|
||||
addContent="Add to Hotbar"
|
||||
removeContent="Remove from Hotbar"
|
||||
/>
|
||||
</MenuActions>
|
||||
);
|
||||
};
|
||||
|
||||
renderName(item: CatalogEntityItem<CatalogEntity>) {
|
||||
const isItemInHotbar = HotbarStore.getInstance().isAddedToActive(item.entity);
|
||||
|
||||
return (
|
||||
<div className={styles.entityName}>
|
||||
{item.name}
|
||||
<Icon
|
||||
small
|
||||
className={styles.pinIcon}
|
||||
material={!isItemInHotbar && "push_pin"}
|
||||
svg={isItemInHotbar && "unpin"}
|
||||
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
|
||||
onClick={prevDefault(() => isItemInHotbar ? this.removeFromHotbar(item) : this.addToHotbar(item))}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderIcon(item: CatalogEntityItem<CatalogEntity>) {
|
||||
return (
|
||||
<RenderDelay>
|
||||
@ -231,7 +258,7 @@ export class Catalog extends React.Component<Props> {
|
||||
renderHeaderTitle={activeCategory?.metadata.name || "Browse All"}
|
||||
isSelectable={false}
|
||||
isConfigurable={true}
|
||||
className="CatalogItemList"
|
||||
className={styles.list}
|
||||
store={this.catalogEntityStore}
|
||||
sortingCallbacks={{
|
||||
[sortBy.name]: item => item.name,
|
||||
@ -255,7 +282,7 @@ export class Catalog extends React.Component<Props> {
|
||||
})}
|
||||
renderTableContents={item => [
|
||||
this.renderIcon(item),
|
||||
item.name,
|
||||
this.renderName(item),
|
||||
!activeCategory && item.kind,
|
||||
item.source,
|
||||
item.getLabelBadges(),
|
||||
|
||||
42
src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx
Normal file
42
src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx
Normal 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, { ReactNode, useState } from "react";
|
||||
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { MenuItem } from "../menu";
|
||||
|
||||
import type { CatalogEntity } from "../../api/catalog-entity";
|
||||
|
||||
export function HotbarToggleMenuItem(props: { entity: CatalogEntity, addContent: ReactNode, removeContent: ReactNode }) {
|
||||
const store = HotbarStore.getInstance(false);
|
||||
const add = () => store.addToHotbar(props.entity);
|
||||
const remove = () => store.removeFromHotbar(props.entity.getId());
|
||||
const [itemInHotbar, setItemInHotbar] = useState(store.isAddedToActive(props.entity));
|
||||
|
||||
return (
|
||||
<MenuItem onClick={() => {
|
||||
itemInHotbar ? remove() : add();
|
||||
setItemInHotbar(!itemInHotbar);
|
||||
}}>
|
||||
{itemInHotbar ? props.removeContent : props.addContent }
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
@ -20,7 +20,7 @@
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import jsYaml from "js-yaml";
|
||||
import yaml from "js-yaml";
|
||||
import { DrawerItem, DrawerParamToggler } from "../drawer";
|
||||
import type { DaemonSet, Deployment, Job, Pod, ReplicaSet, StatefulSet } from "../../../common/k8s-api/endpoints";
|
||||
import { MonacoEditor } from "../monaco-editor";
|
||||
@ -41,7 +41,7 @@ export class PodDetailsAffinities extends React.Component<Props> {
|
||||
<DrawerItem name="Affinities" className="PodDetailsAffinities">
|
||||
<DrawerParamToggler label={affinitiesNum}>
|
||||
<div style={{ height: 200 }}>
|
||||
<MonacoEditor readOnly value={jsYaml.dump(affinities)}/>
|
||||
<MonacoEditor readOnly value={yaml.dump(affinities)}/>
|
||||
</div>
|
||||
</DrawerParamToggler>
|
||||
</DrawerItem>
|
||||
|
||||
@ -21,13 +21,12 @@
|
||||
|
||||
import React from "react";
|
||||
import { GroupSelectOption, Select, SelectOption } from "../select";
|
||||
import jsYaml from "js-yaml";
|
||||
import yaml from "js-yaml";
|
||||
import { computed, makeObservable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { createResourceStore } from "./create-resource.store";
|
||||
import { InfoPanel, InfoPanelProps } from "./info-panel";
|
||||
import * as resourceApplierApi from "../../../common/k8s-api/endpoints/resource-applier.api";
|
||||
import type { JsonApiErrorParsed } from "../../../common/k8s-api/json-api";
|
||||
import { Notifications } from "../notifications";
|
||||
import { TabKind } from "./dock.store";
|
||||
import { dockViewsManager } from "./dock.views-manager";
|
||||
@ -58,36 +57,38 @@ export class CreateResourceInfoPanel extends React.Component<Props> {
|
||||
return createResourceStore.getData(this.tabId);
|
||||
}
|
||||
|
||||
create = async (): Promise<any> => {
|
||||
if (!this.draft) return; // skip: empty draft
|
||||
|
||||
// skip empty documents if "---" pasted at the beginning or end
|
||||
const resources = jsYaml.safeLoadAll(this.draft).filter(Boolean);
|
||||
const createdResources: string[] = [];
|
||||
const errors: string[] = [];
|
||||
|
||||
await Promise.all(
|
||||
resources.map(data => {
|
||||
return resourceApplierApi.update(data)
|
||||
.then(item => createdResources.push(item.metadata.name))
|
||||
.catch((err: JsonApiErrorParsed) => errors.push(err.toString()));
|
||||
})
|
||||
);
|
||||
|
||||
if (errors.length) {
|
||||
errors.forEach(error => Notifications.error(error));
|
||||
if (!createdResources.length) throw errors[0];
|
||||
create = async (): Promise<undefined> => {
|
||||
if (this.error || !this.data.trim()) {
|
||||
// do not save when field is empty or there is an error
|
||||
return null;
|
||||
}
|
||||
const successMessage = (
|
||||
<p>
|
||||
{createdResources.length === 1 ? "Resource" : "Resources"}{" "}
|
||||
<b>{createdResources.join(", ")}</b> successfully created
|
||||
</p>
|
||||
);
|
||||
|
||||
Notifications.ok(successMessage);
|
||||
// skip empty documents
|
||||
const resources = yaml.loadAll(this.data).filter(Boolean);
|
||||
const createdResources: string[] = [];
|
||||
|
||||
return successMessage;
|
||||
if (resources.length === 0) {
|
||||
return void logger.info("Nothing to create");
|
||||
}
|
||||
|
||||
for (const result of await Promise.allSettled(resources.map(resourceApplierApi.update))) {
|
||||
if (result.status === "fulfilled") {
|
||||
createdResources.push(result.value.metadata.name);
|
||||
} else {
|
||||
Notifications.error(result.reason.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (createdResources.length > 0) {
|
||||
Notifications.ok((
|
||||
<p>
|
||||
{createdResources.length === 1 ? "Resource" : "Resources"}{" "}
|
||||
<b>{createdResources.join(", ")}</b> successfully created
|
||||
</p>
|
||||
));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
renderControls() {
|
||||
|
||||
@ -42,29 +42,54 @@ export class EditResourceInfoPanel extends React.Component<Props> {
|
||||
return editResourceStore.getResource(this.tabId);
|
||||
}
|
||||
|
||||
get draft(): string {
|
||||
return editResourceStore.getData(this.tabId)?.draft;
|
||||
}
|
||||
|
||||
saveDraft(draft: string | object) {
|
||||
if (typeof draft === "object") {
|
||||
draft = draft ? jsYaml.safeDump(draft) : undefined;
|
||||
@computed get draft(): string {
|
||||
if (!this.isReadyForEditing) {
|
||||
return ""; // wait until tab's data and kube-object resource are loaded
|
||||
}
|
||||
|
||||
editResourceStore.getData(this.tabId).draft = draft;
|
||||
const { draft } = editResourceStore.getData(this.tabId);
|
||||
|
||||
if (typeof draft === "string") {
|
||||
return draft;
|
||||
}
|
||||
|
||||
return yaml.dump(this.resource.toPlainObject()); // dump resource first time
|
||||
}
|
||||
|
||||
save = async () => {
|
||||
const store = editResourceStore.getStore(this.tabId);
|
||||
const updatedResource: KubeObject = await store.update(this.resource, jsYaml.safeLoad(this.draft));
|
||||
@action
|
||||
saveDraft(draft: string | object) {
|
||||
if (typeof draft === "object") {
|
||||
draft = draft ? yaml.dump(draft) : undefined;
|
||||
}
|
||||
|
||||
this.saveDraft(updatedResource.toPlainObject()); // update with new resourceVersion to avoid further errors on save
|
||||
const resourceType = updatedResource.kind;
|
||||
const resourceName = updatedResource.getName();
|
||||
editResourceStore.setData(this.tabId, {
|
||||
firstDraft: draft, // this must be before the next line
|
||||
...editResourceStore.getData(this.tabId),
|
||||
draft,
|
||||
});
|
||||
}
|
||||
|
||||
onChange = (draft: string, error?: string) => {
|
||||
this.error = error;
|
||||
this.saveDraft(draft);
|
||||
};
|
||||
|
||||
save = async () => {
|
||||
if (this.error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const store = editResourceStore.getStore(this.tabId);
|
||||
const currentVersion = yaml.load(this.draft);
|
||||
const firstVersion = yaml.load(editResourceStore.getData(this.tabId).firstDraft ?? this.draft);
|
||||
const patches = createPatch(firstVersion, currentVersion);
|
||||
const updatedResource = await store.patch(this.resource, patches);
|
||||
|
||||
editResourceStore.clearInitialDraft(this.tabId);
|
||||
|
||||
return (
|
||||
<p>
|
||||
{resourceType} <b>{resourceName}</b> updated.
|
||||
{updatedResource.kind} <b>{updatedResource.getName()}</b> updated.
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
@ -30,7 +30,6 @@ import { navigate } from "../../navigation";
|
||||
import { cssNames, IClassName } from "../../utils";
|
||||
import { Icon } from "../icon";
|
||||
import { HotbarIcon } from "./hotbar-icon";
|
||||
import { HotbarStore } from "../../../common/hotbar-store";
|
||||
import { LensKubernetesClusterStatus } from "../../../common/catalog-entities/kubernetes-cluster";
|
||||
|
||||
interface Props extends DOMAttributes<HTMLElement> {
|
||||
@ -88,10 +87,6 @@ export class HotbarEntityIcon extends React.Component<Props> {
|
||||
return catalogEntityRegistry.activeEntity?.metadata?.uid == item.getId();
|
||||
}
|
||||
|
||||
isPersisted(entity: CatalogEntity) {
|
||||
return HotbarStore.getInstance().getActive().items.find((item) => item?.entity?.uid === entity.metadata.uid) !== undefined;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.contextMenu) {
|
||||
return null;
|
||||
@ -107,21 +102,13 @@ export class HotbarEntityIcon extends React.Component<Props> {
|
||||
disabled: !entity
|
||||
});
|
||||
|
||||
const isPersisted = this.isPersisted(entity);
|
||||
const onOpen = async () => {
|
||||
const menuItems: CatalogEntityContextMenu[] = [];
|
||||
|
||||
if (!isPersisted) {
|
||||
menuItems.unshift({
|
||||
title: "Pin to Hotbar",
|
||||
onClick: () => add(entity, index)
|
||||
});
|
||||
} else {
|
||||
menuItems.unshift({
|
||||
title: "Unpin from Hotbar",
|
||||
onClick: () => remove(entity.metadata.uid)
|
||||
});
|
||||
}
|
||||
menuItems.unshift({
|
||||
title: "Remove from Hotbar",
|
||||
onClick: () => remove(entity.metadata.uid)
|
||||
});
|
||||
|
||||
this.contextMenu.menuItems = menuItems;
|
||||
|
||||
|
||||
@ -138,7 +138,7 @@ export class HotbarMenu extends React.Component<Props> {
|
||||
tooltip={`${item.entity.name} (${item.entity.source})`}
|
||||
menuItems={[
|
||||
{
|
||||
title: "Unpin from Hotbar",
|
||||
title: "Remove from Hotbar",
|
||||
onClick: () => this.removeItem(item.entity.uid)
|
||||
}
|
||||
]}
|
||||
|
||||
6
src/renderer/components/icon/unpin.svg
Normal file
6
src/renderer/components/icon/unpin.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
||||
<path d="M12.6,12.1V2H7C6.5,2,6,2.5,6,3s0.5,1,1,1h1v5c0,1.7-1.3,3-3,3v2h6v7l1,1l1-1v-7h6v-1.9H12.6z"/>
|
||||
<polygon points="23.6,3.7 21.9,2 19.4,4.5 16.9,2 15.2,3.7 17.7,6.2 15.2,8.7 16.9,10.4 19.4,7.9 21.9,10.4 23.6,8.7 21.1,6.2 "/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 487 B |
@ -24,7 +24,7 @@ import "./kubeconfig-dialog.scss";
|
||||
import React from "react";
|
||||
import { makeObservable, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import jsYaml from "js-yaml";
|
||||
import yaml from "js-yaml";
|
||||
import type { ServiceAccount } from "../../../common/k8s-api/endpoints";
|
||||
import { copyToClipboard, cssNames, saveFileDialog } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
@ -86,7 +86,7 @@ export class KubeConfigDialog extends React.Component<Props> {
|
||||
this.close();
|
||||
});
|
||||
|
||||
this.config = config ? jsYaml.dump(config) : "";
|
||||
this.config = config ? yaml.dump(config) : "";
|
||||
}
|
||||
|
||||
copyToClipboard = () => {
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { KubernetesCluster } from "../../common/catalog-entities";
|
||||
import { KubernetesCluster, WebLink } from "../../common/catalog-entities";
|
||||
import { CatalogEntityDetailRegistry, CatalogEntityDetailsProps } from "../../extensions/registries";
|
||||
import { DrawerItem, DrawerTitle } from "../components/drawer";
|
||||
|
||||
@ -45,6 +45,20 @@ export function initCatalogEntityDetailRegistry() {
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
apiVersions: [WebLink.apiVersion],
|
||||
kind: WebLink.kind,
|
||||
components: {
|
||||
Details: ({ entity }: CatalogEntityDetailsProps<WebLink>) => (
|
||||
<>
|
||||
<DrawerTitle title="More Information" />
|
||||
<DrawerItem name="URL">
|
||||
{entity.spec.url}
|
||||
</DrawerItem>
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
65
yarn.lock
65
yarn.lock
@ -272,10 +272,10 @@
|
||||
core-js-pure "^3.0.0"
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
version "7.13.17"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.17.tgz#8966d1fc9593bf848602f0662d6b4d0069e3a7ec"
|
||||
integrity sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA==
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
version "7.15.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
|
||||
integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
@ -1450,10 +1450,10 @@
|
||||
dependencies:
|
||||
"@types/ms" "*"
|
||||
|
||||
"@types/dompurify@^2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.0.2.tgz#94b5c05dc9b8a682a0abb4a8d6f0b82df61baeac"
|
||||
integrity sha512-WHoQQTziRHm5/Fw/KsUKyh2V+wd3k2QUpJyjUXo8K7d9kMJ5i5wQnGDkO4URkwulhY2HuM/gbX25nSooi6+wUA==
|
||||
"@types/dompurify@^2.3.1":
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.3.1.tgz#2934adcd31c4e6b02676f9c22f9756e5091c04dd"
|
||||
integrity sha512-YJth9qa0V/E6/XPH1Jq4BC8uCMmO8V1fKWn8PCvuZcAhMn7q0ez9LW6naQT04UZzjFfAPhyRMZmI2a2rbMlEFA==
|
||||
dependencies:
|
||||
"@types/trusted-types" "*"
|
||||
|
||||
@ -1611,12 +1611,7 @@
|
||||
jest-diff "^26.0.0"
|
||||
pretty-format "^26.0.0"
|
||||
|
||||
"@types/js-yaml@^3.12.4":
|
||||
version "3.12.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.4.tgz#7d3b534ec35a0585128e2d332db1403ebe057e25"
|
||||
integrity sha512-fYMgzN+9e28R81weVN49inn/u798ruU91En1ZnGvSZzCRc5jXx9B2EDhlRaWmcO1RIxFHL8AajRXzxDuJu93+A==
|
||||
|
||||
"@types/js-yaml@^4.0.1":
|
||||
"@types/js-yaml@^4.0.1", "@types/js-yaml@^4.0.2":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.2.tgz#4117a7a378593a218e9d6f0ef44ce6d5d9edf7fa"
|
||||
integrity sha512-KbeHS/Y4R+k+5sWXEYzAZKuB1yQlZtEghuhRxrVRLaqhtoG5+26JwQsa4HyS3AWX8v1Uwukma5HheduUDskasA==
|
||||
@ -8217,12 +8212,12 @@ jest-message-util@^26.6.2:
|
||||
slash "^3.0.0"
|
||||
stack-utils "^2.0.2"
|
||||
|
||||
jest-mock-extended@^1.0.16:
|
||||
version "1.0.16"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock-extended/-/jest-mock-extended-1.0.16.tgz#f6a96c795acc2f5ae9ee74d7b048c3b4399c6fc8"
|
||||
integrity sha512-W1lI7cO0ZYmTQUBZH11nSDKyU36SxAyzBjZNxBWcpFnCKHGNWIPdFA8RJMhmobLQ8WLI4y9AMIbYzMN9q/GiRQ==
|
||||
jest-mock-extended@^1.0.18:
|
||||
version "1.0.18"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock-extended/-/jest-mock-extended-1.0.18.tgz#27a40e882c09f8230243fc6765fe7285b03f2b4b"
|
||||
integrity sha512-qf1n7lIa2dTxxPIBr+FlXrbj3hnV1sG9DPZsrr2H/8W+Jw0wt6OmeOQsPcjRuW8EXIECC9pDXsSIfEdn+HP7JQ==
|
||||
dependencies:
|
||||
ts-essentials "^4.0.0"
|
||||
ts-essentials "^7.0.2"
|
||||
|
||||
jest-mock@^26.6.2:
|
||||
version "26.6.2"
|
||||
@ -8447,7 +8442,7 @@ js-sha3@^0.8.0:
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||
|
||||
js-yaml@^3.13.1, js-yaml@^3.14.0:
|
||||
js-yaml@^3.13.1:
|
||||
version "3.14.1"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
|
||||
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
|
||||
@ -11599,25 +11594,25 @@ react-refresh@^0.9.0:
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf"
|
||||
integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==
|
||||
|
||||
react-router-dom@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
|
||||
integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==
|
||||
react-router-dom@^5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363"
|
||||
integrity sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
"@babel/runtime" "^7.12.13"
|
||||
history "^4.9.0"
|
||||
loose-envify "^1.3.1"
|
||||
prop-types "^15.6.2"
|
||||
react-router "5.2.0"
|
||||
react-router "5.2.1"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router@5.2.0, react-router@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293"
|
||||
integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==
|
||||
react-router@5.2.1, react-router@^5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.1.tgz#4d2e4e9d5ae9425091845b8dbc6d9d276239774d"
|
||||
integrity sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
"@babel/runtime" "^7.12.13"
|
||||
history "^4.9.0"
|
||||
hoist-non-react-statics "^3.1.0"
|
||||
loose-envify "^1.3.1"
|
||||
@ -13671,10 +13666,10 @@ truncate-utf8-bytes@^1.0.0:
|
||||
dependencies:
|
||||
utf8-byte-length "^1.0.1"
|
||||
|
||||
ts-essentials@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-4.0.0.tgz#506c42b270bbd0465574b90416533175b09205ab"
|
||||
integrity sha512-uQJX+SRY9mtbKU+g9kl5Fi7AEMofPCvHfJkQlaygpPmHPZrtgaBqbWFOYyiA47RhnSwwnXdepUJrgqUYxoUyhQ==
|
||||
ts-essentials@^7.0.2:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38"
|
||||
integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==
|
||||
|
||||
ts-jest@26.5.6:
|
||||
version "26.5.6"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user