mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge remote-tracking branch 'origin/master' into webpack5-upgrade-attempt
This commit is contained in:
commit
8a6ef6c9f0
28
.github/workflows/electronegativity.yml
vendored
Normal file
28
.github/workflows/electronegativity.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
name: Electronegativity
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build_job:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
|
||||
- uses: doyensec/electronegativity-action@v1.1
|
||||
with:
|
||||
input: src/
|
||||
electron-version: "14.2.4"
|
||||
severity: medium
|
||||
|
||||
- name: Upload sarif
|
||||
uses: github/codeql-action/upload-sarif@v1
|
||||
with:
|
||||
sarif_file: ../results
|
||||
25
.github/workflows/main.yml
vendored
25
.github/workflows/main.yml
vendored
@ -6,6 +6,9 @@ on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
concurrency:
|
||||
group: publish-docs
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
verify-docs:
|
||||
name: Verify docs
|
||||
@ -17,7 +20,7 @@ jobs:
|
||||
- name: Set up Python 3.7
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Checkout Release from lens
|
||||
uses: actions/checkout@v2
|
||||
@ -31,8 +34,8 @@ jobs:
|
||||
|
||||
- name: Generate Extensions API Reference using typedocs
|
||||
run: |
|
||||
yarn install
|
||||
yarn typedocs-extensions-api
|
||||
yarn install
|
||||
yarn typedocs-extensions-api
|
||||
|
||||
- name: Verify that the markdown is valid
|
||||
run: |
|
||||
@ -49,7 +52,7 @@ jobs:
|
||||
- name: Set up Python 3.7
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@ -64,8 +67,8 @@ jobs:
|
||||
|
||||
- name: git config
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
|
||||
- name: Using Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
@ -74,13 +77,13 @@ jobs:
|
||||
|
||||
- name: Generate Extensions API Reference using typedocs
|
||||
run: |
|
||||
yarn install
|
||||
yarn typedocs-extensions-api
|
||||
yarn install
|
||||
yarn typedocs-extensions-api
|
||||
|
||||
- name: mkdocs deploy master
|
||||
if: contains(github.ref, 'refs/heads/master')
|
||||
run: |
|
||||
mike deploy --push master
|
||||
mike deploy --push master
|
||||
|
||||
- name: Get the release version
|
||||
if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease
|
||||
@ -90,5 +93,5 @@ jobs:
|
||||
- name: mkdocs deploy new release
|
||||
if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease
|
||||
run: |
|
||||
mike deploy --push --update-aliases ${{ steps.get_version.outputs.VERSION }} latest
|
||||
mike set-default --push ${{ steps.get_version.outputs.VERSION }}
|
||||
mike deploy --push --update-aliases ${{ steps.get_version.outputs.VERSION }} latest
|
||||
mike set-default --push ${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
@ -6,7 +6,7 @@ Lens releases are built by CICD automatically on git tags. The typical release p
|
||||
|
||||
1. If doing a patch release checkout the `release/vMAJOR.MINOR` branch for the appropriate `MAJOR`/`MINOR` version and manually `cherry-pick` the PRs required for the patch that were commited to master. If there are any conflicts they must be resolved manually. If necessary, get assistance from the PR authors.
|
||||
|
||||
This can be helped (if you have the `gh` CLI installed) by running:
|
||||
This can be helped (if you have the `gh` CLI and `jq` installed) by running:
|
||||
```
|
||||
gh api -XGET "/repos/lensapp/lens/pulls?state=closed&per_page=100" | jq -r '[.[] | select(.milestone.title == "<VERSION>") | select((.merged_at | type) == "string")] | sort_by(.merged_at) | map(.merge_commit_sha) | join(" ")'
|
||||
```
|
||||
@ -36,11 +36,12 @@ But you will probably need to verify that all the PRs have correct milestones.
|
||||
gh api -XGET "/repos/lensapp/lens/pulls?state=closed&per_page=100" | jq -r '[.[] | select(.milestone.title == "<VERSION>") | select((.merged_at | type) == "string")] | sort_by(.merged_at) | map([.title, " (**#", .number, "**) ", .user.html_url] | join ("")) | join("\n")' | pbcopy
|
||||
```
|
||||
|
||||
And if you want to specify just bug fixes then you can add the following to the end of the `[ ... ]` section in the above command (before the `sort_by`) to just have bug fixes. Switch to `== "enhancement"` for enhancements and `all()` with `. != "bug && . != "enhancement"` for maintanence sections.
|
||||
And if you want to specify just bug fixes then you can add the following to the end of the `[ ... ]` section in the above command (before the `sort_by`) to just have bug fixes. Switch to `== "enhancement"` for enhancements and `all()` instead of `any()` with `. != "bug and . != "enhancement"` for maintanence sections.
|
||||
|
||||
```
|
||||
| select(any(.labels | map(.name)[]; . == "bug"))
|
||||
```
|
||||
See notes below for ideas for building changelogs.
|
||||
|
||||
1. After the PR is accepted and passes CI (and before merging), go to the same branch and run `make tag-release` (set GIT_REMOTE if necessary). This additionally triggers the azure jobs to build the binaries and put them on S3.
|
||||
1. If the CI fails at this stage the problem needs to be fixed. Sometimes an azure job fails due to outside service issues (e.g. Apple signing occasionally fails), in which case the specific azure job can be rerun from https://dev.azure.com/lensapp/lensapp/_build. Otherwise changes to the codebase may need to be done and committed to the release branch and pushed to https://github.com/lensapp/lens. CI will run again. As well the release tag needs to be manually set to this new commit. You can do something like:
|
||||
@ -64,3 +65,31 @@ Other tasks
|
||||
- announce the release on lens and lens-hq slack channels (release is announced automatically on the community slack lens channel through the above publishing process)
|
||||
- announce on lens-hq that master is open for PR merges for the next release (for major/minor releases)
|
||||
- update issues on github (bump those that did not make it into the release to a subsequent release) (for major/minor/patch releases)
|
||||
|
||||
|
||||
### Rough Notes
|
||||
|
||||
To get all of the PRs since a certain date you can use something like:
|
||||
```
|
||||
gh api -XGET "/repos/lensapp/lens/pulls?state=closed&per_page=100&q=committer-date:>YYYY-MM-DD" > prs.json
|
||||
```
|
||||
|
||||
If there are more than 100 PRs you'll need to rerun for multiple pages:
|
||||
```
|
||||
gh api -XGET "/repos/lensapp/lens/pulls?state=closed&per_page=100&page=2&q=committer-date:>YYYY-MM-DD" >> prs.json
|
||||
```
|
||||
|
||||
To get all the bug PRs:
|
||||
```
|
||||
jq -r '[.[] | select((.merged_at | type) == "string") | select(any(.labels | map(.name)[]; . == "bug"))] | sort_by(.merged_at) | reverse | map([.title, " (**#", .number, "**) ", .user.html_url] | join ("")) | join("\n")' prs.json
|
||||
```
|
||||
|
||||
To get all the enhancement PRs:
|
||||
```
|
||||
jq -r '[.[] | select((.merged_at | type) == "string") | select(any(.labels | map(.name)[]; . == "enhancement"))] | sort_by(.merged_at) | reverse | map([.title, " (**#", .number, "**) ", .user.html_url] | join ("")) | join("\n")' prs.json
|
||||
```
|
||||
|
||||
To get all of the maintenance PRs:
|
||||
```
|
||||
jq -r '[.[] | select((.merged_at | type) == "string") | select(all(.labels | map(.name)[]; . != "bug" and . != "enhancement" and . != "skip-changelog"))] | sort_by(.merged_at) | reverse | map([.title, " (**#", .number, "**) ", .user.html_url] | join ("")) | join("\n")' prs.json
|
||||
```
|
||||
@ -233,16 +233,16 @@ const commonPageTests: CommonPageTest[] = [{
|
||||
},
|
||||
},
|
||||
{
|
||||
drawerId: "apps",
|
||||
drawerId: "helm",
|
||||
pages: [
|
||||
{
|
||||
name: "Charts",
|
||||
href: "/apps/charts",
|
||||
href: "/helm/charts",
|
||||
expectedSelector: "div.HelmCharts input",
|
||||
},
|
||||
{
|
||||
name: "Releases",
|
||||
href: "/apps/releases",
|
||||
href: "/helm/releases",
|
||||
expectedSelector: "h5.title",
|
||||
expectedText: "Releases",
|
||||
},
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"productName": "OpenLens",
|
||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||
"homepage": "https://github.com/lensapp/lens",
|
||||
"version": "5.3.0",
|
||||
"version": "5.4.0-beta.1",
|
||||
"main": "static/build/main.js",
|
||||
"copyright": "© 2021 OpenLens Authors",
|
||||
"license": "MIT",
|
||||
@ -194,8 +194,8 @@
|
||||
"@hapi/call": "^8.0.1",
|
||||
"@hapi/subtext": "^7.0.3",
|
||||
"@kubernetes/client-node": "^0.16.1",
|
||||
"@ogre-tools/injectable": "3.2.0",
|
||||
"@ogre-tools/injectable-react": "3.2.0",
|
||||
"@ogre-tools/injectable": "3.2.1",
|
||||
"@ogre-tools/injectable-react": "3.2.1",
|
||||
"@sentry/electron": "^2.5.4",
|
||||
"@sentry/integrations": "^6.15.0",
|
||||
"@types/circular-dependency-plugin": "5.0.4",
|
||||
@ -302,7 +302,7 @@
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-router-dom": "^5.3.2",
|
||||
"@types/react-select": "3.1.2",
|
||||
"@types/react-table": "^7.7.8",
|
||||
"@types/react-table": "^7.7.9",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/readable-stream": "^2.3.12",
|
||||
|
||||
21
src/common/cluster-store/allowed-resources.injectable.ts
Normal file
21
src/common/cluster-store/allowed-resources.injectable.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { comparer, computed } from "mobx";
|
||||
import hostedClusterInjectable from "./hosted-cluster.injectable";
|
||||
|
||||
const allowedResourcesInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const cluster = di.inject(hostedClusterInjectable);
|
||||
|
||||
return computed(() => new Set(cluster.allowedResources), {
|
||||
// This needs to be here so that during refresh changes are only propogated when necessary
|
||||
equals: (cur, prev) => comparer.structural(cur, prev),
|
||||
});
|
||||
},
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default allowedResourcesInjectable;
|
||||
@ -3,8 +3,8 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { getHostedClusterId } from "../../utils";
|
||||
import clusterStoreInjectable from "../cluster-store.injectable";
|
||||
import { getHostedClusterId } from "../utils";
|
||||
import clusterStoreInjectable from "./cluster-store.injectable";
|
||||
|
||||
const hostedClusterInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
@ -21,7 +21,7 @@ export abstract class ItemStore<Item extends ItemObject> {
|
||||
@observable isLoading = false;
|
||||
@observable isLoaded = false;
|
||||
@observable items = observable.array<Item>([], { deep: false });
|
||||
@observable selectedItemsIds = observable.map<string, boolean>();
|
||||
@observable selectedItemsIds = observable.set<string>();
|
||||
|
||||
constructor() {
|
||||
makeObservable(this);
|
||||
@ -29,7 +29,11 @@ export abstract class ItemStore<Item extends ItemObject> {
|
||||
}
|
||||
|
||||
@computed get selectedItems(): Item[] {
|
||||
return this.items.filter(item => this.selectedItemsIds.get(item.getId()));
|
||||
return this.pickOnlySelected(this.items);
|
||||
}
|
||||
|
||||
public pickOnlySelected(items: Item[]): Item[] {
|
||||
return items.filter(item => this.selectedItemsIds.has(item.getId()));
|
||||
}
|
||||
|
||||
public getItems(): Item[] {
|
||||
@ -152,12 +156,12 @@ export abstract class ItemStore<Item extends ItemObject> {
|
||||
}
|
||||
|
||||
isSelected(item: Item) {
|
||||
return !!this.selectedItemsIds.get(item.getId());
|
||||
return this.selectedItemsIds.has(item.getId());
|
||||
}
|
||||
|
||||
@action
|
||||
select(item: Item) {
|
||||
this.selectedItemsIds.set(item.getId(), true);
|
||||
this.selectedItemsIds.add(item.getId());
|
||||
}
|
||||
|
||||
@action
|
||||
@ -207,6 +211,8 @@ export abstract class ItemStore<Item extends ItemObject> {
|
||||
|
||||
async removeSelectedItems?(): Promise<any>;
|
||||
|
||||
async removeItems?(items: Item[]): Promise<void>;
|
||||
|
||||
* [Symbol.iterator]() {
|
||||
yield* this.items;
|
||||
}
|
||||
|
||||
131
src/common/k8s-api/__tests__/nodes.test.ts
Normal file
131
src/common/k8s-api/__tests__/nodes.test.ts
Normal file
@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { Node } from "../endpoints";
|
||||
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
describe("Nodes tests", () => {
|
||||
describe("getRoleLabels()", () => {
|
||||
it("should return empty string if labels is not present", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("");
|
||||
});
|
||||
|
||||
it("should return empty string if labels is empty object", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {},
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("");
|
||||
});
|
||||
|
||||
it("should return rest of keys with substring node-role.kubernetes.io/", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {
|
||||
"node-role.kubernetes.io/foobar": "bat",
|
||||
"hellonode-role.kubernetes.io/foobar1": "bat",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("foobar, foobar1");
|
||||
});
|
||||
|
||||
it("should return rest of keys with substring node-role.kubernetes.io/ after last /", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {
|
||||
"node-role.kubernetes.io/foobar": "bat",
|
||||
"hellonode-role.kubernetes.io//////foobar1": "bat",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("foobar, foobar1");
|
||||
});
|
||||
|
||||
it("should return value of label kubernetes.io/role if present", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {
|
||||
"kubernetes.io/role": "master",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("master");
|
||||
});
|
||||
|
||||
it("should return value of label node.kubernetes.io/role if present", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {
|
||||
"node.kubernetes.io/role": "master",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("master");
|
||||
});
|
||||
|
||||
it("all sources should be joined together", () => {
|
||||
const node = new Node({
|
||||
apiVersion: "foo",
|
||||
kind: "Node",
|
||||
metadata: {
|
||||
name: "bar",
|
||||
resourceVersion: "1",
|
||||
uid: "bat",
|
||||
labels: {
|
||||
"aksjhdkjahsdnode-role.kubernetes.io/foobar": "bat",
|
||||
"kubernetes.io/role": "master",
|
||||
"node.kubernetes.io/role": "master-v2-max",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(node.getRoleLabels()).toBe("foobar, master, master-v2-max");
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -7,7 +7,7 @@ import yaml from "js-yaml";
|
||||
import { formatDuration } from "../../utils";
|
||||
import capitalize from "lodash/capitalize";
|
||||
import { apiBase } from "../index";
|
||||
import { helmChartStore } from "../../../renderer/components/+apps-helm-charts/helm-chart.store";
|
||||
import { helmChartStore } from "../../../renderer/components/+helm-charts/helm-chart.store";
|
||||
import type { ItemObject } from "../../item.store";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { JsonApiData } from "../json-api";
|
||||
|
||||
@ -140,6 +140,12 @@ function* getTrueConditionTypes(conditions: IterableIterator<NodeCondition> | It
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This regex is used in the `getRoleLabels()` method bellow, but placed here
|
||||
* as factoring out regexes is best practice.
|
||||
*/
|
||||
const nodeRoleLabelKeyMatcher = /^.*node-role.kubernetes.io\/+(?<role>.+)$/;
|
||||
|
||||
export class Node extends KubeObject {
|
||||
static kind = "Node";
|
||||
static namespaced = false;
|
||||
@ -165,17 +171,29 @@ export class Node extends KubeObject {
|
||||
return this.spec.taints || [];
|
||||
}
|
||||
|
||||
getRoleLabels() {
|
||||
if (!this.metadata?.labels || typeof this.metadata.labels !== "object") {
|
||||
getRoleLabels(): string {
|
||||
const { labels } = this.metadata;
|
||||
|
||||
if (!labels || typeof labels !== "object") {
|
||||
return "";
|
||||
}
|
||||
|
||||
const roleLabels = Object.keys(this.metadata.labels)
|
||||
.filter(key => key.includes("node-role.kubernetes.io"))
|
||||
.map(key => key.match(/([^/]+$)/)[0]); // all after last slash
|
||||
const roleLabels: string[] = [];
|
||||
|
||||
if (this.metadata.labels["kubernetes.io/role"] != undefined) {
|
||||
roleLabels.push(this.metadata.labels["kubernetes.io/role"]);
|
||||
for (const labelKey of Object.keys(labels)) {
|
||||
const match = nodeRoleLabelKeyMatcher.exec(labelKey);
|
||||
|
||||
if (match) {
|
||||
roleLabels.push(match.groups.role);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof labels["kubernetes.io/role"] === "string") {
|
||||
roleLabels.push(labels["kubernetes.io/role"]);
|
||||
}
|
||||
|
||||
if (typeof labels["node.kubernetes.io/role"] === "string") {
|
||||
roleLabels.push(labels["node.kubernetes.io/role"]);
|
||||
}
|
||||
|
||||
return roleLabels.join(", ");
|
||||
|
||||
@ -16,12 +16,9 @@ import { KubeObjectConstructor, KubeObject, KubeStatus } from "./kube-object";
|
||||
import byline from "byline";
|
||||
import type { IKubeWatchEvent } from "./kube-watch-event";
|
||||
import { KubeJsonApi, KubeJsonApiData } from "./kube-json-api";
|
||||
import { noop } from "../utils";
|
||||
import { noop, WrappedAbortController } from "../utils";
|
||||
import type { RequestInit } from "node-fetch";
|
||||
|
||||
// BUG: https://github.com/mysticatea/abort-controller/pull/22
|
||||
// eslint-disable-next-line import/no-named-as-default
|
||||
import AbortController from "abort-controller";
|
||||
import type AbortController from "abort-controller";
|
||||
import { Agent, AgentOptions } from "https";
|
||||
import type { Patch } from "rfc6902";
|
||||
|
||||
@ -582,14 +579,7 @@ export class KubeApi<T extends KubeObject> {
|
||||
const { watchId = `${this.kind.toLowerCase()}-${this.watchId++}` } = opts;
|
||||
|
||||
// Create AbortController for this request
|
||||
const abortController = new AbortController();
|
||||
|
||||
// If caller aborts, abort using request's abortController
|
||||
if (opts.abortController) {
|
||||
opts.abortController.signal.addEventListener("abort", () => {
|
||||
abortController.abort();
|
||||
});
|
||||
}
|
||||
const abortController = new WrappedAbortController(opts.abortController);
|
||||
|
||||
abortController.signal.addEventListener("abort", () => {
|
||||
logger.info(`[KUBE-API] watch (${watchId}) aborted ${watchUrl}`);
|
||||
@ -687,7 +677,6 @@ export class KubeApi<T extends KubeObject> {
|
||||
protected modifyWatchEvent(event: IKubeWatchEvent<KubeJsonApiData>) {
|
||||
if (event.type === "ERROR") {
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
ensureObjectSelfLink(this, event.object);
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
import type { ClusterContext } from "./cluster-context";
|
||||
|
||||
import { action, computed, makeObservable, observable, reaction, when } from "mobx";
|
||||
import { autoBind, noop, rejectPromiseBy } from "../utils";
|
||||
import { autoBind, Disposer, noop, rejectPromiseBy } from "../utils";
|
||||
import { KubeObject, KubeStatus } from "./kube-object";
|
||||
import type { IKubeWatchEvent } from "./kube-watch-event";
|
||||
import { ItemStore } from "../item.store";
|
||||
@ -369,6 +369,10 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
||||
return Promise.all(this.selectedItems.map(this.remove));
|
||||
}
|
||||
|
||||
async removeItems(items: T[]) {
|
||||
await Promise.all(items.map(this.remove));
|
||||
}
|
||||
|
||||
// collect items from watch-api events to avoid UI blowing up with huge streams of data
|
||||
protected eventsBuffer = observable.array<IKubeWatchEvent<KubeJsonApiData>>([], { deep: false });
|
||||
|
||||
@ -378,7 +382,7 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
||||
});
|
||||
}
|
||||
|
||||
subscribe({ onLoadFailure, abortController = new AbortController() }: KubeObjectStoreSubscribeParams = {}) {
|
||||
subscribe({ onLoadFailure, abortController = new AbortController() }: KubeObjectStoreSubscribeParams = {}): Disposer {
|
||||
if (this.api.isNamespaced) {
|
||||
Promise.race([rejectPromiseBy(abortController.signal), Promise.all([this.contextReady, this.namespacesReady])])
|
||||
.then(() => {
|
||||
|
||||
@ -5,10 +5,10 @@
|
||||
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../utils/buildUrl";
|
||||
import { appsRoute } from "./apps";
|
||||
import { helmRoute } from "./helm";
|
||||
|
||||
export const helmChartsRoute: RouteProps = {
|
||||
path: `${appsRoute.path}/charts/:repo?/:chartName?`,
|
||||
path: `${helmRoute.path}/charts/:repo?/:chartName?`,
|
||||
};
|
||||
|
||||
export interface HelmChartsRouteParams {
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../utils/buildUrl";
|
||||
|
||||
export const appsRoute: RouteProps = {
|
||||
path: "/apps",
|
||||
export const helmRoute: RouteProps = {
|
||||
path: "/helm",
|
||||
};
|
||||
|
||||
export const appsURL = buildURL(appsRoute.path);
|
||||
export const helmURL = buildURL(helmRoute.path);
|
||||
@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
export * from "./add-cluster";
|
||||
export * from "./apps";
|
||||
export * from "./catalog";
|
||||
export * from "./cluster-view";
|
||||
export * from "./cluster";
|
||||
@ -16,6 +15,7 @@ export * from "./entity-settings";
|
||||
export * from "./events";
|
||||
export * from "./extensions";
|
||||
export * from "./helm-charts";
|
||||
export * from "./helm";
|
||||
export * from "./hpa";
|
||||
export * from "./ingresses";
|
||||
export * from "./limit-ranges";
|
||||
|
||||
@ -5,10 +5,10 @@
|
||||
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../utils/buildUrl";
|
||||
import { appsRoute } from "./apps";
|
||||
import { helmRoute } from "./helm";
|
||||
|
||||
export const releaseRoute: RouteProps = {
|
||||
path: `${appsRoute.path}/releases/:namespace?/:name?`,
|
||||
path: `${helmRoute.path}/releases/:namespace?/:name?`,
|
||||
};
|
||||
|
||||
export interface ReleaseRouteParams {
|
||||
|
||||
15
src/common/utils/abort-controller.ts
Normal file
15
src/common/utils/abort-controller.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import AbortController from "abort-controller";
|
||||
|
||||
export class WrappedAbortController extends AbortController {
|
||||
constructor(parent?: AbortController) {
|
||||
super();
|
||||
|
||||
parent?.signal.addEventListener("abort", () => {
|
||||
this.abort();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { ClusterStore } from "../cluster-store/cluster-store";
|
||||
import type { KubeResource } from "../rbac";
|
||||
import { getHostedClusterId } from "./cluster-id-url-parsing";
|
||||
|
||||
export function isAllowedResource(resource: KubeResource | KubeResource[]) {
|
||||
const resources = [resource].flat();
|
||||
const cluster = ClusterStore.getInstance().getById(getHostedClusterId());
|
||||
|
||||
if (!cluster?.allowedResources) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resources.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const allowedResources = new Set(cluster.allowedResources);
|
||||
|
||||
return resources.every(resource => allowedResources.has(resource));
|
||||
}
|
||||
@ -10,6 +10,7 @@ export function noop<T extends any[]>(...args: T): void {
|
||||
return void args;
|
||||
}
|
||||
|
||||
export * from "./abort-controller";
|
||||
export * from "./app-version";
|
||||
export * from "./autobind";
|
||||
export * from "./camelCase";
|
||||
|
||||
22
src/common/utils/is-allowed-resource.injectable.ts
Normal file
22
src/common/utils/is-allowed-resource.injectable.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import allowedResourcesInjectable from "../cluster-store/allowed-resources.injectable";
|
||||
import type { KubeResource } from "../rbac";
|
||||
|
||||
export type IsAllowedResource = (resource: KubeResource) => boolean;
|
||||
|
||||
// TODO: This injectable obscures MobX de-referencing. Make it more apparent in usage.
|
||||
const isAllowedResourceInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const allowedResources = di.inject(allowedResourcesInjectable);
|
||||
|
||||
return (resource: KubeResource) => allowedResources.get().has(resource);
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default isAllowedResourceInjectable;
|
||||
@ -3,7 +3,14 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export { isAllowedResource } from "../../common/utils/allowed-resource";
|
||||
/**
|
||||
* @deprecated This function never works
|
||||
* @returns false
|
||||
*/
|
||||
export function isAllowedResource(...args: any[]) {
|
||||
return Boolean(void args);
|
||||
}
|
||||
|
||||
export { ResourceStack } from "../../common/k8s/resource-stack";
|
||||
export { apiManager } from "../../common/k8s-api/api-manager";
|
||||
export { KubeApi, forCluster, forRemoteCluster } from "../../common/k8s-api/kube-api";
|
||||
|
||||
@ -2,8 +2,19 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { KubeResource } from "../../common/rbac";
|
||||
import isAllowedResourceInjectable from "../../common/utils/is-allowed-resource.injectable";
|
||||
import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
||||
import { castArray } from "lodash/fp";
|
||||
|
||||
export function isAllowedResource(resource: KubeResource | KubeResource[]) {
|
||||
const _isAllowedResource = asLegacyGlobalFunctionForExtensionApi(isAllowedResourceInjectable);
|
||||
|
||||
const resources = castArray(resource);
|
||||
|
||||
return resources.every(x => _isAllowedResource(x));
|
||||
}
|
||||
|
||||
export { isAllowedResource } from "../../common/utils/allowed-resource";
|
||||
export { ResourceStack } from "../../common/k8s/resource-stack";
|
||||
export { apiManager } from "../../common/k8s-api/api-manager";
|
||||
export { KubeObjectStore } from "../../common/k8s-api/kube-object.store";
|
||||
|
||||
@ -179,12 +179,13 @@ export async function getHistory(name: string, namespace: string, kubeconfigPath
|
||||
}
|
||||
|
||||
export async function rollback(name: string, namespace: string, revision: number, kubeconfigPath: string) {
|
||||
return JSON.parse(await execHelm([
|
||||
await execHelm([
|
||||
"rollback",
|
||||
name,
|
||||
`${revision}`,
|
||||
"--namespace", namespace,
|
||||
"--kubeconfig", kubeconfigPath,
|
||||
]));
|
||||
]);
|
||||
}
|
||||
|
||||
async function getResources(name: string, namespace: string, kubeconfigPath: string, kubectlPath: string) {
|
||||
|
||||
@ -102,9 +102,7 @@ class HelmService {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug("Rollback release");
|
||||
const output = rollback(releaseName, namespace, revision, proxyKubeconfig);
|
||||
|
||||
return { message: output };
|
||||
await rollback(releaseName, namespace, revision, proxyKubeconfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -71,9 +71,9 @@ export class HelmApiRoute {
|
||||
const { cluster, params, payload, response } = request;
|
||||
|
||||
try {
|
||||
const result = await helmService.rollback(cluster, params.release, params.namespace, payload.revision);
|
||||
await helmService.rollback(cluster, params.release, params.namespace, payload.revision);
|
||||
|
||||
respondJson(response, result);
|
||||
response.end();
|
||||
} catch (error) {
|
||||
logger.debug(error);
|
||||
respondText(response, error?.toString() || "Error rolling back chart", 422);
|
||||
|
||||
@ -94,6 +94,7 @@ export class NodeShellSession extends ShellSession {
|
||||
tolerations: [{
|
||||
operator: "Exists",
|
||||
}],
|
||||
priorityClassName: "system-node-critical",
|
||||
containers: [{
|
||||
name: "shell",
|
||||
image: this.cluster.nodeShellImage,
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { ClusterFrameContext } from "./cluster-frame-context";
|
||||
import namespaceStoreInjectable from "../components/+namespaces/namespace-store/namespace-store.injectable";
|
||||
import hostedClusterInjectable from "../../common/cluster-store/hosted-cluster/hosted-cluster.injectable";
|
||||
import hostedClusterInjectable from "../../common/cluster-store/hosted-cluster.injectable";
|
||||
|
||||
const clusterFrameContextInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { updateRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
|
||||
const updateReleaseInjectable = getInjectable({
|
||||
instantiate: () => updateRelease,
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default updateReleaseInjectable;
|
||||
@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { HelmCharts } from "../+apps-helm-charts";
|
||||
import { HelmReleases } from "../+apps-releases";
|
||||
import { helmChartsURL, helmChartsRoute, releaseURL, releaseRoute } from "../../../common/routes";
|
||||
|
||||
@observer
|
||||
export class Apps extends React.Component {
|
||||
static get tabRoutes(): TabLayoutRoute[] {
|
||||
|
||||
return [
|
||||
{
|
||||
title: "Charts",
|
||||
component: HelmCharts,
|
||||
url: helmChartsURL(),
|
||||
routePath: helmChartsRoute.path.toString(),
|
||||
},
|
||||
{
|
||||
title: "Releases",
|
||||
component: HelmReleases,
|
||||
url: releaseURL(),
|
||||
routePath: releaseRoute.path.toString(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TabLayout className="Apps" tabs={Apps.tabRoutes}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export * from "./apps";
|
||||
@ -43,6 +43,8 @@
|
||||
}
|
||||
|
||||
.pinIcon {
|
||||
--color-active: var(--textColorAccent);
|
||||
|
||||
transition: none;
|
||||
opacity: 0;
|
||||
margin-left: var(--padding);
|
||||
|
||||
37
src/renderer/components/+cluster/sidebar-item.tsx
Normal file
37
src/renderer/components/+cluster/sidebar-item.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import { clusterRoute, clusterURL } from "../../../common/routes";
|
||||
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import { isActiveRoute } from "../../navigation";
|
||||
import { Icon } from "../icon";
|
||||
import { SidebarItem } from "../layout/sidebar-item";
|
||||
|
||||
export interface ClusterSidebarItemProps {}
|
||||
|
||||
interface Dependencies {
|
||||
isAllowedResource: IsAllowedResource;
|
||||
}
|
||||
|
||||
const NonInjectedClusterSidebarItem = observer(({ isAllowedResource }: Dependencies & ClusterSidebarItemProps) => (
|
||||
<SidebarItem
|
||||
id="cluster"
|
||||
text="Cluster"
|
||||
isActive={isActiveRoute(clusterRoute)}
|
||||
isHidden={!isAllowedResource("nodes")}
|
||||
url={clusterURL()}
|
||||
icon={<Icon svg="kube"/>}
|
||||
/>
|
||||
));
|
||||
|
||||
export const ClusterSidebarItem = withInjectables<Dependencies, ClusterSidebarItemProps>(NonInjectedClusterSidebarItem, {
|
||||
getProps: (di, props) => ({
|
||||
isAllowedResource: di.inject(isAllowedResourceInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
@ -9,19 +9,23 @@
|
||||
}
|
||||
|
||||
.quota-entries {
|
||||
margin: -$margin * 0.5;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-left: -1px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.quota {
|
||||
--flex-gap: #{$padding};
|
||||
border: 1px solid var(--halfGray);
|
||||
border-radius: $radius;
|
||||
margin: $margin * 0.5;
|
||||
padding: $padding * 0.5 $padding;
|
||||
transition: all 150ms ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 0 0 1px var(--borderColor);
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export * from "./config";
|
||||
@ -2,22 +2,24 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { ConfigMaps } from "../+config-maps";
|
||||
import { Secrets } from "../+config-secrets";
|
||||
import { ResourceQuotas } from "../+config-resource-quotas";
|
||||
import { PodDisruptionBudgets } from "../+config-pod-disruption-budgets";
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import { HorizontalPodAutoscalers } from "../+config-autoscalers";
|
||||
import { isAllowedResource } from "../../../common/utils/allowed-resource";
|
||||
import { LimitRanges } from "../+config-limit-ranges";
|
||||
import { ConfigMaps } from "../+config-maps";
|
||||
import { PodDisruptionBudgets } from "../+config-pod-disruption-budgets";
|
||||
import { ResourceQuotas } from "../+config-resource-quotas";
|
||||
import { Secrets } from "../+config-secrets";
|
||||
import isAllowedResourceInjectable, { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import type { TabLayoutRoute } from "../layout/tab-layout";
|
||||
import * as routes from "../../../common/routes";
|
||||
|
||||
@observer
|
||||
export class Config extends React.Component {
|
||||
static get tabRoutes(): TabLayoutRoute[] {
|
||||
interface Dependencies {
|
||||
isAllowedResource: IsAllowedResource;
|
||||
}
|
||||
|
||||
function getRouteTabs({ isAllowedResource }: Dependencies) {
|
||||
return computed(() => {
|
||||
const tabs: TabLayoutRoute[] = [];
|
||||
|
||||
if (isAllowedResource("configmaps")) {
|
||||
@ -75,11 +77,14 @@ export class Config extends React.Component {
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TabLayout className="Config" tabs={Config.tabRoutes}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const configRoutesInjectable = getInjectable({
|
||||
instantiate: (di) => getRouteTabs({
|
||||
isAllowedResource: di.inject(isAllowedResourceInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default configRoutesInjectable;
|
||||
31
src/renderer/components/+config/route.tsx
Normal file
31
src/renderer/components/+config/route.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import configRoutesInjectable from "./route-tabs.injectable";
|
||||
|
||||
export interface ConfigRouteProps {}
|
||||
|
||||
interface Dependencies {
|
||||
routes: IComputedValue<TabLayoutRoute[]>;
|
||||
}
|
||||
|
||||
const NonInjectedConfigRoute = observer(({ routes }: Dependencies & ConfigRouteProps) => (
|
||||
<TabLayout
|
||||
className="Config"
|
||||
tabs={routes.get()}
|
||||
/>
|
||||
));
|
||||
|
||||
export const ConfigRoute = withInjectables<Dependencies, ConfigRouteProps>(NonInjectedConfigRoute, {
|
||||
getProps: (di, props) => ({
|
||||
routes: di.inject(configRoutesInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
45
src/renderer/components/+config/sidebar-item.tsx
Normal file
45
src/renderer/components/+config/sidebar-item.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import { configRoute, configURL } from "../../../common/routes";
|
||||
import { isActiveRoute } from "../../navigation";
|
||||
import { Icon } from "../icon";
|
||||
import { SidebarItem } from "../layout/sidebar-item";
|
||||
import type { TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items";
|
||||
import configRoutesInjectable from "./route-tabs.injectable";
|
||||
|
||||
export interface ConfigSidebarItemProps {}
|
||||
|
||||
interface Dependencies {
|
||||
routes: IComputedValue<TabLayoutRoute[]>;
|
||||
}
|
||||
|
||||
const NonInjectedConfigSidebarItem = observer(({ routes }: Dependencies & ConfigSidebarItemProps) => {
|
||||
const tabRoutes = routes.get();
|
||||
|
||||
return (
|
||||
<SidebarItem
|
||||
id="config"
|
||||
text="Configuration"
|
||||
isActive={isActiveRoute(configRoute)}
|
||||
isHidden={tabRoutes.length == 0}
|
||||
url={configURL()}
|
||||
icon={<Icon material="list"/>}
|
||||
>
|
||||
<TabRoutesSidebarItems routes={tabRoutes} />
|
||||
</SidebarItem>
|
||||
);
|
||||
});
|
||||
|
||||
export const ConfigSidebarItem = withInjectables<Dependencies, ConfigSidebarItemProps>(NonInjectedConfigSidebarItem, {
|
||||
getProps: (di, props) => ({
|
||||
routes: di.inject(configRoutesInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Redirect, Route, Switch } from "react-router";
|
||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { CrdList } from "./crd-list";
|
||||
import { CrdResources } from "./crd-resources";
|
||||
import { crdURL, crdDefinitionsRoute, crdResourcesRoute } from "../../../common/routes";
|
||||
|
||||
@observer
|
||||
export class CustomResources extends React.Component {
|
||||
static get tabRoutes(): TabLayoutRoute[] {
|
||||
return [
|
||||
{
|
||||
title: "Definitions",
|
||||
component: CustomResources,
|
||||
url: crdURL(),
|
||||
routePath: String(crdDefinitionsRoute.path),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TabLayout>
|
||||
<Switch>
|
||||
<Route component={CrdList} {...crdDefinitionsRoute} exact/>
|
||||
<Route component={CrdResources} {...crdResourcesRoute}/>
|
||||
<Redirect to={crdURL()}/>
|
||||
</Switch>
|
||||
</TabLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { computed, IComputedValue } from "mobx";
|
||||
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints";
|
||||
import { getOrInsert } from "../../utils";
|
||||
import customResourceDefinitionsInjectable from "./custom-resources.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
definitions: IComputedValue<CustomResourceDefinition[]>;
|
||||
}
|
||||
|
||||
function getGroupedCustomResourceDefinitions({ definitions }: Dependencies) {
|
||||
return computed(() => {
|
||||
const groups = new Map<string, CustomResourceDefinition[]>();
|
||||
|
||||
for (const crd of definitions.get()) {
|
||||
getOrInsert(groups, crd.getGroup(), []).push(crd);
|
||||
}
|
||||
|
||||
return groups;
|
||||
});
|
||||
}
|
||||
|
||||
const groupedCustomResourceDefinitionsInjectable = getInjectable({
|
||||
instantiate: (di) => getGroupedCustomResourceDefinitions({
|
||||
definitions: di.inject(customResourceDefinitionsInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default groupedCustomResourceDefinitionsInjectable;
|
||||
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { computed, IComputedValue } from "mobx";
|
||||
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints";
|
||||
import { crdURL, crdDefinitionsRoute } from "../../../common/routes";
|
||||
import type { TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { CrdList } from "./crd-list";
|
||||
import { CrdResources } from "./crd-resources";
|
||||
import groupedCustomResourceDefinitionsInjectable from "./grouped-custom-resources.injectable";
|
||||
|
||||
export interface CustomResourceTabLayoutRoute extends TabLayoutRoute {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface CustomResourceGroupTabLayoutRoute extends CustomResourceTabLayoutRoute {
|
||||
subRoutes?: CustomResourceTabLayoutRoute[];
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
customResourcesDefinitions: IComputedValue<Map<string, CustomResourceDefinition[]>>;
|
||||
}
|
||||
|
||||
function getRouteTabs({ customResourcesDefinitions }: Dependencies) {
|
||||
return computed(() => {
|
||||
const tabs: CustomResourceGroupTabLayoutRoute[] = [
|
||||
{
|
||||
id: "definitions",
|
||||
title: "Definitions",
|
||||
component: CrdList,
|
||||
url: crdURL(),
|
||||
routePath: String(crdDefinitionsRoute.path),
|
||||
exact: true,
|
||||
},
|
||||
];
|
||||
|
||||
for (const [group, definitions] of customResourcesDefinitions.get()) {
|
||||
tabs.push({
|
||||
id: `crd-group:${group}`,
|
||||
title: group,
|
||||
routePath: crdURL({ query: { groups: group }}),
|
||||
component: CrdResources,
|
||||
subRoutes: definitions.map(crd => ({
|
||||
id: `crd-resource:${crd.getResourceApiBase()}`,
|
||||
title: crd.getResourceKind(),
|
||||
routePath: crd.getResourceUrl(),
|
||||
component: CrdResources,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
return tabs;
|
||||
});
|
||||
}
|
||||
|
||||
const customResourcesRouteTabsInjectable = getInjectable({
|
||||
instantiate: (di) => getRouteTabs({
|
||||
customResourcesDefinitions: di.inject(groupedCustomResourceDefinitionsInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default customResourcesRouteTabsInjectable;
|
||||
43
src/renderer/components/+custom-resources/route.tsx
Normal file
43
src/renderer/components/+custom-resources/route.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Redirect, Route, Switch } from "react-router";
|
||||
import { TabLayout } from "../layout/tab-layout";
|
||||
import { crdURL } from "../../../common/routes";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import type { CustomResourceGroupTabLayoutRoute } from "./route-tabs.injectable";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import customResourcesRouteTabsInjectable from "./route-tabs.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
routes: IComputedValue<CustomResourceGroupTabLayoutRoute[]>;
|
||||
}
|
||||
|
||||
const NonInjectedCustomResourcesRoute = observer(({ routes }: Dependencies) => (
|
||||
<TabLayout>
|
||||
<Switch>
|
||||
{
|
||||
routes.get().map(({ id, component, routePath, exact }) => (
|
||||
<Route
|
||||
key={id}
|
||||
component={component}
|
||||
path={routePath}
|
||||
exact={exact}
|
||||
/>
|
||||
))
|
||||
}
|
||||
<Redirect to={crdURL()}/>
|
||||
</Switch>
|
||||
</TabLayout>
|
||||
));
|
||||
|
||||
export const CustomResourcesRoute = withInjectables<Dependencies>(NonInjectedCustomResourcesRoute, {
|
||||
getProps: (di, props) => ({
|
||||
routes: di.inject(customResourcesRouteTabsInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
76
src/renderer/components/+custom-resources/sidebar-item.tsx
Normal file
76
src/renderer/components/+custom-resources/sidebar-item.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import React, { useEffect } from "react";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import customResourcesRouteTabsInjectable, { type CustomResourceGroupTabLayoutRoute } from "./route-tabs.injectable";
|
||||
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import { crdURL, crdRoute } from "../../../common/routes";
|
||||
import { isActiveRoute } from "../../navigation";
|
||||
import { Icon } from "../icon";
|
||||
import { SidebarItem } from "../layout/sidebar-item";
|
||||
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
|
||||
import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api";
|
||||
import { crdStore } from "./crd.store";
|
||||
import { Spinner } from "../spinner";
|
||||
|
||||
export interface CustomResourcesSidebarItemProps {}
|
||||
|
||||
interface Dependencies {
|
||||
routes: IComputedValue<CustomResourceGroupTabLayoutRoute[]>;
|
||||
isAllowedResource: IsAllowedResource;
|
||||
subscribeStores: SubscribeStores;
|
||||
}
|
||||
|
||||
const NonInjectedCustomResourcesSidebarItem = observer(({ routes, isAllowedResource, subscribeStores }: Dependencies & CustomResourcesSidebarItemProps) => {
|
||||
useEffect(() => subscribeStores([
|
||||
crdStore,
|
||||
]), []);
|
||||
|
||||
return (
|
||||
<SidebarItem
|
||||
id="custom-resources"
|
||||
text="Custom Resources"
|
||||
url={crdURL()}
|
||||
isActive={isActiveRoute(crdRoute)}
|
||||
isHidden={!isAllowedResource("customresourcedefinitions")}
|
||||
icon={<Icon material="extension"/>}
|
||||
>
|
||||
{routes.get().map((route) => (
|
||||
<SidebarItem
|
||||
key={route.id}
|
||||
id={route.id}
|
||||
text={route.title}
|
||||
url={route.routePath}
|
||||
>
|
||||
{route.subRoutes?.map((subRoute) => (
|
||||
<SidebarItem
|
||||
key={subRoute.id}
|
||||
id={subRoute.id}
|
||||
url={subRoute.routePath}
|
||||
text={subRoute.title}
|
||||
/>
|
||||
))}
|
||||
</SidebarItem>
|
||||
))}
|
||||
{crdStore.isLoading && (
|
||||
<div className="flex justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
</SidebarItem>
|
||||
);
|
||||
});
|
||||
|
||||
export const CustomResourcesSidebarItem = withInjectables<Dependencies, CustomResourcesSidebarItemProps>(NonInjectedCustomResourcesSidebarItem, {
|
||||
getProps: (di, props) => ({
|
||||
routes: di.inject(customResourcesRouteTabsInjectable),
|
||||
isAllowedResource: di.inject(isAllowedResourceInjectable),
|
||||
subscribeStores: di.inject(subscribeStoresInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
@ -124,7 +124,7 @@ export class Events extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { store, visibleItems } = this;
|
||||
const { store } = this;
|
||||
const { compact, compactLimit, className, ...layoutProps } = this.props;
|
||||
|
||||
const events = (
|
||||
@ -137,7 +137,7 @@ export class Events extends React.Component<Props> {
|
||||
renderHeaderTitle="Events"
|
||||
customizeHeader={this.customizeHeader}
|
||||
isSelectable={false}
|
||||
items={visibleItems}
|
||||
getItems={() => this.visibleItems}
|
||||
virtual={!compact}
|
||||
tableProps={{
|
||||
sortSyncWithUrl: false,
|
||||
|
||||
@ -3,5 +3,4 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export * from "./events";
|
||||
export * from "./event-details";
|
||||
|
||||
37
src/renderer/components/+events/sidebar-item.tsx
Normal file
37
src/renderer/components/+events/sidebar-item.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import { eventRoute, eventsURL } from "../../../common/routes";
|
||||
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import { isActiveRoute } from "../../navigation";
|
||||
import { Icon } from "../icon";
|
||||
import { SidebarItem } from "../layout/sidebar-item";
|
||||
|
||||
export interface EventsSidebarItemProps {}
|
||||
|
||||
interface Dependencies {
|
||||
isAllowedResource: IsAllowedResource;
|
||||
}
|
||||
|
||||
const NonInjectedEventsSidebarItem = observer(({ isAllowedResource }: Dependencies & EventsSidebarItemProps) => (
|
||||
<SidebarItem
|
||||
id="events"
|
||||
text="Events"
|
||||
isActive={isActiveRoute(eventRoute)}
|
||||
isHidden={!isAllowedResource("events")}
|
||||
url={eventsURL()}
|
||||
icon={<Icon material="access_time"/>}
|
||||
/>
|
||||
));
|
||||
|
||||
export const EventsSidebarItem = withInjectables<Dependencies, EventsSidebarItemProps>(NonInjectedEventsSidebarItem, {
|
||||
getProps: (di, props) => ({
|
||||
isAllowedResource: di.inject(isAllowedResourceInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
@ -12,7 +12,7 @@ import { Button } from "../../button";
|
||||
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
|
||||
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||
import React from "react";
|
||||
import fse from "fs-extra";
|
||||
import { remove as removeDir } from "fs-extra";
|
||||
import { shell } from "electron";
|
||||
import type { InstallRequestValidated } from "./create-temp-files-and-validate/create-temp-files-and-validate";
|
||||
import type { InstallRequest } from "./install-request";
|
||||
@ -80,17 +80,12 @@ export const attemptInstall =
|
||||
}
|
||||
|
||||
const extensionFolder = getExtensionDestFolder(name);
|
||||
const folderExists = await fse.pathExists(extensionFolder);
|
||||
const installedExtension = extensionLoader.getExtension(validatedRequest.id);
|
||||
|
||||
if (!folderExists) {
|
||||
// install extension if not yet exists
|
||||
await unpackExtension(validatedRequest, dispose);
|
||||
} else {
|
||||
const {
|
||||
manifest: { version: oldVersion },
|
||||
} = extensionLoader.getExtension(validatedRequest.id);
|
||||
if (installedExtension) {
|
||||
const { version: oldVersion } = installedExtension.manifest;
|
||||
|
||||
// otherwise confirmation required (re-install / update)
|
||||
// confirm to uninstall old version before installing new version
|
||||
const removeNotification = Notifications.info(
|
||||
<div className="InstallingExtensionNotification flex gaps align-center">
|
||||
<div className="flex column gaps">
|
||||
@ -130,5 +125,11 @@ export const attemptInstall =
|
||||
onClose: dispose,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// clean up old data if still around
|
||||
await removeDir(extensionFolder);
|
||||
|
||||
// install extension if not yet exists
|
||||
await unpackExtension(validatedRequest, dispose);
|
||||
}
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
@ -10,7 +10,7 @@ import releaseInjectable from "./release.injectable";
|
||||
const releaseDetailsInjectable = getInjectable({
|
||||
instantiate: (di) =>
|
||||
asyncComputed(async () => {
|
||||
const release = di.inject(releaseInjectable).value.get();
|
||||
const release = di.inject(releaseInjectable).get();
|
||||
|
||||
return await getRelease(release.name, release.namespace);
|
||||
}),
|
||||
@ -7,7 +7,7 @@ import "./release-details.scss";
|
||||
|
||||
import React, { Component } from "react";
|
||||
import groupBy from "lodash/groupBy";
|
||||
import { computed, makeObservable, observable } from "mobx";
|
||||
import { computed, IComputedValue, makeObservable, observable } from "mobx";
|
||||
import { Link } from "react-router-dom";
|
||||
import kebabCase from "lodash/kebabCase";
|
||||
import type { HelmRelease, IReleaseDetails, IReleaseUpdateDetails, IReleaseUpdatePayload } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
@ -39,7 +39,7 @@ interface Props {
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
release: IAsyncComputed<HelmRelease>
|
||||
release: IComputedValue<HelmRelease>
|
||||
releaseDetails: IAsyncComputed<IReleaseDetails>
|
||||
releaseValues: IAsyncComputed<string>
|
||||
updateRelease: (name: string, namespace: string, payload: IReleaseUpdatePayload) => Promise<IReleaseUpdateDetails>
|
||||
@ -59,7 +59,7 @@ class NonInjectedReleaseDetails extends Component<Props & Dependencies> {
|
||||
}
|
||||
|
||||
@computed get release() {
|
||||
return this.props.release.value.get();
|
||||
return this.props.release.get();
|
||||
}
|
||||
|
||||
@computed get details() {
|
||||
@ -12,7 +12,13 @@ import userSuppliedValuesAreShownInjectable from "./user-supplied-values-are-sho
|
||||
const releaseValuesInjectable = getInjectable({
|
||||
instantiate: (di) =>
|
||||
asyncComputed(async () => {
|
||||
const release = di.inject(releaseInjectable).value.get();
|
||||
const release = di.inject(releaseInjectable).get();
|
||||
|
||||
// TODO: Figure out way to get rid of defensive code
|
||||
if (!release) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const userSuppliedValuesAreShown = di.inject(userSuppliedValuesAreShownInjectable).value;
|
||||
|
||||
try {
|
||||
@ -6,14 +6,14 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { matches } from "lodash/fp";
|
||||
import releasesInjectable from "../releases.injectable";
|
||||
import releaseRouteParametersInjectable from "./release-route-parameters.injectable";
|
||||
import { asyncComputed } from "@ogre-tools/injectable-react";
|
||||
import { computed } from "mobx";
|
||||
|
||||
const releaseInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const releases = di.inject(releasesInjectable);
|
||||
const releaseRouteParameters = di.inject(releaseRouteParametersInjectable);
|
||||
|
||||
return asyncComputed(async () => {
|
||||
return computed(() => {
|
||||
const { name, namespace } = releaseRouteParameters.get();
|
||||
|
||||
if (!name || !namespace) {
|
||||
@ -109,11 +109,23 @@ class NonInjectedHelmReleases extends Component<Dependencies & Props> {
|
||||
item.toggle();
|
||||
},
|
||||
|
||||
isSelectedAll: () =>
|
||||
releases.get().every((release) => release.isSelected),
|
||||
isSelectedAll: (visibleItems: RemovableHelmRelease[]) => (
|
||||
visibleItems.length > 0
|
||||
&& visibleItems.every((release) => release.isSelected)
|
||||
),
|
||||
|
||||
toggleSelectionAll: () => {
|
||||
releases.get().forEach((release) => release.toggle());
|
||||
toggleSelectionAll: (visibleItems: RemovableHelmRelease[]) => {
|
||||
let selected = false;
|
||||
|
||||
if (!legacyReleaseStore.isSelectedAll(visibleItems)) {
|
||||
selected = true;
|
||||
}
|
||||
|
||||
visibleItems.forEach((release) => {
|
||||
if (release.isSelected !== selected) {
|
||||
release.toggle();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
isSelected: (item) => item.isSelected,
|
||||
@ -122,10 +134,12 @@ class NonInjectedHelmReleases extends Component<Dependencies & Props> {
|
||||
return releases.get().filter((release) => release.isSelected);
|
||||
},
|
||||
|
||||
removeSelectedItems() {
|
||||
return Promise.all(
|
||||
releases.get().filter((release) => release.isSelected).map((release) => release.delete()),
|
||||
);
|
||||
pickOnlySelected: (releases) => {
|
||||
return releases.filter(release => release.isSelected);
|
||||
},
|
||||
|
||||
removeItems: async (releases) => {
|
||||
await Promise.all(releases.map(release => release.delete()));
|
||||
},
|
||||
} as ItemStore<RemovableHelmRelease>;
|
||||
|
||||
@ -197,7 +211,7 @@ class NonInjectedHelmReleases extends Component<Dependencies & Props> {
|
||||
})}
|
||||
onDetails={this.onDetails}
|
||||
/>
|
||||
|
||||
|
||||
<ReleaseDetails
|
||||
hideDetails={this.hideDetails}
|
||||
/>
|
||||
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import {
|
||||
IReleaseUpdatePayload,
|
||||
updateRelease,
|
||||
} from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import releasesInjectable from "../releases.injectable";
|
||||
|
||||
const updateReleaseInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const releases = di.inject(releasesInjectable);
|
||||
|
||||
return async (
|
||||
name: string,
|
||||
namespace: string,
|
||||
payload: IReleaseUpdatePayload,
|
||||
) => {
|
||||
const result = await updateRelease(name, namespace, payload);
|
||||
|
||||
releases.invalidate();
|
||||
|
||||
return result;
|
||||
};
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default updateReleaseInjectable;
|
||||
34
src/renderer/components/+helm/route-tabs.injectable.ts
Normal file
34
src/renderer/components/+helm/route-tabs.injectable.ts
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import type { TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { HelmCharts } from "../+helm-charts";
|
||||
import { HelmReleases } from "../+helm-releases";
|
||||
import { helmChartsURL, helmChartsRoute, releaseURL, releaseRoute } from "../../../common/routes";
|
||||
|
||||
function getRouteTabs() {
|
||||
return computed((): TabLayoutRoute[] => [
|
||||
{
|
||||
title: "Charts",
|
||||
component: HelmCharts,
|
||||
url: helmChartsURL(),
|
||||
routePath: helmChartsRoute.path.toString(),
|
||||
},
|
||||
{
|
||||
title: "Releases",
|
||||
component: HelmReleases,
|
||||
url: releaseURL(),
|
||||
routePath: releaseRoute.path.toString(),
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
const helmRoutesInjectable = getInjectable({
|
||||
instantiate: () => getRouteTabs(),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default helmRoutesInjectable;
|
||||
31
src/renderer/components/+helm/route.tsx
Normal file
31
src/renderer/components/+helm/route.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import helmRoutesInjectable from "./route-tabs.injectable";
|
||||
|
||||
export interface HelmRouteProps {}
|
||||
|
||||
interface Dependencies {
|
||||
routes: IComputedValue<TabLayoutRoute[]>;
|
||||
}
|
||||
|
||||
const NonInjectedHelmRoute = observer(({ routes }: Dependencies & HelmRouteProps) => (
|
||||
<TabLayout
|
||||
className="Apps"
|
||||
tabs={routes.get()}
|
||||
/>
|
||||
));
|
||||
|
||||
export const HelmRoute = withInjectables<Dependencies, HelmRouteProps>(NonInjectedHelmRoute, {
|
||||
getProps: (di, props) => ({
|
||||
routes: di.inject(helmRoutesInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
44
src/renderer/components/+helm/sidebar-item.tsx
Normal file
44
src/renderer/components/+helm/sidebar-item.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import React from "react";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { isActiveRoute } from "../../navigation";
|
||||
import { Icon } from "../icon";
|
||||
import { SidebarItem } from "../layout/sidebar-item";
|
||||
import type { TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items";
|
||||
import { helmRoute, helmURL } from "../../../common/routes";
|
||||
import networkRouteTabsInjectable from "./route-tabs.injectable";
|
||||
|
||||
export interface HelmSidebarItemProps {}
|
||||
|
||||
interface Dependencies {
|
||||
routes: IComputedValue<TabLayoutRoute[]>;
|
||||
}
|
||||
|
||||
const NonInjectedHelmSidebarItem = observer(({ routes }: Dependencies & HelmSidebarItemProps) => {
|
||||
const tabRoutes = routes.get();
|
||||
|
||||
return (
|
||||
<SidebarItem
|
||||
id="helm"
|
||||
text="Helm"
|
||||
isActive={isActiveRoute(helmRoute)}
|
||||
url={helmURL()}
|
||||
icon={<Icon material="apps"/>}
|
||||
>
|
||||
<TabRoutesSidebarItems routes={tabRoutes} />
|
||||
</SidebarItem>
|
||||
);
|
||||
});
|
||||
|
||||
export const HelmSidebarItem = withInjectables<Dependencies, HelmSidebarItemProps>(NonInjectedHelmSidebarItem, {
|
||||
getProps: (di, props) => ({
|
||||
routes: di.inject(networkRouteTabsInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
@ -3,6 +3,5 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export * from "./namespaces";
|
||||
export * from "./namespace-details";
|
||||
export * from "./add-namespace-dialog";
|
||||
|
||||
@ -1,96 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./namespaces.scss";
|
||||
|
||||
import React from "react";
|
||||
import { NamespaceStatus } from "../../../common/k8s-api/endpoints";
|
||||
import { AddNamespaceDialog } from "./add-namespace-dialog";
|
||||
import { TabLayout } from "../layout/tab-layout";
|
||||
import { Badge } from "../badge";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import type { NamespaceStore } from "./namespace-store/namespace.store";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { NamespacesRouteParams } from "../../../common/routes";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
|
||||
import addNamespaceDialogModelInjectable
|
||||
from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
labels = "labels",
|
||||
age = "age",
|
||||
status = "status",
|
||||
}
|
||||
|
||||
interface Props extends RouteComponentProps<NamespacesRouteParams> {
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
namespaceStore: NamespaceStore
|
||||
openAddNamespaceDialog: () => void
|
||||
}
|
||||
|
||||
class NonInjectedNamespaces extends React.Component<Props & Dependencies> {
|
||||
render() {
|
||||
return (
|
||||
<TabLayout>
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
tableId="namespaces"
|
||||
className="Namespaces"
|
||||
store={this.props.namespaceStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: ns => ns.getName(),
|
||||
[columnId.labels]: ns => ns.getLabels(),
|
||||
[columnId.age]: ns => ns.getTimeDiffFromNow(),
|
||||
[columnId.status]: ns => ns.getStatus(),
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getSearchFields(),
|
||||
item => item.getStatus(),
|
||||
]}
|
||||
renderHeaderTitle="Namespaces"
|
||||
renderTableHeader={[
|
||||
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
|
||||
{ className: "warning", showWithColumn: columnId.name },
|
||||
{ title: "Labels", className: "labels scrollable", sortBy: columnId.labels, id: columnId.labels },
|
||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
|
||||
]}
|
||||
renderTableContents={item => [
|
||||
item.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={item} />,
|
||||
item.getLabels().map(label => <Badge scrollable key={label} label={label}/>),
|
||||
item.getAge(),
|
||||
{ title: item.getStatus(), className: item.getStatus().toLowerCase() },
|
||||
]}
|
||||
addRemoveButtons={{
|
||||
addTooltip: "Add Namespace",
|
||||
onAdd: () => this.props.openAddNamespaceDialog(),
|
||||
}}
|
||||
customizeTableRowProps={item => ({
|
||||
disabled: item.getStatus() === NamespaceStatus.TERMINATING,
|
||||
})}
|
||||
/>
|
||||
<AddNamespaceDialog/>
|
||||
</TabLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const Namespaces = withInjectables<Dependencies, Props>(
|
||||
NonInjectedNamespaces,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
namespaceStore: di.inject(namespaceStoreInjectable),
|
||||
openAddNamespaceDialog: di.inject(addNamespaceDialogModelInjectable).open,
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
89
src/renderer/components/+namespaces/route.tsx
Normal file
89
src/renderer/components/+namespaces/route.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./namespaces.scss";
|
||||
|
||||
import React from "react";
|
||||
import { NamespaceStatus } from "../../../common/k8s-api/endpoints";
|
||||
import { AddNamespaceDialog } from "./add-namespace-dialog";
|
||||
import { TabLayout } from "../layout/tab-layout";
|
||||
import { Badge } from "../badge";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import type { NamespaceStore } from "./namespace-store/namespace.store";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { NamespacesRouteParams } from "../../../common/routes";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
|
||||
import addNamespaceDialogModelInjectable
|
||||
from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
labels = "labels",
|
||||
age = "age",
|
||||
status = "status",
|
||||
}
|
||||
|
||||
export interface NamespacesRouteProps extends RouteComponentProps<NamespacesRouteParams> {
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
namespaceStore: NamespaceStore
|
||||
openAddNamespaceDialog: () => void
|
||||
}
|
||||
|
||||
export const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDialog }: Dependencies & NamespacesRouteProps) => (
|
||||
<TabLayout>
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
tableId="namespaces"
|
||||
className="Namespaces"
|
||||
store={namespaceStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: ns => ns.getName(),
|
||||
[columnId.labels]: ns => ns.getLabels(),
|
||||
[columnId.age]: ns => ns.getTimeDiffFromNow(),
|
||||
[columnId.status]: ns => ns.getStatus(),
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getSearchFields(),
|
||||
item => item.getStatus(),
|
||||
]}
|
||||
renderHeaderTitle="Namespaces"
|
||||
renderTableHeader={[
|
||||
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
|
||||
{ className: "warning", showWithColumn: columnId.name },
|
||||
{ title: "Labels", className: "labels scrollable", sortBy: columnId.labels, id: columnId.labels },
|
||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
|
||||
]}
|
||||
renderTableContents={item => [
|
||||
item.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={item} />,
|
||||
item.getLabels().map(label => <Badge scrollable key={label} label={label}/>),
|
||||
item.getAge(),
|
||||
{ title: item.getStatus(), className: item.getStatus().toLowerCase() },
|
||||
]}
|
||||
addRemoveButtons={{
|
||||
addTooltip: "Add Namespace",
|
||||
onAdd: openAddNamespaceDialog,
|
||||
}}
|
||||
customizeTableRowProps={item => ({
|
||||
disabled: item.getStatus() === NamespaceStatus.TERMINATING,
|
||||
})}
|
||||
/>
|
||||
<AddNamespaceDialog/>
|
||||
</TabLayout>
|
||||
);
|
||||
|
||||
|
||||
export const NamespacesRoute = withInjectables<Dependencies, NamespacesRouteProps>(NonInjectedNamespacesRoute, {
|
||||
getProps: (di, props) => ({
|
||||
namespaceStore: di.inject(namespaceStoreInjectable),
|
||||
openAddNamespaceDialog: di.inject(addNamespaceDialogModelInjectable).open,
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
37
src/renderer/components/+namespaces/sidebar-item.tsx
Normal file
37
src/renderer/components/+namespaces/sidebar-item.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import { namespacesRoute, namespacesURL } from "../../../common/routes";
|
||||
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import { isActiveRoute } from "../../navigation";
|
||||
import { Icon } from "../icon";
|
||||
import { SidebarItem } from "../layout/sidebar-item";
|
||||
|
||||
export interface NamespacesSidebarItemProps {}
|
||||
|
||||
interface Dependencies {
|
||||
isAllowedResource: IsAllowedResource;
|
||||
}
|
||||
|
||||
const NonInjectedNamespacesSidebarItem = observer(({ isAllowedResource }: Dependencies & NamespacesSidebarItemProps) => (
|
||||
<SidebarItem
|
||||
id="namespaces"
|
||||
text="Namespaces"
|
||||
isActive={isActiveRoute(namespacesRoute)}
|
||||
isHidden={!isAllowedResource("namespaces")}
|
||||
url={namespacesURL()}
|
||||
icon={<Icon material="layers"/>}
|
||||
/>
|
||||
));
|
||||
|
||||
export const NamespacesSidebarItem = withInjectables<Dependencies, NamespacesSidebarItemProps>(NonInjectedNamespacesSidebarItem, {
|
||||
getProps: (di, props) => ({
|
||||
isAllowedResource: di.inject(isAllowedResourceInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export * from "./network";
|
||||
@ -2,23 +2,24 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./network.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import type { TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { Services } from "../+network-services";
|
||||
import { Endpoints } from "../+network-endpoints";
|
||||
import { Ingresses } from "../+network-ingresses";
|
||||
import { NetworkPolicies } from "../+network-policies";
|
||||
import { PortForwards } from "../+network-port-forwards";
|
||||
import { isAllowedResource } from "../../../common/utils/allowed-resource";
|
||||
import * as routes from "../../../common/routes";
|
||||
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
|
||||
|
||||
@observer
|
||||
export class Network extends React.Component {
|
||||
static get tabRoutes(): TabLayoutRoute[] {
|
||||
interface Dependencies {
|
||||
isAllowedResource: IsAllowedResource;
|
||||
}
|
||||
|
||||
function getRouteTabs({ isAllowedResource }: Dependencies) {
|
||||
return computed(() => {
|
||||
const tabs: TabLayoutRoute[] = [];
|
||||
|
||||
if (isAllowedResource("services")) {
|
||||
@ -65,11 +66,14 @@ export class Network extends React.Component {
|
||||
});
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TabLayout className="Network" tabs={Network.tabRoutes}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const networkRouteTabsInjectable = getInjectable({
|
||||
instantiate: (di) => getRouteTabs({
|
||||
isAllowedResource: di.inject(isAllowedResourceInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default networkRouteTabsInjectable;
|
||||
33
src/renderer/components/+network/route.tsx
Normal file
33
src/renderer/components/+network/route.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./network.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import networkRouteTabsInjectable from "./route-tabs.injectable";
|
||||
|
||||
export interface NetworksRouteProps {}
|
||||
|
||||
interface Dependencies {
|
||||
routes: IComputedValue<TabLayoutRoute[]>;
|
||||
}
|
||||
|
||||
const NonInjectedNetworksRoute = observer(({ routes }: Dependencies & NetworksRouteProps) => (
|
||||
<TabLayout
|
||||
className="Network"
|
||||
tabs={routes.get()}
|
||||
/>
|
||||
));
|
||||
|
||||
export const NetworkRoute = withInjectables<Dependencies, NetworksRouteProps>(NonInjectedNetworksRoute, {
|
||||
getProps: (di, props) => ({
|
||||
routes: di.inject(networkRouteTabsInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
45
src/renderer/components/+network/sidebar-item.tsx
Normal file
45
src/renderer/components/+network/sidebar-item.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import React from "react";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { isActiveRoute } from "../../navigation";
|
||||
import { Icon } from "../icon";
|
||||
import { SidebarItem } from "../layout/sidebar-item";
|
||||
import type { TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items";
|
||||
import { networkRoute, networkURL } from "../../../common/routes";
|
||||
import networkRouteTabsInjectable from "./route-tabs.injectable";
|
||||
|
||||
export interface NetworkSidebarItemProps {}
|
||||
|
||||
interface Dependencies {
|
||||
routes: IComputedValue<TabLayoutRoute[]>;
|
||||
}
|
||||
|
||||
const NonInjectedNetworkSidebarItem = observer(({ routes }: Dependencies & NetworkSidebarItemProps) => {
|
||||
const tabRoutes = routes.get();
|
||||
|
||||
return (
|
||||
<SidebarItem
|
||||
id="networks"
|
||||
text="Network"
|
||||
isActive={isActiveRoute(networkRoute)}
|
||||
isHidden={tabRoutes.length == 0}
|
||||
url={networkURL()}
|
||||
icon={<Icon material="device_hub"/>}
|
||||
>
|
||||
<TabRoutesSidebarItems routes={tabRoutes} />
|
||||
</SidebarItem>
|
||||
);
|
||||
});
|
||||
|
||||
export const NetworkSidebarItem = withInjectables<Dependencies, NetworkSidebarItemProps>(NonInjectedNetworkSidebarItem, {
|
||||
getProps: (di, props) => ({
|
||||
routes: di.inject(networkRouteTabsInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./node-details-resources.scss";
|
||||
import "./details-resources.scss";
|
||||
|
||||
import { Table } from "../table/table";
|
||||
import { TableHead } from "../table/table-head";
|
||||
@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./node-details.scss";
|
||||
import "./details.scss";
|
||||
|
||||
import React from "react";
|
||||
import upperFirst from "lodash/upperFirst";
|
||||
@ -21,7 +21,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
||||
import { KubeObjectMeta } from "../kube-object-meta";
|
||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||
import { NodeDetailsResources } from "./node-details-resources";
|
||||
import { NodeDetailsResources } from "./details-resources";
|
||||
import { DrawerTitle } from "../drawer/drawer-title";
|
||||
import { boundMethod, Disposer } from "../../utils";
|
||||
import logger from "../../../common/logger";
|
||||
@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export * from "./nodes";
|
||||
export * from "./node-details";
|
||||
@ -50,7 +50,7 @@ interface UsageArgs {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Nodes extends React.Component<Props> {
|
||||
export class NodesRoute extends React.Component<Props> {
|
||||
@observable.ref metrics: Partial<INodeMetrics> = {};
|
||||
private metricsWatcher = interval(30, async () => this.metrics = await getMetricsForAllNodes());
|
||||
|
||||
37
src/renderer/components/+nodes/sidebar-item.tsx
Normal file
37
src/renderer/components/+nodes/sidebar-item.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import { nodesRoute, nodesURL } from "../../../common/routes";
|
||||
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import { isActiveRoute } from "../../navigation";
|
||||
import { Icon } from "../icon";
|
||||
import { SidebarItem } from "../layout/sidebar-item";
|
||||
|
||||
export interface NodeSidebarItemProps {}
|
||||
|
||||
interface Dependencies {
|
||||
isAllowedResource: IsAllowedResource;
|
||||
}
|
||||
|
||||
const NonInjectedNodeSidebarItem = observer(({ isAllowedResource }: Dependencies & NodeSidebarItemProps) => (
|
||||
<SidebarItem
|
||||
id="nodes"
|
||||
text="Nodes"
|
||||
isActive={isActiveRoute(nodesRoute)}
|
||||
isHidden={!isAllowedResource("nodes")}
|
||||
url={nodesURL()}
|
||||
icon={<Icon svg="nodes"/>}
|
||||
/>
|
||||
));
|
||||
|
||||
export const NodesSidebarItem = withInjectables<Dependencies, NodeSidebarItemProps>(NonInjectedNodeSidebarItem, {
|
||||
getProps: (di, props) => ({
|
||||
isAllowedResource: di.inject(isAllowedResourceInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Input, InputValidators } from "../input";
|
||||
import { SubTitle } from "../layout/sub-title";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
@ -19,7 +20,7 @@ interface Dependencies {
|
||||
defaultPathForKubectlBinaries: string
|
||||
}
|
||||
|
||||
const NonInjectedKubectlBinaries: React.FC<Dependencies> = (({ defaultPathForKubectlBinaries }) => {
|
||||
const NonInjectedKubectlBinaries: React.FC<Dependencies> = observer(({ defaultPathForKubectlBinaries }) => {
|
||||
const userStore = UserStore.getInstance();
|
||||
const [downloadPath, setDownloadPath] = useState(userStore.downloadBinariesPath || "");
|
||||
const [binariesPath, setBinariesPath] = useState(userStore.kubectlBinariesPath || "");
|
||||
|
||||
@ -24,7 +24,9 @@ export const Terminal = observer(() => {
|
||||
: "System default shell"
|
||||
);
|
||||
|
||||
return (<div>
|
||||
return (<section>
|
||||
<h2>Terminal</h2>
|
||||
|
||||
<section id="shell">
|
||||
<SubTitle title="Terminal Shell Path"/>
|
||||
<Input
|
||||
@ -78,5 +80,5 @@ export const Terminal = observer(() => {
|
||||
onChange={(value) => userStore.terminalConfig.fontFamily=value}
|
||||
/>
|
||||
</section>
|
||||
</div>);
|
||||
</section>);
|
||||
});
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export * from "./storage";
|
||||
@ -2,21 +2,22 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./storage.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import type { TabLayoutRoute } from "../layout/tab-layout";
|
||||
import { PersistentVolumes } from "../+storage-volumes";
|
||||
import { StorageClasses } from "../+storage-classes";
|
||||
import { PersistentVolumeClaims } from "../+storage-volume-claims";
|
||||
import { isAllowedResource } from "../../../common/utils/allowed-resource";
|
||||
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
|
||||
import * as routes from "../../../common/routes";
|
||||
|
||||
@observer
|
||||
export class Storage extends React.Component {
|
||||
static get tabRoutes() {
|
||||
interface Dependencies {
|
||||
isAllowedResource: IsAllowedResource;
|
||||
}
|
||||
|
||||
function getRouteTabs({ isAllowedResource }: Dependencies) {
|
||||
return computed(() => {
|
||||
const tabs: TabLayoutRoute[] = [];
|
||||
|
||||
if (isAllowedResource("persistentvolumeclaims")) {
|
||||
@ -47,11 +48,14 @@ export class Storage extends React.Component {
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TabLayout className="Storage" tabs={Storage.tabRoutes}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const storageRouteTabsInjectable = getInjectable({
|
||||
instantiate: (di) => getRouteTabs({
|
||||
isAllowedResource: di.inject(isAllowedResourceInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default storageRouteTabsInjectable;
|
||||
34
src/renderer/components/+storage/route.tsx
Normal file
34
src/renderer/components/+storage/route.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./storage.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import storageRouteTabsInjectable from "./route-tabs.injectable";
|
||||
|
||||
export interface StorageRouteProps {}
|
||||
|
||||
interface Dependencies {
|
||||
routes: IComputedValue<TabLayoutRoute[]>;
|
||||
}
|
||||
|
||||
const NonInjectedStorageRoute = observer(({ routes }: Dependencies & StorageRouteProps) => (
|
||||
<TabLayout
|
||||
className="Storage"
|
||||
tabs={routes.get()}
|
||||
/>
|
||||
));
|
||||
|
||||
export const StorageRoute = withInjectables<Dependencies, StorageRouteProps>(NonInjectedStorageRoute, {
|
||||
getProps: (di, props) => ({
|
||||
routes: di.inject(storageRouteTabsInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user