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

Merge branch 'load_resources_per_namespaces' into fix-1898/watch-api-streaming

This commit is contained in:
Roman 2021-01-13 18:33:13 +02:00
commit 195f911342
25 changed files with 290 additions and 237 deletions

View File

@ -57,7 +57,10 @@ jobs:
displayName: Run tests displayName: Run tests
- script: make test-extensions - script: make test-extensions
displayName: Run In-tree Extension tests displayName: Run In-tree Extension tests
- script: make integration-win - bash: |
rm -rf extensions/telemetry
make integration-win
git checkout extensions/telemetry
displayName: Run integration tests displayName: Run integration tests
- script: make build - script: make build
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))" condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
@ -98,7 +101,10 @@ jobs:
displayName: Run tests displayName: Run tests
- script: make test-extensions - script: make test-extensions
displayName: Run In-tree Extension tests displayName: Run In-tree Extension tests
- script: make integration-mac - bash: |
rm -rf extensions/telemetry
make integration-mac
git checkout extensions/telemetry
displayName: Run integration tests displayName: Run integration tests
- script: make test-extensions - script: make test-extensions
displayName: Run In-tree Extension tests displayName: Run In-tree Extension tests
@ -152,7 +158,10 @@ jobs:
# Although the kube and minikube config files are in placed $HOME they are owned by root # Although the kube and minikube config files are in placed $HOME they are owned by root
sudo chown -R $USER $HOME/.kube $HOME/.minikube sudo chown -R $USER $HOME/.kube $HOME/.minikube
displayName: Install integration test dependencies displayName: Install integration test dependencies
- script: xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' make integration-linux - bash: |
rm -rf extensions/telemetry
xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' make integration-linux
git checkout extensions/telemetry
displayName: Run integration tests displayName: Run integration tests
- bash: | - bash: |
sudo chown root:root / sudo chown root:root /

1
.gitignore vendored
View File

@ -17,3 +17,4 @@ types/extension-renderer-api.d.ts
extensions/*/dist extensions/*/dist
docs/extensions/api docs/extensions/api
site/ site/
.vscode/

View File

@ -105,34 +105,18 @@ docs:
.PHONY: clean-extensions .PHONY: clean-extensions
clean-extensions: clean-extensions:
ifeq "$(DETECTED_OS)" "Windows"
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), if exist $(dir)\dist del /s /q $(dir)\dist)
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), if exist $(dir)\node_modules del /s /q $(dir)\node_modules)
else
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/dist) $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/dist)
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/node_modules) $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/node_modules)
endif
.PHONY: clean-npm .PHONY: clean-npm
clean-npm: clean-npm:
ifeq "$(DETECTED_OS)" "Windows"
if exist src\extensions\npm\extensions\dist del /s /q src\extensions\npm\extensions\dist
if exist src\extensions\npm\extensions\__mocks__ del /s /q src\extensions\npm\extensions\__mocks__
if exist src\extensions\npm\extensions\node_modules del /s /q src\extensions\npm\extensions\node_modules
else
rm -rf src/extensions/npm/extensions/dist rm -rf src/extensions/npm/extensions/dist
rm -rf src/extensions/npm/extensions/__mocks__ rm -rf src/extensions/npm/extensions/__mocks__
rm -rf src/extensions/npm/extensions/node_modules rm -rf src/extensions/npm/extensions/node_modules
endif
.PHONY: clean .PHONY: clean
clean: clean-npm clean-extensions clean: clean-npm clean-extensions
ifeq "$(DETECTED_OS)" "Windows"
if exist binaries\client del /s /q binaries\client
if exist dist del /s /q dist\*.*
if exist static\build del /s /q static\build\*.*
else
rm -rf binaries/client rm -rf binaries/client
rm -rf dist/* rm -rf dist/*
rm -rf static/build/* rm -rf static/build/*
endif rm -rf node_modules/

View File

@ -19,6 +19,9 @@ When contributing to this repository, please consider first discussing the chang
* `make dev` - builds and starts the app * `make dev` - builds and starts the app
* `make clean` - cleanup local environment build artifacts * `make clean` - cleanup local environment build artifacts
### Developing on Windows
On Windows we only support [Git Bash](https://gitforwindows.org/) (or similar shell) for running commands.
## Github Workflow ## Github Workflow

View File

@ -224,7 +224,7 @@
"tar": "^6.0.5", "tar": "^6.0.5",
"tcp-port-used": "^1.0.1", "tcp-port-used": "^1.0.1",
"tempy": "^0.5.0", "tempy": "^0.5.0",
"uuid": "^8.1.0", "uuid": "^8.3.2",
"win-ca": "^3.2.0", "win-ca": "^3.2.0",
"winston": "^3.2.1", "winston": "^3.2.1",
"winston-transport-browserconsole": "^1.0.5", "winston-transport-browserconsole": "^1.0.5",
@ -279,18 +279,18 @@
"@types/tempy": "^0.3.0", "@types/tempy": "^0.3.0",
"@types/terser-webpack-plugin": "^3.0.0", "@types/terser-webpack-plugin": "^3.0.0",
"@types/universal-analytics": "^0.4.4", "@types/universal-analytics": "^0.4.4",
"@types/uuid": "^8.0.0", "@types/uuid": "^8.3.0",
"@types/webdriverio": "^4.13.0", "@types/webdriverio": "^4.13.0",
"@types/webpack": "^4.41.17", "@types/webpack": "^4.41.17",
"@types/webpack-dev-server": "^3.11.1", "@types/webpack-dev-server": "^3.11.1",
"@types/webpack-env": "^1.15.2", "@types/webpack-env": "^1.15.2",
"@types/webpack-node-externals": "^1.7.1", "@types/webpack-node-externals": "^1.7.1",
"@typescript-eslint/eslint-plugin": "^4.0.0", "@typescript-eslint/eslint-plugin": "^4.12.0",
"@typescript-eslint/parser": "^4.0.0", "@typescript-eslint/parser": "^4.0.0",
"ace-builds": "^1.4.11", "ace-builds": "^1.4.11",
"ansi_up": "^4.0.4", "ansi_up": "^4.0.4",
"chart.js": "^2.9.3", "chart.js": "^2.9.3",
"circular-dependency-plugin": "^5.2.0", "circular-dependency-plugin": "^5.2.2",
"color": "^3.1.2", "color": "^3.1.2",
"concurrently": "^5.2.0", "concurrently": "^5.2.0",
"css-element-queries": "^1.2.3", "css-element-queries": "^1.2.3",

View File

@ -7,38 +7,49 @@ export type KubeResource =
"endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets"; "endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets";
export interface KubeApiResource { export interface KubeApiResource {
kind: string; // resource type
resource: KubeResource; // valid resource name resource: KubeResource; // valid resource name
group?: string; // api-group group?: string; // api-group
} }
// TODO: auto-populate all resources dynamically (see: kubectl api-resources -o=wide -v=7) // TODO: auto-populate all resources dynamically (see: kubectl api-resources -o=wide -v=7)
export const apiResources: KubeApiResource[] = [ export const apiResources: KubeApiResource[] = [
{ resource: "configmaps" }, { kind: "ConfigMap", resource: "configmaps" },
{ resource: "cronjobs", group: "batch" }, { kind: "CronJob", resource: "cronjobs", group: "batch" },
{ resource: "customresourcedefinitions", group: "apiextensions.k8s.io" }, { kind: "CustomResourceDefinition", resource: "customresourcedefinitions", group: "apiextensions.k8s.io" },
{ resource: "daemonsets", group: "apps" }, { kind: "DaemonSet", resource: "daemonsets", group: "apps" },
{ resource: "deployments", group: "apps" }, { kind: "Deployment", resource: "deployments", group: "apps" },
{ resource: "endpoints" }, { kind: "Endpoint", resource: "endpoints" },
{ resource: "events" }, { kind: "Event", resource: "events" },
{ resource: "horizontalpodautoscalers" }, { kind: "HorizontalPodAutoscaler", resource: "horizontalpodautoscalers" },
{ resource: "ingresses", group: "networking.k8s.io" }, { kind: "Ingress", resource: "ingresses", group: "networking.k8s.io" },
{ resource: "jobs", group: "batch" }, { kind: "Job", resource: "jobs", group: "batch" },
{ resource: "namespaces" }, { kind: "Namespace", resource: "namespaces" },
{ resource: "networkpolicies", group: "networking.k8s.io" }, { kind: "NetworkPolicy", resource: "networkpolicies", group: "networking.k8s.io" },
{ resource: "nodes" }, { kind: "Node", resource: "nodes" },
{ resource: "persistentvolumes" }, { kind: "PersistentVolume", resource: "persistentvolumes" },
{ resource: "persistentvolumeclaims" }, { kind: "PersistentVolumeClaim", resource: "persistentvolumeclaims" },
{ resource: "pods" }, { kind: "Pod", resource: "pods" },
{ resource: "poddisruptionbudgets" }, { kind: "PodDisruptionBudget", resource: "poddisruptionbudgets" },
{ resource: "podsecuritypolicies" }, { kind: "PodSecurityPolicy", resource: "podsecuritypolicies" },
{ resource: "resourcequotas" }, { kind: "ResourceQuota", resource: "resourcequotas" },
{ resource: "replicasets", group: "apps" }, { kind: "ReplicaSet", resource: "replicasets", group: "apps" },
{ resource: "secrets" }, { kind: "Secret", resource: "secrets" },
{ resource: "services" }, { kind: "Service", resource: "services" },
{ resource: "statefulsets", group: "apps" }, { kind: "StatefulSet", resource: "statefulsets", group: "apps" },
{ resource: "storageclasses", group: "storage.k8s.io" }, { kind: "StorageClass", resource: "storageclasses", group: "storage.k8s.io" },
]; ];
export function isAllowedResourceType(kind: string): boolean {
const apiResource = apiResources.find(resource => resource.kind === kind);
if (apiResource) {
return getHostedCluster().allowedResources.includes(apiResource.resource);
}
return true; // allowed by default for other resources
}
export function isAllowedResource(resources: KubeResource | KubeResource[]) { export function isAllowedResource(resources: KubeResource | KubeResource[]) {
if (!Array.isArray(resources)) { if (!Array.isArray(resources)) {
resources = [resources]; resources = [resources];

View File

@ -28,6 +28,7 @@ export interface UserPreferences {
downloadBinariesPath?: string; downloadBinariesPath?: string;
kubectlBinariesPath?: string; kubectlBinariesPath?: string;
openAtLogin?: boolean; openAtLogin?: boolean;
hiddenTableColumns?: Record<string, string[]>
} }
export class UserStore extends BaseStore<UserStoreModel> { export class UserStore extends BaseStore<UserStoreModel> {
@ -54,6 +55,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
downloadMirror: "default", downloadMirror: "default",
downloadKubectlBinaries: true, // Download kubectl binaries matching cluster version downloadKubectlBinaries: true, // Download kubectl binaries matching cluster version
openAtLogin: false, openAtLogin: false,
hiddenTableColumns: {},
}; };
protected async handleOnLoad() { protected async handleOnLoad() {

View File

@ -10,7 +10,7 @@ import { workspaceStore } from "../common/workspace-store";
import { preferencesURL } from "../renderer/components/+preferences/preferences.route"; import { preferencesURL } from "../renderer/components/+preferences/preferences.route";
import { clusterViewURL } from "../renderer/components/cluster-manager/cluster-view.route"; import { clusterViewURL } from "../renderer/components/cluster-manager/cluster-view.route";
import logger from "./logger"; import logger from "./logger";
import { isDevelopment } from "../common/vars"; import { isDevelopment, isWindows } from "../common/vars";
import { exitApp } from "./exit-app"; import { exitApp } from "./exit-app";
// note: instance of Tray should be saved somewhere, otherwise it disappears // note: instance of Tray should be saved somewhere, otherwise it disappears
@ -29,7 +29,7 @@ export function initTray(windowManager: WindowManager) {
try { try {
const menu = createTrayMenu(windowManager); const menu = createTrayMenu(windowManager);
buildTray(getTrayIcon(), menu); buildTray(getTrayIcon(), menu, windowManager);
} catch (err) { } catch (err) {
logger.error(`[TRAY]: building failed: ${err}`); logger.error(`[TRAY]: building failed: ${err}`);
} }
@ -42,20 +42,25 @@ export function initTray(windowManager: WindowManager) {
}; };
} }
export function buildTray(icon: string | NativeImage, menu: Menu) { function buildTray(icon: string | NativeImage, menu: Menu, windowManager: WindowManager) {
if (!tray) { if (!tray) {
tray = new Tray(icon); tray = new Tray(icon);
tray.setToolTip(packageInfo.description); tray.setToolTip(packageInfo.description);
tray.setIgnoreDoubleClickEvents(true); tray.setIgnoreDoubleClickEvents(true);
} tray.setImage(icon);
tray.setContextMenu(menu);
tray.setImage(icon); if (isWindows) {
tray.setContextMenu(menu); tray.on("click", () => {
windowManager.ensureMainWindow();
});
}
}
return tray; return tray;
} }
export function createTrayMenu(windowManager: WindowManager): Menu { function createTrayMenu(windowManager: WindowManager): Menu {
return Menu.buildFromTemplate([ return Menu.buildFromTemplate([
{ {
label: "About Lens", label: "About Lens",

View File

@ -78,10 +78,24 @@ export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics {
result.forEach(res => { result.forEach(res => {
if (!res.values || !res.values.length) return; if (!res.values || !res.values.length) return;
let now = moment().startOf("minute").subtract(1, "minute").unix();
let timestamp = res.values[0][0];
while (timestamp <= now) {
timestamp = moment.unix(timestamp).add(1, "minute").unix();
if (!res.values.find((value) => value[0] === timestamp)) {
res.values.push([timestamp, "0"]);
}
}
while (res.values.length < frames) { while (res.values.length < frames) {
const timestamp = moment.unix(res.values[0][0]).subtract(1, "minute").unix(); const timestamp = moment.unix(res.values[0][0]).subtract(1, "minute").unix();
res.values.unshift([timestamp, "0"]); if (!res.values.find((value) => value[0] === timestamp)) {
res.values.unshift([timestamp, "0"]);
}
now = timestamp;
} }
}); });
} }

View File

@ -18,7 +18,7 @@ export const resourceApplierApi = {
.post<KubeJsonApiData[]>("/stack", { data: resource }) .post<KubeJsonApiData[]>("/stack", { data: resource })
.then(data => { .then(data => {
const items = data.map(obj => { const items = data.map(obj => {
const api = apiManager.getApi(obj.metadata.selfLink); const api = apiManager.getApiByKind(obj.kind, obj.apiVersion);
if (api) { if (api) {
return new api.objectConstructor(obj); return new api.objectConstructor(obj);

View File

@ -4,7 +4,7 @@ import React, { Fragment } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { TabLayout } from "../layout/tab-layout"; import { TabLayout } from "../layout/tab-layout";
import { eventStore } from "./event.store"; import { eventStore } from "./event.store";
import { KubeObjectListLayout, KubeObjectListLayoutProps, getDetailsUrl } from "../kube-object"; import { getDetailsUrl, KubeObjectListLayout, KubeObjectListLayoutProps } from "../kube-object";
import { KubeEvent } from "../../api/endpoints/events.api"; import { KubeEvent } from "../../api/endpoints/events.api";
import { Tooltip } from "../tooltip"; import { Tooltip } from "../tooltip";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
@ -65,7 +65,7 @@ export class Events extends React.Component<Props> {
small small
material="help_outline" material="help_outline"
className="help-icon" className="help-icon"
tooltip="Limited to {eventStore.limit}" tooltip={`Limited to ${eventStore.limit}`}
/> />
</> </>
) )

View File

@ -1,3 +1,4 @@
import { debounce } from "lodash";
import { action, comparer, IReactionDisposer, IReactionOptions, observable, reaction, toJS, when } from "mobx"; import { action, comparer, IReactionDisposer, IReactionOptions, observable, reaction, toJS, when } from "mobx";
import { autobind, createStorage } from "../../utils"; import { autobind, createStorage } from "../../utils";
import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../kube-object.store"; import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../kube-object.store";
@ -7,14 +8,14 @@ import { apiManager } from "../../api/api-manager";
import { isAllowedResource } from "../../../common/rbac"; import { isAllowedResource } from "../../../common/rbac";
import { clusterStore, getHostedCluster } from "../../../common/cluster-store"; import { clusterStore, getHostedCluster } from "../../../common/cluster-store";
const storage = createStorage<string[]>("context_namespaces", []); const storage = createStorage<string[]>("context_namespaces");
export const namespaceUrlParam = createPageParam<string[]>({ export const namespaceUrlParam = createPageParam<string[]>({
name: "namespaces", name: "namespaces",
isSystem: true, isSystem: true,
multiValues: true, multiValues: true,
get defaultValue() { get defaultValue() {
return storage.get(); // initial namespaces coming from URL or local-storage (default) return storage.get() ?? []; // initial namespaces coming from URL or local-storage (default)
} }
}); });
@ -51,9 +52,9 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
await getHostedCluster().whenReady; // wait for cluster-state from main await getHostedCluster().whenReady; // wait for cluster-state from main
this.isReady = true; this.isReady = true;
this.setContext(this.initNamespaces); this.setContext(this.initialNamespaces);
this.onSelectedNamespacesChange(); this.autoLoadAllowedNamespaces();
this.onAllowedNamespacesChange(); this.autoUpdateUrlAndLocalStorage();
} }
public onContextChange(callback: (contextNamespaces: string[]) => void, opts: IReactionOptions = {}): IReactionDisposer { public onContextChange(callback: (contextNamespaces: string[]) => void, opts: IReactionOptions = {}): IReactionDisposer {
@ -63,7 +64,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
}); });
} }
private onSelectedNamespacesChange(): IReactionDisposer { private autoUpdateUrlAndLocalStorage(): IReactionDisposer {
return this.onContextChange(namespaces => { return this.onContextChange(namespaces => {
storage.set(namespaces); // save to local-storage storage.set(namespaces); // save to local-storage
namespaceUrlParam.set(namespaces, { replaceHistory: true }); // update url namespaceUrlParam.set(namespaces, { replaceHistory: true }); // update url
@ -73,7 +74,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
}); });
} }
private onAllowedNamespacesChange(): IReactionDisposer { private autoLoadAllowedNamespaces(): IReactionDisposer {
return reaction(() => this.allowedNamespaces, () => this.loadAll(), { return reaction(() => this.allowedNamespaces, () => this.loadAll(), {
fireImmediately: true, fireImmediately: true,
equals: comparer.identity, equals: comparer.identity,
@ -84,28 +85,19 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
return toJS(getHostedCluster().allowedNamespaces); return toJS(getHostedCluster().allowedNamespaces);
} }
get initNamespaces() { private get initialNamespaces(): string[] {
const allowedNamespaces = new Set(this.allowedNamespaces); const allowed = new Set(this.allowedNamespaces);
const lastUsedNamespaces = new Set(storage.get()); const prevSelected = storage.get();
// remove previously saved, but currently disallowed namespaces if (Array.isArray(prevSelected)) {
lastUsedNamespaces.forEach(namespace => { return prevSelected.filter(namespace => allowed.has(namespace));
if (!allowedNamespaces.has(namespace)) {
lastUsedNamespaces.delete(namespace);
}
});
// return previously saved and currently allowed namespaces
if (lastUsedNamespaces.size) {
return Array.from(lastUsedNamespaces);
} }
// otherwise select "default" or first allowed namespace // otherwise select "default" or first allowed namespace
else { if (allowed.has("default")) {
if (allowedNamespaces.has("default")) { return ["default"];
return ["default"]; } else if (allowed.size) {
} else if (allowedNamespaces.size) { return [Array.from(allowed)[0]];
return [Array.from(allowedNamespaces)[0]];
}
} }
return []; return [];
@ -132,17 +124,18 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
return super.subscribe(apis); return super.subscribe(apis);
} }
async loadAll() { // prevent multiple loading from different sources (e.g. items-list-layout, namespace-select)
return super.loadAll({ private loadAllLazy = debounce(() => {
super.loadAll({
namespaces: this.allowedNamespaces, namespaces: this.allowedNamespaces,
}); });
}, 250);
async loadAll() {
this.loadAllLazy();
} }
protected async loadItems({ isAdmin, namespaces }: KubeObjectStoreLoadingParams) { protected async loadItems({ namespaces }: KubeObjectStoreLoadingParams) {
if (isAdmin) {
return this.api.list();
}
if (!isAllowedResource("namespaces")) { if (!isAllowedResource("namespaces")) {
return namespaces.map(getDummyNamespace); return namespaces.map(getDummyNamespace);
} }

View File

@ -7,6 +7,7 @@ import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metri
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { ChartOptions, ChartPoint } from "chart.js"; import { ChartOptions, ChartPoint } from "chart.js";
import { themeStore } from "../../theme.store"; import { themeStore } from "../../theme.store";
import { mapValues } from "lodash";
type IContext = IResourceMetricsValue<Node, { metrics: IClusterMetrics }>; type IContext = IResourceMetricsValue<Node, { metrics: IClusterMetrics }>;
@ -15,26 +16,26 @@ export const NodeCharts = observer(() => {
const id = object.getId(); const id = object.getId();
const { chartCapacityColor } = themeStore.activeTheme.colors; const { chartCapacityColor } = themeStore.activeTheme.colors;
if (!metrics) return null; if (!metrics) {
if (isMetricsEmpty(metrics)) return <NoMetrics/>; return null;
}
const values = Object.values(metrics).map(metric => if (isMetricsEmpty(metrics)) {
normalizeMetrics(metric).data.result[0].values return <NoMetrics />;
); }
const [
const {
memoryUsage, memoryUsage,
memoryRequests, memoryRequests,
_memoryLimits, // eslint-disable-line unused-imports/no-unused-vars-ts
memoryCapacity, memoryCapacity,
cpuUsage, cpuUsage,
cpuRequests, cpuRequests,
_cpuLimits, // eslint-disable-line unused-imports/no-unused-vars-ts
cpuCapacity, cpuCapacity,
podUsage, podUsage,
podCapacity, podCapacity,
fsSize, fsSize,
fsUsage fsUsage
] = values; } = mapValues(metrics, metric => normalizeMetrics(metric).data.result[0].values);
const datasets = [ const datasets = [
// CPU // CPU

View File

@ -26,10 +26,10 @@ export class RoleBindingsStore extends KubeObjectStore<RoleBinding> {
return clusterRoleBindingApi.get(params); return clusterRoleBindingApi.get(params);
} }
protected async loadItems({ isAdmin, namespaces }: KubeObjectStoreLoadingParams): Promise<RoleBinding[]> { protected async loadItems(params: KubeObjectStoreLoadingParams): Promise<RoleBinding[]> {
const items = await Promise.all([ const items = await Promise.all([
super.loadItems({ isAdmin, namespaces, api: clusterRoleBindingApi }), super.loadItems({ ...params, api: clusterRoleBindingApi }),
super.loadItems({ isAdmin, namespaces, api: roleBindingApi }), super.loadItems({ ...params, api: roleBindingApi }),
]); ]);
return items.flat(); return items.flat();

View File

@ -24,10 +24,10 @@ export class RolesStore extends KubeObjectStore<Role> {
return clusterRoleApi.get(params); return clusterRoleApi.get(params);
} }
protected async loadItems({ isAdmin, namespaces }: KubeObjectStoreLoadingParams): Promise<Role[]> { protected async loadItems(params: KubeObjectStoreLoadingParams): Promise<Role[]> {
const items = await Promise.all([ const items = await Promise.all([
super.loadItems({ isAdmin, namespaces, api: clusterRoleApi }), super.loadItems({ ...params, api: clusterRoleApi }),
super.loadItems({ isAdmin, namespaces, api: roleApi }), super.loadItems({ ...params, api: roleApi }),
]); ]);
return items.flat(); return items.flat();

View File

@ -48,7 +48,6 @@ export class WorkloadsOverview extends React.Component<Props> {
if (this.isUnmounting) break; if (this.isUnmounting) break;
try { try {
store.reset();
await store.loadAll(); await store.loadAll();
unsubscribeMap.get(store)?.(); // unsubscribe previous watcher unsubscribeMap.get(store)?.(); // unsubscribe previous watcher
unsubscribeMap.set(store, store.subscribe()); unsubscribeMap.set(store, store.subscribe());

View File

@ -74,6 +74,8 @@ export class Pods extends React.Component<Props> {
<KubeObjectListLayout <KubeObjectListLayout
className="Pods" store={podsStore} className="Pods" store={podsStore}
dependentStores={[volumeClaimStore, eventStore]} dependentStores={[volumeClaimStore, eventStore]}
tableId = "workloads_pods"
isConfigurable
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (pod: Pod) => pod.getName(), [sortBy.name]: (pod: Pod) => pod.getName(),
[sortBy.namespace]: (pod: Pod) => pod.getNs(), [sortBy.namespace]: (pod: Pod) => pod.getNs(),
@ -94,7 +96,7 @@ export class Pods extends React.Component<Props> {
renderHeaderTitle="Pods" renderHeaderTitle="Pods"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: sortBy.name },
{ className: "warning" }, { className: "warning", showWithColumn: "name" },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: sortBy.namespace },
{ title: "Containers", className: "containers", sortBy: sortBy.containers }, { title: "Containers", className: "containers", sortBy: sortBy.containers },
{ title: "Restarts", className: "restarts", sortBy: sortBy.restarts }, { title: "Restarts", className: "restarts", sortBy: sortBy.restarts },

View File

@ -22,7 +22,7 @@
height: 18px; // Must be equal to lineHeight variable in pod-log-list.tsx height: 18px; // Must be equal to lineHeight variable in pod-log-list.tsx
font-family: var(--font-monospace); font-family: var(--font-monospace);
font-size: smaller; font-size: smaller;
white-space: pre; white-space: nowrap;
&:hover { &:hover {
background: var(--logRowHoverBackground); background: var(--logRowHoverBackground);

View File

@ -1,4 +1,5 @@
import "./item-list-layout.scss"; import "./item-list-layout.scss";
import "./table-menu.scss";
import groupBy from "lodash/groupBy"; import groupBy from "lodash/groupBy";
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
@ -18,6 +19,11 @@ import { PageFiltersList } from "./page-filters-list";
import { PageFiltersSelect } from "./page-filters-select"; import { PageFiltersSelect } from "./page-filters-select";
import { NamespaceSelectFilter } from "../+namespaces/namespace-select"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select";
import { themeStore } from "../../theme.store"; import { themeStore } from "../../theme.store";
import { MenuActions} from "../menu/menu-actions";
import { MenuItem } from "../menu";
import { Checkbox } from "../checkbox";
import { userStore } from "../../../common/user-store";
import logger from "../../../main/logger";
// todo: refactor, split to small re-usable components // todo: refactor, split to small re-usable components
@ -32,6 +38,7 @@ interface IHeaderPlaceholders {
} }
export interface ItemListLayoutProps<T extends ItemObject = ItemObject> { export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
tableId?: string;
className: IClassName; className: IClassName;
store: ItemStore<T>; store: ItemStore<T>;
dependentStores?: ItemStore[]; dependentStores?: ItemStore[];
@ -50,6 +57,7 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
isReady?: boolean; // show loading indicator while not ready isReady?: boolean; // show loading indicator while not ready
isSelectable?: boolean; // show checkbox in rows for selecting items isSelectable?: boolean; // show checkbox in rows for selecting items
isSearchable?: boolean; // apply search-filter & add search-input isSearchable?: boolean; // apply search-filter & add search-input
isConfigurable?: boolean;
copyClassNameFromHeadCells?: boolean; copyClassNameFromHeadCells?: boolean;
sortingCallbacks?: { [sortBy: string]: TableSortCallback }; sortingCallbacks?: { [sortBy: string]: TableSortCallback };
tableProps?: Partial<TableProps>; // low-level table configuration tableProps?: Partial<TableProps>; // low-level table configuration
@ -74,6 +82,7 @@ const defaultProps: Partial<ItemListLayoutProps> = {
showHeader: true, showHeader: true,
isSearchable: true, isSearchable: true,
isSelectable: true, isSelectable: true,
isConfigurable: false,
copyClassNameFromHeadCells: true, copyClassNameFromHeadCells: true,
dependentStores: [], dependentStores: [],
filterItems: [], filterItems: [],
@ -92,6 +101,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
private watchDisposers: IReactionDisposer[] = []; private watchDisposers: IReactionDisposer[] = [];
@observable hiddenColumnNames = new Set<string>();
@observable isUnmounting = false; @observable isUnmounting = false;
@observable userSettings: ItemListLayoutUserSettings = { @observable userSettings: ItemListLayoutUserSettings = {
@ -112,11 +122,11 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
} }
async componentDidMount() { async componentDidMount() {
this.loadStores();
if (!this.props.isClusterScoped) { if (!this.props.isClusterScoped) {
disposeOnUnmount(this, [ disposeOnUnmount(this, [
namespaceStore.onContextChange(() => this.loadStores(), { namespaceStore.onContextChange(() => this.loadStores())
fireImmediately: true,
})
]); ]);
} }
} }
@ -127,7 +137,10 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
} }
@computed get stores() { @computed get stores() {
const { store, dependentStores, isClusterScoped } = this.props; const { store, dependentStores, isClusterScoped, tableId } = this.props;
if (this.canBeConfigured) this.hiddenColumnNames = new Set(userStore.preferences?.hiddenTableColumns?.[tableId]);
const stores = new Set([store, ...dependentStores]); const stores = new Set([store, ...dependentStores]);
if (!isClusterScoped) { if (!isClusterScoped) {
@ -148,7 +161,6 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
} }
try { try {
store.reset();
await store.loadAll(); await store.loadAll();
this.watchDisposers.push(store.subscribe()); this.watchDisposers.push(store.subscribe());
} catch (error) { } catch (error) {
@ -195,9 +207,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
}; };
@computed get isReady() { @computed get isReady() {
const { isReady, store } = this.props; return this.props.isReady ?? this.props.store.isLoaded;
return typeof isReady == "boolean" ? isReady : store.isLoaded;
} }
@computed get filters() { @computed get filters() {
@ -243,6 +253,42 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
return this.applyFilters(filterItems, allItems); return this.applyFilters(filterItems, allItems);
} }
updateColumnFilter(checkboxValue: boolean, columnName: string) {
if (checkboxValue){
this.hiddenColumnNames.delete(columnName);
} else {
this.hiddenColumnNames.add(columnName);
}
if (this.canBeConfigured) {
userStore.preferences.hiddenTableColumns[this.props.tableId] = Array.from(this.hiddenColumnNames);
}
}
columnIsVisible(index: number): boolean {
const {renderTableHeader} = this.props;
if (!this.canBeConfigured) return true;
return !this.hiddenColumnNames.has(renderTableHeader[index].showWithColumn ?? renderTableHeader[index].className);
}
get canBeConfigured(): boolean {
const { isConfigurable, tableId, renderTableHeader } = this.props;
if (!isConfigurable || !tableId) {
return false;
}
if (!renderTableHeader?.every(({ className }) => className)) {
logger.warning("[ItemObjectList]: cannot configure an object list without all headers being identifiable");
return false;
}
return true;
}
@autobind() @autobind()
getRow(uid: string) { getRow(uid: string) {
const { const {
@ -286,7 +332,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
} }
} }
return <TableCell key={index} {...cellProps} />; return this.columnIsVisible(index) ? <TableCell key={index} {...cellProps} /> : null;
}) })
} }
{renderItemMenu && ( {renderItemMenu && (
@ -330,12 +376,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
} }
renderNoItems() { renderNoItems() {
const { allItems, items, filters } = this; if (this.filters.length > 0) {
const allItemsCount = allItems.length;
const itemsCount = items.length;
const isFiltered = filters.length > 0 && allItemsCount > itemsCount;
if (isFiltered) {
return ( return (
<NoItems> <NoItems>
No items found. No items found.
@ -458,14 +499,18 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
onClick={prevDefault(() => store.toggleSelectionAll(items))} onClick={prevDefault(() => store.toggleSelectionAll(items))}
/> />
)} )}
{renderTableHeader.map((cellProps, index) => <TableCell key={index} {...cellProps} />)} {renderTableHeader.map((cellProps, index) => this.columnIsVisible(index) ? <TableCell key={index} {...cellProps} /> : null)}
{renderItemMenu && <TableCell className="menu"/>} {renderItemMenu && <TableCell className="menu">
{this.canBeConfigured && this.renderColumnMenu()}
</TableCell>
}
</TableHead> </TableHead>
)} )}
{ {
!virtual && items.map(item => this.getRow(item.getId())) !virtual && items.map(item => this.getRow(item.getId()))
} }
</Table> </Table>
)} )}
<AddRemoveButtons <AddRemoveButtons
onRemove={selectedItems.length ? removeItemsDialog : null} onRemove={selectedItems.length ? removeItemsDialog : null}
@ -476,6 +521,29 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
); );
} }
renderColumnMenu() {
const { renderTableHeader} = this.props;
return (
<MenuActions
toolbar = {false}
autoCloseOnSelect = {false}
className={cssNames("KubeObjectMenu")}
>
{renderTableHeader.map((cellProps, index) => (
!cellProps.showWithColumn &&
<MenuItem key={index} className="input">
<Checkbox label = {cellProps.title ?? `<${cellProps.className}>`}
className = "MenuCheckbox"
value ={!this.hiddenColumnNames.has(cellProps.className)}
onChange = {(v) => this.updateColumnFilter(v, cellProps.className)}
/>
</MenuItem>
))}
</MenuActions>
);
}
renderFooter() { renderFooter() {
if (this.props.renderFooter) { if (this.props.renderFooter) {
return this.props.renderFooter(this); return this.props.renderFooter(this);

View File

@ -0,0 +1,4 @@
.MenuCheckbox {
width: 100%;
height: 100%;
}

View File

@ -13,6 +13,7 @@ import isString from "lodash/isString";
export interface MenuActionsProps extends Partial<MenuProps> { export interface MenuActionsProps extends Partial<MenuProps> {
className?: string; className?: string;
toolbar?: boolean; // display menu as toolbar with icons toolbar?: boolean; // display menu as toolbar with icons
autoCloseOnSelect?: boolean;
triggerIcon?: string | IconProps | React.ReactNode; triggerIcon?: string | IconProps | React.ReactNode;
removeConfirmationMessage?: React.ReactNode | (() => React.ReactNode); removeConfirmationMessage?: React.ReactNode | (() => React.ReactNode);
updateAction?(): void; updateAction?(): void;
@ -80,7 +81,7 @@ export class MenuActions extends React.Component<MenuActionsProps> {
render() { render() {
const { const {
className, toolbar, children, updateAction, removeAction, triggerIcon, removeConfirmationMessage, className, toolbar, autoCloseOnSelect, children, updateAction, removeAction, triggerIcon, removeConfirmationMessage,
...menuProps ...menuProps
} = this.props; } = this.props;
const menuClassName = cssNames("MenuActions flex", className, { const menuClassName = cssNames("MenuActions flex", className, {
@ -98,7 +99,7 @@ export class MenuActions extends React.Component<MenuActionsProps> {
className={menuClassName} className={menuClassName}
usePortal={autoClose} usePortal={autoClose}
closeOnScroll={autoClose} closeOnScroll={autoClose}
closeOnClickItem={autoClose} closeOnClickItem={autoCloseOnSelect ?? autoClose }
closeOnClickOutside={autoClose} closeOnClickOutside={autoClose}
{...menuProps} {...menuProps}
> >

View File

@ -15,6 +15,7 @@ export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
isChecked?: boolean; // mark checkbox as checked or not isChecked?: boolean; // mark checkbox as checked or not
renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean" renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean"
sortBy?: TableSortBy; // column name, must be same as key in sortable object <Table sortable={}/> sortBy?: TableSortBy; // column name, must be same as key in sortable object <Table sortable={}/>
showWithColumn?: string // className of another column, if it is not empty the current column is not shown in the filter menu, visibility of this one is the same as a specified column, applicable to headers only
_sorting?: Partial<TableSortParams>; // <Table> sorting state, don't use this prop outside (!) _sorting?: Partial<TableSortParams>; // <Table> sorting state, don't use this prop outside (!)
_sort?(sortBy: TableSortBy): void; // <Table> sort function, don't use this prop outside (!) _sort?(sortBy: TableSortBy): void; // <Table> sort function, don't use this prop outside (!)
_nowrap?: boolean; // indicator, might come from parent <TableHead>, don't use this prop outside (!) _nowrap?: boolean; // indicator, might come from parent <TableHead>, don't use this prop outside (!)
@ -63,7 +64,7 @@ export class TableCell extends React.Component<TableCellProps> {
} }
render() { render() {
const { className, checkbox, isChecked, sortBy, _sort, _sorting, _nowrap, children, title, renderBoolean: displayBoolean, ...cellProps } = this.props; const { className, checkbox, isChecked, sortBy, _sort, _sorting, _nowrap, children, title, renderBoolean: displayBoolean, showWithColumn, ...cellProps } = this.props;
const classNames = cssNames("TableCell", className, { const classNames = cssNames("TableCell", className, {
checkbox, checkbox,
nowrap: _nowrap, nowrap: _nowrap,

View File

@ -9,7 +9,7 @@ export interface ItemObject {
@autobind() @autobind()
export abstract class ItemStore<T extends ItemObject = ItemObject> { export abstract class ItemStore<T extends ItemObject = ItemObject> {
abstract loadAll(...args: any[]): Promise<any>; abstract loadAll(...args: any[]): Promise<void>;
protected defaultSorting = (item: T) => item.getName(); protected defaultSorting = (item: T) => item.getName();

View File

@ -6,10 +6,9 @@ import { ItemStore } from "./item.store";
import { apiManager } from "./api/api-manager"; import { apiManager } from "./api/api-manager";
import { IKubeApiQueryParams, KubeApi } from "./api/kube-api"; import { IKubeApiQueryParams, KubeApi } from "./api/kube-api";
import { KubeJsonApiData } from "./api/kube-json-api"; import { KubeJsonApiData } from "./api/kube-json-api";
import { getHostedCluster } from "../common/cluster-store"; import { isAllowedResourceType } from "../common/rbac";
export interface KubeObjectStoreLoadingParams { export interface KubeObjectStoreLoadingParams {
isAdmin: boolean;
namespaces: string[]; namespaces: string[];
api?: KubeApi; api?: KubeApi;
} }
@ -76,16 +75,18 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
} }
} }
protected async loadItems({ isAdmin, namespaces, api }: KubeObjectStoreLoadingParams): Promise<T[]> { protected async loadItems({ namespaces, api }: KubeObjectStoreLoadingParams): Promise<T[]> {
if (!api.isNamespaced) { if (isAllowedResourceType(api.kind)) {
if (isAdmin) return api.list({}, this.query); if (api.isNamespaced) {
return Promise
.all(namespaces.map(namespace => api.list({ namespace })))
.then(items => items.flat());
}
return []; return api.list({}, this.query);
} }
return Promise return [];
.all(namespaces.map(namespace => api.list({ namespace })))
.then(items => items.flat());
} }
protected filterItemsOnLoad(items: T[]) { protected filterItemsOnLoad(items: T[]) {
@ -93,28 +94,21 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
} }
@action @action
async loadAll(params: { namespaces?: string[] } = {}) { async loadAll({ namespaces: contextNamespaces }: { namespaces?: string[] } = {}) {
this.isLoading = true; this.isLoading = true;
let items: T[];
try { try {
let contextNamespaces = params.namespaces; if (!contextNamespaces) {
if (!params.namespaces) {
const { namespaceStore } = await import("./components/+namespaces/namespace.store"); const { namespaceStore } = await import("./components/+namespaces/namespace.store");
await namespaceStore.whenReady;
contextNamespaces = namespaceStore.getContextNamespaces(); contextNamespaces = namespaceStore.getContextNamespaces();
} }
items = await this.loadItems({ let items = await this.loadItems({ namespaces: contextNamespaces, api: this.api });
isAdmin: getHostedCluster().isAdmin,
namespaces: contextNamespaces,
api: this.api,
});
items = this.filterItemsOnLoad(items); items = this.filterItemsOnLoad(items);
items = this.sortItems(items); items = this.sortItems(items);
this.items.replace(items); this.items.replace(items);
this.isLoaded = true; this.isLoaded = true;
} catch (error) { } catch (error) {

133
yarn.lock
View File

@ -1710,10 +1710,10 @@
resolved "https://registry.yarnpkg.com/@types/universal-analytics/-/universal-analytics-0.4.4.tgz#496a52b92b599a0112bec7c12414062de6ea8449" resolved "https://registry.yarnpkg.com/@types/universal-analytics/-/universal-analytics-0.4.4.tgz#496a52b92b599a0112bec7c12414062de6ea8449"
integrity sha512-9g3F0SGxVr4UDd6y07bWtFnkpSSX1Ake7U7AGHgSFrwM6pF53/fV85bfxT2JLWS/3sjLCcyzoYzQlCxpkVo7wA== integrity sha512-9g3F0SGxVr4UDd6y07bWtFnkpSSX1Ake7U7AGHgSFrwM6pF53/fV85bfxT2JLWS/3sjLCcyzoYzQlCxpkVo7wA==
"@types/uuid@^8.0.0": "@types/uuid@^8.3.0":
version "8.0.0" version "8.3.0"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f"
integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==
"@types/webdriverio@^4.13.0", "@types/webdriverio@^4.8.0": "@types/webdriverio@^4.13.0", "@types/webdriverio@^4.8.0":
version "4.13.3" version "4.13.3"
@ -1792,53 +1792,28 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@typescript-eslint/eslint-plugin@^4.0.0": "@typescript-eslint/eslint-plugin@^4.12.0", "@typescript-eslint/eslint-plugin@^4.5.0":
version "4.0.0" version "4.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.0.0.tgz#99349a501447fed91de18346705c0c65cf603bee" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.12.0.tgz#00d1b23b40b58031e6d7c04a5bc6c1a30a2e834a"
integrity sha512-5e6q1TR7gS2P+8W2xndCu7gBh3BzmYEo70OyIdsmCmknHha/yNbz2vdevl+tP1uoaMOcrzg4gyrAijuV3DDBHA== integrity sha512-wHKj6q8s70sO5i39H2g1gtpCXCvjVszzj6FFygneNFyIAxRvNSVz9GML7XpqrB9t7hNutXw+MHnLN/Ih6uyB8Q==
dependencies: dependencies:
"@typescript-eslint/experimental-utils" "4.0.0" "@typescript-eslint/experimental-utils" "4.12.0"
"@typescript-eslint/scope-manager" "4.0.0" "@typescript-eslint/scope-manager" "4.12.0"
debug "^4.1.1" debug "^4.1.1"
functional-red-black-tree "^1.0.1" functional-red-black-tree "^1.0.1"
regexpp "^3.0.0" regexpp "^3.0.0"
semver "^7.3.2" semver "^7.3.2"
tsutils "^3.17.1" tsutils "^3.17.1"
"@typescript-eslint/eslint-plugin@^4.5.0": "@typescript-eslint/experimental-utils@4.12.0":
version "4.8.2" version "4.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.8.2.tgz#cf9102ec800391caa574f589ffe0623cca1d9308" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.12.0.tgz#372838e76db76c9a56959217b768a19f7129546b"
integrity sha512-gQ06QLV5l1DtvYtqOyFLXD9PdcILYqlrJj2l+CGDlPtmgLUzc1GpqciJFIRvyfvgLALpnxYINFuw+n9AZhPBKQ== integrity sha512-MpXZXUAvHt99c9ScXijx7i061o5HEjXltO+sbYfZAAHxv3XankQkPaNi5myy0Yh0Tyea3Hdq1pi7Vsh0GJb0fA==
dependencies:
"@typescript-eslint/experimental-utils" "4.8.2"
"@typescript-eslint/scope-manager" "4.8.2"
debug "^4.1.1"
functional-red-black-tree "^1.0.1"
regexpp "^3.0.0"
semver "^7.3.2"
tsutils "^3.17.1"
"@typescript-eslint/experimental-utils@4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.0.0.tgz#fbec21a3b5ab59127edb6ce2e139ed378cc50eb5"
integrity sha512-hbX6zR+a/vcpFVNJYN/Nbd7gmaMosDTxHEKcvmhWeWcq/0UDifrqmCfkkodbAKL46Fn4ekSBMTyq2zlNDzcQxw==
dependencies: dependencies:
"@types/json-schema" "^7.0.3" "@types/json-schema" "^7.0.3"
"@typescript-eslint/scope-manager" "4.0.0" "@typescript-eslint/scope-manager" "4.12.0"
"@typescript-eslint/types" "4.0.0" "@typescript-eslint/types" "4.12.0"
"@typescript-eslint/typescript-estree" "4.0.0" "@typescript-eslint/typescript-estree" "4.12.0"
eslint-scope "^5.0.0"
eslint-utils "^2.0.0"
"@typescript-eslint/experimental-utils@4.8.2":
version "4.8.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.8.2.tgz#8909a5732f19329cf5ef0c39766170476bff5e50"
integrity sha512-hpTw6o6IhBZEsQsjuw/4RWmceRyESfAiEzAEnXHKG1X7S5DXFaZ4IO1JO7CW1aQ604leQBzjZmuMI9QBCAJX8Q==
dependencies:
"@types/json-schema" "^7.0.3"
"@typescript-eslint/scope-manager" "4.8.2"
"@typescript-eslint/types" "4.8.2"
"@typescript-eslint/typescript-estree" "4.8.2"
eslint-scope "^5.0.0" eslint-scope "^5.0.0"
eslint-utils "^2.0.0" eslint-utils "^2.0.0"
@ -1852,13 +1827,13 @@
"@typescript-eslint/typescript-estree" "4.8.2" "@typescript-eslint/typescript-estree" "4.8.2"
debug "^4.1.1" debug "^4.1.1"
"@typescript-eslint/scope-manager@4.0.0": "@typescript-eslint/scope-manager@4.12.0":
version "4.0.0" version "4.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.0.0.tgz#8c9e3b3b8cdf5a1fbe671d9fad73ff67bc027ea8" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.12.0.tgz#beeb8beca895a07b10c593185a5612f1085ef279"
integrity sha512-9gcWUPoWo7gk/+ZQPg7L1ySRmR5HLIy3Vu6/LfhQbuzIkGm6v2CGIjpVRISoDLFRovNRDImd4aP/sa8O4yIEBg== integrity sha512-QVf9oCSVLte/8jvOsxmgBdOaoe2J0wtEmBr13Yz0rkBNkl5D8bfnf6G4Vhox9qqMIoG7QQoVwd2eG9DM/ge4Qg==
dependencies: dependencies:
"@typescript-eslint/types" "4.0.0" "@typescript-eslint/types" "4.12.0"
"@typescript-eslint/visitor-keys" "4.0.0" "@typescript-eslint/visitor-keys" "4.12.0"
"@typescript-eslint/scope-manager@4.8.2": "@typescript-eslint/scope-manager@4.8.2":
version "4.8.2" version "4.8.2"
@ -1868,23 +1843,23 @@
"@typescript-eslint/types" "4.8.2" "@typescript-eslint/types" "4.8.2"
"@typescript-eslint/visitor-keys" "4.8.2" "@typescript-eslint/visitor-keys" "4.8.2"
"@typescript-eslint/types@4.0.0": "@typescript-eslint/types@4.12.0":
version "4.0.0" version "4.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.0.0.tgz#ec1f9fc06b8558a1d5afa6e337182d08beece7f5" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.12.0.tgz#fb891fe7ccc9ea8b2bbd2780e36da45d0dc055e5"
integrity sha512-bK+c2VLzznX2fUWLK6pFDv3cXGTp7nHIuBMq1B9klA+QCsqLHOOqe5TQReAQDl7DN2RfH+neweo0oC5hYlG7Rg== integrity sha512-N2RhGeheVLGtyy+CxRmxdsniB7sMSCfsnbh8K/+RUIXYYq3Ub5+sukRCjVE80QerrUBvuEvs4fDhz5AW/pcL6g==
"@typescript-eslint/types@4.8.2": "@typescript-eslint/types@4.8.2":
version "4.8.2" version "4.8.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.8.2.tgz#c862dd0e569d9478eb82d6aee662ea53f5661a36" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.8.2.tgz#c862dd0e569d9478eb82d6aee662ea53f5661a36"
integrity sha512-z1/AVcVF8ju5ObaHe2fOpZYEQrwHyZ7PTOlmjd3EoFeX9sv7UekQhfrCmgUO7PruLNfSHrJGQvrW3Q7xQ8EoAw== integrity sha512-z1/AVcVF8ju5ObaHe2fOpZYEQrwHyZ7PTOlmjd3EoFeX9sv7UekQhfrCmgUO7PruLNfSHrJGQvrW3Q7xQ8EoAw==
"@typescript-eslint/typescript-estree@4.0.0": "@typescript-eslint/typescript-estree@4.12.0":
version "4.0.0" version "4.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.0.0.tgz#2244c63de2f2190bc5718eb0fb3fd2c437d42097" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.12.0.tgz#3963418c850f564bdab3882ae23795d115d6d32e"
integrity sha512-ewFMPi2pMLDNIXGMPdf8r7El2oPSZw9PEYB0j+WcpKd7AX2ARmajGa7RUHTukllWX2bj4vWX6JLE1Oih2BMokA== integrity sha512-gZkFcmmp/CnzqD2RKMich2/FjBTsYopjiwJCroxqHZIY11IIoN0l5lKqcgoAPKHt33H2mAkSfvzj8i44Jm7F4w==
dependencies: dependencies:
"@typescript-eslint/types" "4.0.0" "@typescript-eslint/types" "4.12.0"
"@typescript-eslint/visitor-keys" "4.0.0" "@typescript-eslint/visitor-keys" "4.12.0"
debug "^4.1.1" debug "^4.1.1"
globby "^11.0.1" globby "^11.0.1"
is-glob "^4.0.1" is-glob "^4.0.1"
@ -1906,12 +1881,12 @@
semver "^7.3.2" semver "^7.3.2"
tsutils "^3.17.1" tsutils "^3.17.1"
"@typescript-eslint/visitor-keys@4.0.0": "@typescript-eslint/visitor-keys@4.12.0":
version "4.0.0" version "4.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.0.0.tgz#e2bbb69d98076d6a3f06abcb2048225a74362c33" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.12.0.tgz#a470a79be6958075fa91c725371a83baf428a67a"
integrity sha512-sTouJbv6rjVJeTE4lpSBVYXq/u5K3gbB6LKt7ccFEZPTZB/VeQ0ssUz9q5Hx++sCqBbdF8PzrrgvEnicXAR6NQ== integrity sha512-hVpsLARbDh4B9TKYz5cLbcdMIOAoBYgFPCSP9FFS/liSF+b33gVNq8JHY3QGhHNVz85hObvL7BEYLlgx553WCw==
dependencies: dependencies:
"@typescript-eslint/types" "4.0.0" "@typescript-eslint/types" "4.12.0"
eslint-visitor-keys "^2.0.0" eslint-visitor-keys "^2.0.0"
"@typescript-eslint/visitor-keys@4.8.2": "@typescript-eslint/visitor-keys@4.8.2":
@ -3463,10 +3438,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
inherits "^2.0.1" inherits "^2.0.1"
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
circular-dependency-plugin@^5.2.0: circular-dependency-plugin@^5.2.2:
version "5.2.0" version "5.2.2"
resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.2.0.tgz#e09dbc2dd3e2928442403e2d45b41cea06bc0a93" resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz#39e836079db1d3cf2f988dc48c5188a44058b600"
integrity sha512-7p4Kn/gffhQaavNfyDFg7LS5S/UT1JAjyGd4UqR2+jzoYF02eDkj0Ec3+48TsIa4zghjLY87nQHIh/ecK9qLdw== integrity sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ==
class-utils@^0.3.5: class-utils@^0.3.5:
version "0.3.6" version "0.3.6"
@ -4254,27 +4229,13 @@ debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
dependencies: dependencies:
ms "^2.1.1" ms "^2.1.1"
debug@^4.0.1, debug@^4.1.0: debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
dependencies:
ms "^2.1.1"
debug@^4.1.1:
version "4.3.1" version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
dependencies: dependencies:
ms "2.1.2" ms "2.1.2"
debug@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
dependencies:
ms "2.1.2"
debuglog@^1.0.1: debuglog@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
@ -13803,10 +13764,10 @@ uuid@^7.0.3:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
uuid@^8.1.0: uuid@^8.3.2:
version "8.1.0" version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg== integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-compile-cache@2.0.3: v8-compile-cache@2.0.3:
version "2.0.3" version "2.0.3"