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
|
||||||
7
.github/workflows/main.yml
vendored
7
.github/workflows/main.yml
vendored
@ -6,6 +6,9 @@ on:
|
|||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
|
concurrency:
|
||||||
|
group: publish-docs
|
||||||
|
cancel-in-progress: true
|
||||||
jobs:
|
jobs:
|
||||||
verify-docs:
|
verify-docs:
|
||||||
name: Verify docs
|
name: Verify docs
|
||||||
@ -17,7 +20,7 @@ jobs:
|
|||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: "3.x"
|
||||||
|
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@ -49,7 +52,7 @@ jobs:
|
|||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: "3.x"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@ -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.
|
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(" ")'
|
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
|
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"))
|
| 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. 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:
|
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 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)
|
- 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)
|
- 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: [
|
pages: [
|
||||||
{
|
{
|
||||||
name: "Charts",
|
name: "Charts",
|
||||||
href: "/apps/charts",
|
href: "/helm/charts",
|
||||||
expectedSelector: "div.HelmCharts input",
|
expectedSelector: "div.HelmCharts input",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Releases",
|
name: "Releases",
|
||||||
href: "/apps/releases",
|
href: "/helm/releases",
|
||||||
expectedSelector: "h5.title",
|
expectedSelector: "h5.title",
|
||||||
expectedText: "Releases",
|
expectedText: "Releases",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"productName": "OpenLens",
|
"productName": "OpenLens",
|
||||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"homepage": "https://github.com/lensapp/lens",
|
"homepage": "https://github.com/lensapp/lens",
|
||||||
"version": "5.3.0",
|
"version": "5.4.0-beta.1",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2021 OpenLens Authors",
|
"copyright": "© 2021 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -194,8 +194,8 @@
|
|||||||
"@hapi/call": "^8.0.1",
|
"@hapi/call": "^8.0.1",
|
||||||
"@hapi/subtext": "^7.0.3",
|
"@hapi/subtext": "^7.0.3",
|
||||||
"@kubernetes/client-node": "^0.16.1",
|
"@kubernetes/client-node": "^0.16.1",
|
||||||
"@ogre-tools/injectable": "3.2.0",
|
"@ogre-tools/injectable": "3.2.1",
|
||||||
"@ogre-tools/injectable-react": "3.2.0",
|
"@ogre-tools/injectable-react": "3.2.1",
|
||||||
"@sentry/electron": "^2.5.4",
|
"@sentry/electron": "^2.5.4",
|
||||||
"@sentry/integrations": "^6.15.0",
|
"@sentry/integrations": "^6.15.0",
|
||||||
"@types/circular-dependency-plugin": "5.0.4",
|
"@types/circular-dependency-plugin": "5.0.4",
|
||||||
@ -302,7 +302,7 @@
|
|||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.11",
|
||||||
"@types/react-router-dom": "^5.3.2",
|
"@types/react-router-dom": "^5.3.2",
|
||||||
"@types/react-select": "3.1.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-virtualized-auto-sizer": "^1.0.1",
|
||||||
"@types/react-window": "^1.8.5",
|
"@types/react-window": "^1.8.5",
|
||||||
"@types/readable-stream": "^2.3.12",
|
"@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.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { getHostedClusterId } from "../../utils";
|
import { getHostedClusterId } from "../utils";
|
||||||
import clusterStoreInjectable from "../cluster-store.injectable";
|
import clusterStoreInjectable from "./cluster-store.injectable";
|
||||||
|
|
||||||
const hostedClusterInjectable = getInjectable({
|
const hostedClusterInjectable = getInjectable({
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
@ -21,7 +21,7 @@ export abstract class ItemStore<Item extends ItemObject> {
|
|||||||
@observable isLoading = false;
|
@observable isLoading = false;
|
||||||
@observable isLoaded = false;
|
@observable isLoaded = false;
|
||||||
@observable items = observable.array<Item>([], { deep: false });
|
@observable items = observable.array<Item>([], { deep: false });
|
||||||
@observable selectedItemsIds = observable.map<string, boolean>();
|
@observable selectedItemsIds = observable.set<string>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
@ -29,7 +29,11 @@ export abstract class ItemStore<Item extends ItemObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@computed get selectedItems(): Item[] {
|
@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[] {
|
public getItems(): Item[] {
|
||||||
@ -152,12 +156,12 @@ export abstract class ItemStore<Item extends ItemObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isSelected(item: Item) {
|
isSelected(item: Item) {
|
||||||
return !!this.selectedItemsIds.get(item.getId());
|
return this.selectedItemsIds.has(item.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
select(item: Item) {
|
select(item: Item) {
|
||||||
this.selectedItemsIds.set(item.getId(), true);
|
this.selectedItemsIds.add(item.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -207,6 +211,8 @@ export abstract class ItemStore<Item extends ItemObject> {
|
|||||||
|
|
||||||
async removeSelectedItems?(): Promise<any>;
|
async removeSelectedItems?(): Promise<any>;
|
||||||
|
|
||||||
|
async removeItems?(items: Item[]): Promise<void>;
|
||||||
|
|
||||||
* [Symbol.iterator]() {
|
* [Symbol.iterator]() {
|
||||||
yield* this.items;
|
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 { formatDuration } from "../../utils";
|
||||||
import capitalize from "lodash/capitalize";
|
import capitalize from "lodash/capitalize";
|
||||||
import { apiBase } from "../index";
|
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 type { ItemObject } from "../../item.store";
|
||||||
import { KubeObject } from "../kube-object";
|
import { KubeObject } from "../kube-object";
|
||||||
import type { JsonApiData } from "../json-api";
|
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 {
|
export class Node extends KubeObject {
|
||||||
static kind = "Node";
|
static kind = "Node";
|
||||||
static namespaced = false;
|
static namespaced = false;
|
||||||
@ -165,17 +171,29 @@ export class Node extends KubeObject {
|
|||||||
return this.spec.taints || [];
|
return this.spec.taints || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
getRoleLabels() {
|
getRoleLabels(): string {
|
||||||
if (!this.metadata?.labels || typeof this.metadata.labels !== "object") {
|
const { labels } = this.metadata;
|
||||||
|
|
||||||
|
if (!labels || typeof labels !== "object") {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const roleLabels = Object.keys(this.metadata.labels)
|
const roleLabels: string[] = [];
|
||||||
.filter(key => key.includes("node-role.kubernetes.io"))
|
|
||||||
.map(key => key.match(/([^/]+$)/)[0]); // all after last slash
|
|
||||||
|
|
||||||
if (this.metadata.labels["kubernetes.io/role"] != undefined) {
|
for (const labelKey of Object.keys(labels)) {
|
||||||
roleLabels.push(this.metadata.labels["kubernetes.io/role"]);
|
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(", ");
|
return roleLabels.join(", ");
|
||||||
|
|||||||
@ -16,12 +16,9 @@ import { KubeObjectConstructor, KubeObject, KubeStatus } from "./kube-object";
|
|||||||
import byline from "byline";
|
import byline from "byline";
|
||||||
import type { IKubeWatchEvent } from "./kube-watch-event";
|
import type { IKubeWatchEvent } from "./kube-watch-event";
|
||||||
import { KubeJsonApi, KubeJsonApiData } from "./kube-json-api";
|
import { KubeJsonApi, KubeJsonApiData } from "./kube-json-api";
|
||||||
import { noop } from "../utils";
|
import { noop, WrappedAbortController } from "../utils";
|
||||||
import type { RequestInit } from "node-fetch";
|
import type { RequestInit } from "node-fetch";
|
||||||
|
import type AbortController from "abort-controller";
|
||||||
// BUG: https://github.com/mysticatea/abort-controller/pull/22
|
|
||||||
// eslint-disable-next-line import/no-named-as-default
|
|
||||||
import AbortController from "abort-controller";
|
|
||||||
import { Agent, AgentOptions } from "https";
|
import { Agent, AgentOptions } from "https";
|
||||||
import type { Patch } from "rfc6902";
|
import type { Patch } from "rfc6902";
|
||||||
|
|
||||||
@ -582,14 +579,7 @@ export class KubeApi<T extends KubeObject> {
|
|||||||
const { watchId = `${this.kind.toLowerCase()}-${this.watchId++}` } = opts;
|
const { watchId = `${this.kind.toLowerCase()}-${this.watchId++}` } = opts;
|
||||||
|
|
||||||
// Create AbortController for this request
|
// Create AbortController for this request
|
||||||
const abortController = new AbortController();
|
const abortController = new WrappedAbortController(opts.abortController);
|
||||||
|
|
||||||
// If caller aborts, abort using request's abortController
|
|
||||||
if (opts.abortController) {
|
|
||||||
opts.abortController.signal.addEventListener("abort", () => {
|
|
||||||
abortController.abort();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
abortController.signal.addEventListener("abort", () => {
|
abortController.signal.addEventListener("abort", () => {
|
||||||
logger.info(`[KUBE-API] watch (${watchId}) aborted ${watchUrl}`);
|
logger.info(`[KUBE-API] watch (${watchId}) aborted ${watchUrl}`);
|
||||||
@ -687,7 +677,6 @@ export class KubeApi<T extends KubeObject> {
|
|||||||
protected modifyWatchEvent(event: IKubeWatchEvent<KubeJsonApiData>) {
|
protected modifyWatchEvent(event: IKubeWatchEvent<KubeJsonApiData>) {
|
||||||
if (event.type === "ERROR") {
|
if (event.type === "ERROR") {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureObjectSelfLink(this, event.object);
|
ensureObjectSelfLink(this, event.object);
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
import type { ClusterContext } from "./cluster-context";
|
import type { ClusterContext } from "./cluster-context";
|
||||||
|
|
||||||
import { action, computed, makeObservable, observable, reaction, when } from "mobx";
|
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 { KubeObject, KubeStatus } from "./kube-object";
|
||||||
import type { IKubeWatchEvent } from "./kube-watch-event";
|
import type { IKubeWatchEvent } from "./kube-watch-event";
|
||||||
import { ItemStore } from "../item.store";
|
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));
|
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
|
// collect items from watch-api events to avoid UI blowing up with huge streams of data
|
||||||
protected eventsBuffer = observable.array<IKubeWatchEvent<KubeJsonApiData>>([], { deep: false });
|
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) {
|
if (this.api.isNamespaced) {
|
||||||
Promise.race([rejectPromiseBy(abortController.signal), Promise.all([this.contextReady, this.namespacesReady])])
|
Promise.race([rejectPromiseBy(abortController.signal), Promise.all([this.contextReady, this.namespacesReady])])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|||||||
@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
import type { RouteProps } from "react-router";
|
import type { RouteProps } from "react-router";
|
||||||
import { buildURL } from "../utils/buildUrl";
|
import { buildURL } from "../utils/buildUrl";
|
||||||
import { appsRoute } from "./apps";
|
import { helmRoute } from "./helm";
|
||||||
|
|
||||||
export const helmChartsRoute: RouteProps = {
|
export const helmChartsRoute: RouteProps = {
|
||||||
path: `${appsRoute.path}/charts/:repo?/:chartName?`,
|
path: `${helmRoute.path}/charts/:repo?/:chartName?`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface HelmChartsRouteParams {
|
export interface HelmChartsRouteParams {
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
import type { RouteProps } from "react-router";
|
import type { RouteProps } from "react-router";
|
||||||
import { buildURL } from "../utils/buildUrl";
|
import { buildURL } from "../utils/buildUrl";
|
||||||
|
|
||||||
export const appsRoute: RouteProps = {
|
export const helmRoute: RouteProps = {
|
||||||
path: "/apps",
|
path: "/helm",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const appsURL = buildURL(appsRoute.path);
|
export const helmURL = buildURL(helmRoute.path);
|
||||||
@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./add-cluster";
|
export * from "./add-cluster";
|
||||||
export * from "./apps";
|
|
||||||
export * from "./catalog";
|
export * from "./catalog";
|
||||||
export * from "./cluster-view";
|
export * from "./cluster-view";
|
||||||
export * from "./cluster";
|
export * from "./cluster";
|
||||||
@ -16,6 +15,7 @@ export * from "./entity-settings";
|
|||||||
export * from "./events";
|
export * from "./events";
|
||||||
export * from "./extensions";
|
export * from "./extensions";
|
||||||
export * from "./helm-charts";
|
export * from "./helm-charts";
|
||||||
|
export * from "./helm";
|
||||||
export * from "./hpa";
|
export * from "./hpa";
|
||||||
export * from "./ingresses";
|
export * from "./ingresses";
|
||||||
export * from "./limit-ranges";
|
export * from "./limit-ranges";
|
||||||
|
|||||||
@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
import type { RouteProps } from "react-router";
|
import type { RouteProps } from "react-router";
|
||||||
import { buildURL } from "../utils/buildUrl";
|
import { buildURL } from "../utils/buildUrl";
|
||||||
import { appsRoute } from "./apps";
|
import { helmRoute } from "./helm";
|
||||||
|
|
||||||
export const releaseRoute: RouteProps = {
|
export const releaseRoute: RouteProps = {
|
||||||
path: `${appsRoute.path}/releases/:namespace?/:name?`,
|
path: `${helmRoute.path}/releases/:namespace?/:name?`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ReleaseRouteParams {
|
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;
|
return void args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export * from "./abort-controller";
|
||||||
export * from "./app-version";
|
export * from "./app-version";
|
||||||
export * from "./autobind";
|
export * from "./autobind";
|
||||||
export * from "./camelCase";
|
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.
|
* 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 { ResourceStack } from "../../common/k8s/resource-stack";
|
||||||
export { apiManager } from "../../common/k8s-api/api-manager";
|
export { apiManager } from "../../common/k8s-api/api-manager";
|
||||||
export { KubeApi, forCluster, forRemoteCluster } from "../../common/k8s-api/kube-api";
|
export { KubeApi, forCluster, forRemoteCluster } from "../../common/k8s-api/kube-api";
|
||||||
|
|||||||
@ -2,8 +2,19 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* 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 { ResourceStack } from "../../common/k8s/resource-stack";
|
||||||
export { apiManager } from "../../common/k8s-api/api-manager";
|
export { apiManager } from "../../common/k8s-api/api-manager";
|
||||||
export { KubeObjectStore } from "../../common/k8s-api/kube-object.store";
|
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) {
|
export async function rollback(name: string, namespace: string, revision: number, kubeconfigPath: string) {
|
||||||
return JSON.parse(await execHelm([
|
await execHelm([
|
||||||
"rollback",
|
"rollback",
|
||||||
name,
|
name,
|
||||||
|
`${revision}`,
|
||||||
"--namespace", namespace,
|
"--namespace", namespace,
|
||||||
"--kubeconfig", kubeconfigPath,
|
"--kubeconfig", kubeconfigPath,
|
||||||
]));
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getResources(name: string, namespace: string, kubeconfigPath: string, kubectlPath: string) {
|
async function getResources(name: string, namespace: string, kubeconfigPath: string, kubectlPath: string) {
|
||||||
|
|||||||
@ -102,9 +102,7 @@ class HelmService {
|
|||||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||||
|
|
||||||
logger.debug("Rollback release");
|
logger.debug("Rollback release");
|
||||||
const output = rollback(releaseName, namespace, revision, proxyKubeconfig);
|
await rollback(releaseName, namespace, revision, proxyKubeconfig);
|
||||||
|
|
||||||
return { message: output };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -71,9 +71,9 @@ export class HelmApiRoute {
|
|||||||
const { cluster, params, payload, response } = request;
|
const { cluster, params, payload, response } = request;
|
||||||
|
|
||||||
try {
|
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) {
|
} catch (error) {
|
||||||
logger.debug(error);
|
logger.debug(error);
|
||||||
respondText(response, error?.toString() || "Error rolling back chart", 422);
|
respondText(response, error?.toString() || "Error rolling back chart", 422);
|
||||||
|
|||||||
@ -94,6 +94,7 @@ export class NodeShellSession extends ShellSession {
|
|||||||
tolerations: [{
|
tolerations: [{
|
||||||
operator: "Exists",
|
operator: "Exists",
|
||||||
}],
|
}],
|
||||||
|
priorityClassName: "system-node-critical",
|
||||||
containers: [{
|
containers: [{
|
||||||
name: "shell",
|
name: "shell",
|
||||||
image: this.cluster.nodeShellImage,
|
image: this.cluster.nodeShellImage,
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { ClusterFrameContext } from "./cluster-frame-context";
|
import { ClusterFrameContext } from "./cluster-frame-context";
|
||||||
import namespaceStoreInjectable from "../components/+namespaces/namespace-store/namespace-store.injectable";
|
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({
|
const clusterFrameContextInjectable = getInjectable({
|
||||||
instantiate: (di) => {
|
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 {
|
.pinIcon {
|
||||||
|
--color-active: var(--textColorAccent);
|
||||||
|
|
||||||
transition: none;
|
transition: none;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
margin-left: var(--padding);
|
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 {
|
.quota-entries {
|
||||||
margin: -$margin * 0.5;
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-left: -1px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
&:empty {
|
&:empty {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quota {
|
.quota {
|
||||||
--flex-gap: #{$padding};
|
|
||||||
border: 1px solid var(--halfGray);
|
border: 1px solid var(--halfGray);
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
margin: $margin * 0.5;
|
|
||||||
padding: $padding * 0.5 $padding;
|
padding: $padding * 0.5 $padding;
|
||||||
transition: all 150ms ease;
|
transition: all 150ms ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: inset 0 0 0 1px var(--borderColor);
|
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.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import React from "react";
|
import { computed } from "mobx";
|
||||||
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 { HorizontalPodAutoscalers } from "../+config-autoscalers";
|
import { HorizontalPodAutoscalers } from "../+config-autoscalers";
|
||||||
import { isAllowedResource } from "../../../common/utils/allowed-resource";
|
|
||||||
import { LimitRanges } from "../+config-limit-ranges";
|
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";
|
import * as routes from "../../../common/routes";
|
||||||
|
|
||||||
@observer
|
interface Dependencies {
|
||||||
export class Config extends React.Component {
|
isAllowedResource: IsAllowedResource;
|
||||||
static get tabRoutes(): TabLayoutRoute[] {
|
}
|
||||||
|
|
||||||
|
function getRouteTabs({ isAllowedResource }: Dependencies) {
|
||||||
|
return computed(() => {
|
||||||
const tabs: TabLayoutRoute[] = [];
|
const tabs: TabLayoutRoute[] = [];
|
||||||
|
|
||||||
if (isAllowedResource("configmaps")) {
|
if (isAllowedResource("configmaps")) {
|
||||||
@ -75,11 +77,14 @@ export class Config extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return tabs;
|
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() {
|
render() {
|
||||||
const { store, visibleItems } = this;
|
const { store } = this;
|
||||||
const { compact, compactLimit, className, ...layoutProps } = this.props;
|
const { compact, compactLimit, className, ...layoutProps } = this.props;
|
||||||
|
|
||||||
const events = (
|
const events = (
|
||||||
@ -137,7 +137,7 @@ export class Events extends React.Component<Props> {
|
|||||||
renderHeaderTitle="Events"
|
renderHeaderTitle="Events"
|
||||||
customizeHeader={this.customizeHeader}
|
customizeHeader={this.customizeHeader}
|
||||||
isSelectable={false}
|
isSelectable={false}
|
||||||
items={visibleItems}
|
getItems={() => this.visibleItems}
|
||||||
virtual={!compact}
|
virtual={!compact}
|
||||||
tableProps={{
|
tableProps={{
|
||||||
sortSyncWithUrl: false,
|
sortSyncWithUrl: false,
|
||||||
|
|||||||
@ -3,5 +3,4 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./events";
|
|
||||||
export * from "./event-details";
|
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 { ExtensionLoader } from "../../../../extensions/extension-loader";
|
||||||
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import fse from "fs-extra";
|
import { remove as removeDir } from "fs-extra";
|
||||||
import { shell } from "electron";
|
import { shell } from "electron";
|
||||||
import type { InstallRequestValidated } from "./create-temp-files-and-validate/create-temp-files-and-validate";
|
import type { InstallRequestValidated } from "./create-temp-files-and-validate/create-temp-files-and-validate";
|
||||||
import type { InstallRequest } from "./install-request";
|
import type { InstallRequest } from "./install-request";
|
||||||
@ -80,17 +80,12 @@ export const attemptInstall =
|
|||||||
}
|
}
|
||||||
|
|
||||||
const extensionFolder = getExtensionDestFolder(name);
|
const extensionFolder = getExtensionDestFolder(name);
|
||||||
const folderExists = await fse.pathExists(extensionFolder);
|
const installedExtension = extensionLoader.getExtension(validatedRequest.id);
|
||||||
|
|
||||||
if (!folderExists) {
|
if (installedExtension) {
|
||||||
// install extension if not yet exists
|
const { version: oldVersion } = installedExtension.manifest;
|
||||||
await unpackExtension(validatedRequest, dispose);
|
|
||||||
} else {
|
|
||||||
const {
|
|
||||||
manifest: { version: oldVersion },
|
|
||||||
} = extensionLoader.getExtension(validatedRequest.id);
|
|
||||||
|
|
||||||
// otherwise confirmation required (re-install / update)
|
// confirm to uninstall old version before installing new version
|
||||||
const removeNotification = Notifications.info(
|
const removeNotification = Notifications.info(
|
||||||
<div className="InstallingExtensionNotification flex gaps align-center">
|
<div className="InstallingExtensionNotification flex gaps align-center">
|
||||||
<div className="flex column gaps">
|
<div className="flex column gaps">
|
||||||
@ -130,5 +125,11 @@ export const attemptInstall =
|
|||||||
onClose: dispose,
|
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({
|
const releaseDetailsInjectable = getInjectable({
|
||||||
instantiate: (di) =>
|
instantiate: (di) =>
|
||||||
asyncComputed(async () => {
|
asyncComputed(async () => {
|
||||||
const release = di.inject(releaseInjectable).value.get();
|
const release = di.inject(releaseInjectable).get();
|
||||||
|
|
||||||
return await getRelease(release.name, release.namespace);
|
return await getRelease(release.name, release.namespace);
|
||||||
}),
|
}),
|
||||||
@ -7,7 +7,7 @@ import "./release-details.scss";
|
|||||||
|
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import groupBy from "lodash/groupBy";
|
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 { Link } from "react-router-dom";
|
||||||
import kebabCase from "lodash/kebabCase";
|
import kebabCase from "lodash/kebabCase";
|
||||||
import type { HelmRelease, IReleaseDetails, IReleaseUpdateDetails, IReleaseUpdatePayload } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
import type { HelmRelease, IReleaseDetails, IReleaseUpdateDetails, IReleaseUpdatePayload } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||||
@ -39,7 +39,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
release: IAsyncComputed<HelmRelease>
|
release: IComputedValue<HelmRelease>
|
||||||
releaseDetails: IAsyncComputed<IReleaseDetails>
|
releaseDetails: IAsyncComputed<IReleaseDetails>
|
||||||
releaseValues: IAsyncComputed<string>
|
releaseValues: IAsyncComputed<string>
|
||||||
updateRelease: (name: string, namespace: string, payload: IReleaseUpdatePayload) => Promise<IReleaseUpdateDetails>
|
updateRelease: (name: string, namespace: string, payload: IReleaseUpdatePayload) => Promise<IReleaseUpdateDetails>
|
||||||
@ -59,7 +59,7 @@ class NonInjectedReleaseDetails extends Component<Props & Dependencies> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@computed get release() {
|
@computed get release() {
|
||||||
return this.props.release.value.get();
|
return this.props.release.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get details() {
|
@computed get details() {
|
||||||
@ -12,7 +12,13 @@ import userSuppliedValuesAreShownInjectable from "./user-supplied-values-are-sho
|
|||||||
const releaseValuesInjectable = getInjectable({
|
const releaseValuesInjectable = getInjectable({
|
||||||
instantiate: (di) =>
|
instantiate: (di) =>
|
||||||
asyncComputed(async () => {
|
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;
|
const userSuppliedValuesAreShown = di.inject(userSuppliedValuesAreShownInjectable).value;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -6,14 +6,14 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|||||||
import { matches } from "lodash/fp";
|
import { matches } from "lodash/fp";
|
||||||
import releasesInjectable from "../releases.injectable";
|
import releasesInjectable from "../releases.injectable";
|
||||||
import releaseRouteParametersInjectable from "./release-route-parameters.injectable";
|
import releaseRouteParametersInjectable from "./release-route-parameters.injectable";
|
||||||
import { asyncComputed } from "@ogre-tools/injectable-react";
|
import { computed } from "mobx";
|
||||||
|
|
||||||
const releaseInjectable = getInjectable({
|
const releaseInjectable = getInjectable({
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const releases = di.inject(releasesInjectable);
|
const releases = di.inject(releasesInjectable);
|
||||||
const releaseRouteParameters = di.inject(releaseRouteParametersInjectable);
|
const releaseRouteParameters = di.inject(releaseRouteParametersInjectable);
|
||||||
|
|
||||||
return asyncComputed(async () => {
|
return computed(() => {
|
||||||
const { name, namespace } = releaseRouteParameters.get();
|
const { name, namespace } = releaseRouteParameters.get();
|
||||||
|
|
||||||
if (!name || !namespace) {
|
if (!name || !namespace) {
|
||||||
@ -109,11 +109,23 @@ class NonInjectedHelmReleases extends Component<Dependencies & Props> {
|
|||||||
item.toggle();
|
item.toggle();
|
||||||
},
|
},
|
||||||
|
|
||||||
isSelectedAll: () =>
|
isSelectedAll: (visibleItems: RemovableHelmRelease[]) => (
|
||||||
releases.get().every((release) => release.isSelected),
|
visibleItems.length > 0
|
||||||
|
&& visibleItems.every((release) => release.isSelected)
|
||||||
|
),
|
||||||
|
|
||||||
toggleSelectionAll: () => {
|
toggleSelectionAll: (visibleItems: RemovableHelmRelease[]) => {
|
||||||
releases.get().forEach((release) => release.toggle());
|
let selected = false;
|
||||||
|
|
||||||
|
if (!legacyReleaseStore.isSelectedAll(visibleItems)) {
|
||||||
|
selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
visibleItems.forEach((release) => {
|
||||||
|
if (release.isSelected !== selected) {
|
||||||
|
release.toggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
isSelected: (item) => item.isSelected,
|
isSelected: (item) => item.isSelected,
|
||||||
@ -122,10 +134,12 @@ class NonInjectedHelmReleases extends Component<Dependencies & Props> {
|
|||||||
return releases.get().filter((release) => release.isSelected);
|
return releases.get().filter((release) => release.isSelected);
|
||||||
},
|
},
|
||||||
|
|
||||||
removeSelectedItems() {
|
pickOnlySelected: (releases) => {
|
||||||
return Promise.all(
|
return releases.filter(release => release.isSelected);
|
||||||
releases.get().filter((release) => release.isSelected).map((release) => release.delete()),
|
},
|
||||||
);
|
|
||||||
|
removeItems: async (releases) => {
|
||||||
|
await Promise.all(releases.map(release => release.delete()));
|
||||||
},
|
},
|
||||||
} as ItemStore<RemovableHelmRelease>;
|
} as ItemStore<RemovableHelmRelease>;
|
||||||
|
|
||||||
@ -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.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./namespaces";
|
|
||||||
export * from "./namespace-details";
|
export * from "./namespace-details";
|
||||||
export * from "./add-namespace-dialog";
|
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.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import "./network.scss";
|
import { computed } from "mobx";
|
||||||
|
import type { TabLayoutRoute } from "../layout/tab-layout";
|
||||||
import React from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
|
||||||
import { Services } from "../+network-services";
|
import { Services } from "../+network-services";
|
||||||
import { Endpoints } from "../+network-endpoints";
|
import { Endpoints } from "../+network-endpoints";
|
||||||
import { Ingresses } from "../+network-ingresses";
|
import { Ingresses } from "../+network-ingresses";
|
||||||
import { NetworkPolicies } from "../+network-policies";
|
import { NetworkPolicies } from "../+network-policies";
|
||||||
import { PortForwards } from "../+network-port-forwards";
|
import { PortForwards } from "../+network-port-forwards";
|
||||||
import { isAllowedResource } from "../../../common/utils/allowed-resource";
|
|
||||||
import * as routes from "../../../common/routes";
|
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
|
interface Dependencies {
|
||||||
export class Network extends React.Component {
|
isAllowedResource: IsAllowedResource;
|
||||||
static get tabRoutes(): TabLayoutRoute[] {
|
}
|
||||||
|
|
||||||
|
function getRouteTabs({ isAllowedResource }: Dependencies) {
|
||||||
|
return computed(() => {
|
||||||
const tabs: TabLayoutRoute[] = [];
|
const tabs: TabLayoutRoute[] = [];
|
||||||
|
|
||||||
if (isAllowedResource("services")) {
|
if (isAllowedResource("services")) {
|
||||||
@ -65,11 +66,14 @@ export class Network extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return tabs;
|
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.
|
* 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 { Table } from "../table/table";
|
||||||
import { TableHead } from "../table/table-head";
|
import { TableHead } from "../table/table-head";
|
||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import "./node-details.scss";
|
import "./details.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import upperFirst from "lodash/upperFirst";
|
import upperFirst from "lodash/upperFirst";
|
||||||
@ -21,7 +21,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
|||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { NodeDetailsResources } from "./node-details-resources";
|
import { NodeDetailsResources } from "./details-resources";
|
||||||
import { DrawerTitle } from "../drawer/drawer-title";
|
import { DrawerTitle } from "../drawer/drawer-title";
|
||||||
import { boundMethod, Disposer } from "../../utils";
|
import { boundMethod, Disposer } from "../../utils";
|
||||||
import logger from "../../../common/logger";
|
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
|
@observer
|
||||||
export class Nodes extends React.Component<Props> {
|
export class NodesRoute extends React.Component<Props> {
|
||||||
@observable.ref metrics: Partial<INodeMetrics> = {};
|
@observable.ref metrics: Partial<INodeMetrics> = {};
|
||||||
private metricsWatcher = interval(30, async () => this.metrics = await getMetricsForAllNodes());
|
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 React, { useState } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
import { Input, InputValidators } from "../input";
|
import { Input, InputValidators } from "../input";
|
||||||
import { SubTitle } from "../layout/sub-title";
|
import { SubTitle } from "../layout/sub-title";
|
||||||
import { UserStore } from "../../../common/user-store";
|
import { UserStore } from "../../../common/user-store";
|
||||||
@ -19,7 +20,7 @@ interface Dependencies {
|
|||||||
defaultPathForKubectlBinaries: string
|
defaultPathForKubectlBinaries: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedKubectlBinaries: React.FC<Dependencies> = (({ defaultPathForKubectlBinaries }) => {
|
const NonInjectedKubectlBinaries: React.FC<Dependencies> = observer(({ defaultPathForKubectlBinaries }) => {
|
||||||
const userStore = UserStore.getInstance();
|
const userStore = UserStore.getInstance();
|
||||||
const [downloadPath, setDownloadPath] = useState(userStore.downloadBinariesPath || "");
|
const [downloadPath, setDownloadPath] = useState(userStore.downloadBinariesPath || "");
|
||||||
const [binariesPath, setBinariesPath] = useState(userStore.kubectlBinariesPath || "");
|
const [binariesPath, setBinariesPath] = useState(userStore.kubectlBinariesPath || "");
|
||||||
|
|||||||
@ -24,7 +24,9 @@ export const Terminal = observer(() => {
|
|||||||
: "System default shell"
|
: "System default shell"
|
||||||
);
|
);
|
||||||
|
|
||||||
return (<div>
|
return (<section>
|
||||||
|
<h2>Terminal</h2>
|
||||||
|
|
||||||
<section id="shell">
|
<section id="shell">
|
||||||
<SubTitle title="Terminal Shell Path"/>
|
<SubTitle title="Terminal Shell Path"/>
|
||||||
<Input
|
<Input
|
||||||
@ -78,5 +80,5 @@ export const Terminal = observer(() => {
|
|||||||
onChange={(value) => userStore.terminalConfig.fontFamily=value}
|
onChange={(value) => userStore.terminalConfig.fontFamily=value}
|
||||||
/>
|
/>
|
||||||
</section>
|
</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.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import "./storage.scss";
|
import { computed } from "mobx";
|
||||||
|
import type { TabLayoutRoute } from "../layout/tab-layout";
|
||||||
import React from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
|
|
||||||
import { PersistentVolumes } from "../+storage-volumes";
|
import { PersistentVolumes } from "../+storage-volumes";
|
||||||
import { StorageClasses } from "../+storage-classes";
|
import { StorageClasses } from "../+storage-classes";
|
||||||
import { PersistentVolumeClaims } from "../+storage-volume-claims";
|
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";
|
import * as routes from "../../../common/routes";
|
||||||
|
|
||||||
@observer
|
interface Dependencies {
|
||||||
export class Storage extends React.Component {
|
isAllowedResource: IsAllowedResource;
|
||||||
static get tabRoutes() {
|
}
|
||||||
|
|
||||||
|
function getRouteTabs({ isAllowedResource }: Dependencies) {
|
||||||
|
return computed(() => {
|
||||||
const tabs: TabLayoutRoute[] = [];
|
const tabs: TabLayoutRoute[] = [];
|
||||||
|
|
||||||
if (isAllowedResource("persistentvolumeclaims")) {
|
if (isAllowedResource("persistentvolumeclaims")) {
|
||||||
@ -47,11 +48,14 @@ export class Storage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return tabs;
|
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