mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
commit
39947aea5a
@ -5,7 +5,7 @@ Lens is lightweight and simple to install. You'll be up and running in just a fe
|
|||||||
|
|
||||||
## System Requirements
|
## System Requirements
|
||||||
|
|
||||||
Review the [System Requirements](/supporting/requirements/) to check if your computer configuration is supported.
|
Review the [System Requirements](../supporting/requirements.md) to check if your computer configuration is supported.
|
||||||
|
|
||||||
|
|
||||||
## macOS
|
## macOS
|
||||||
|
|||||||
@ -4,7 +4,7 @@ Lens has integration to Helm making it easy to install and manage Helm charts an
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Managing Helm Reporistories
|
## Managing Helm Repositories
|
||||||
|
|
||||||
Used Helm repositories are possible to configure in the [Preferences](/getting-started/preferences). Lens app will fetch available Helm repositories from the [Artifact HUB](https://artifacthub.io/) and automatically add `bitnami` repository by default if no other repositories are already configured. If any other repositories are needed to add, those can be added manually via command line. **Note!** Configured Helm repositories are added globally to user's computer, so other processes can see those as well.
|
Used Helm repositories are possible to configure in the [Preferences](/getting-started/preferences). Lens app will fetch available Helm repositories from the [Artifact HUB](https://artifacthub.io/) and automatically add `bitnami` repository by default if no other repositories are already configured. If any other repositories are needed to add, those can be added manually via command line. **Note!** Configured Helm repositories are added globally to user's computer, so other processes can see those as well.
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "kontena-lens",
|
"name": "kontena-lens",
|
||||||
"productName": "Lens",
|
"productName": "Lens",
|
||||||
"description": "Lens - The Kubernetes IDE",
|
"description": "Lens - The Kubernetes IDE",
|
||||||
"version": "4.1.2",
|
"version": "4.1.3",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2020, Mirantis, Inc.",
|
"copyright": "© 2020, Mirantis, Inc.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@ -8,7 +8,7 @@ interface StatusBarComponents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface StatusBarRegistrationV2 {
|
interface StatusBarRegistrationV2 {
|
||||||
components: StatusBarComponents;
|
components?: StatusBarComponents; // has to be optional for backwards compatability
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StatusBarRegistration extends StatusBarRegistrationV2 {
|
export interface StatusBarRegistration extends StatusBarRegistrationV2 {
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { delay } from "../common/utils";
|
|||||||
import { areArgsUpdateAvailableToBackchannel, AutoUpdateLogPrefix, broadcastMessage, onceCorrect, UpdateAvailableChannel, UpdateAvailableToBackchannel } from "../common/ipc";
|
import { areArgsUpdateAvailableToBackchannel, AutoUpdateLogPrefix, broadcastMessage, onceCorrect, UpdateAvailableChannel, UpdateAvailableToBackchannel } from "../common/ipc";
|
||||||
import { ipcMain } from "electron";
|
import { ipcMain } from "electron";
|
||||||
|
|
||||||
|
let installVersion: null | string = null;
|
||||||
|
|
||||||
function handleAutoUpdateBackChannel(event: Electron.IpcMainEvent, ...[arg]: UpdateAvailableToBackchannel) {
|
function handleAutoUpdateBackChannel(event: Electron.IpcMainEvent, ...[arg]: UpdateAvailableToBackchannel) {
|
||||||
if (arg.doUpdate) {
|
if (arg.doUpdate) {
|
||||||
if (arg.now) {
|
if (arg.now) {
|
||||||
@ -37,6 +39,22 @@ export function startUpdateChecking(interval = 1000 * 60 * 60 * 24): void {
|
|||||||
|
|
||||||
autoUpdater
|
autoUpdater
|
||||||
.on("update-available", (args: UpdateInfo) => {
|
.on("update-available", (args: UpdateInfo) => {
|
||||||
|
if (autoUpdater.autoInstallOnAppQuit) {
|
||||||
|
// a previous auto-update loop was completed with YES+LATER, check if same version
|
||||||
|
if (installVersion === args.version) {
|
||||||
|
// same version, don't broadcast
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should be always set to false here because it is the reasonable
|
||||||
|
* default. Namely, if a don't auto update to a version that the user
|
||||||
|
* didn't ask for.
|
||||||
|
*/
|
||||||
|
autoUpdater.autoInstallOnAppQuit = false;
|
||||||
|
installVersion = args.version;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const backchannel = `auto-update:${args.version}`;
|
const backchannel = `auto-update:${args.version}`;
|
||||||
|
|
||||||
@ -53,6 +71,7 @@ export function startUpdateChecking(interval = 1000 * 60 * 60 * 24): void {
|
|||||||
broadcastMessage(UpdateAvailableChannel, backchannel, args);
|
broadcastMessage(UpdateAvailableChannel, backchannel, args);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`${AutoUpdateLogPrefix}: broadcasting failed`, { error });
|
logger.error(`${AutoUpdateLogPrefix}: broadcasting failed`, { error });
|
||||||
|
installVersion = undefined;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
108
src/main/helm/__mocks__/helm-chart-manager.ts
Normal file
108
src/main/helm/__mocks__/helm-chart-manager.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { HelmRepo, HelmRepoManager } from "../helm-repo-manager";
|
||||||
|
|
||||||
|
export class HelmChartManager {
|
||||||
|
private cache: any = {};
|
||||||
|
private repo: HelmRepo;
|
||||||
|
|
||||||
|
constructor(repo: HelmRepo){
|
||||||
|
this.cache = HelmRepoManager.cache;
|
||||||
|
this.repo = repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async charts(): Promise<any> {
|
||||||
|
switch (this.repo.name) {
|
||||||
|
case "stable":
|
||||||
|
return Promise.resolve({
|
||||||
|
"apm-server": [
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "apm-server",
|
||||||
|
version: "2.1.7",
|
||||||
|
repo: "stable",
|
||||||
|
digest: "test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "apm-server",
|
||||||
|
version: "2.1.6",
|
||||||
|
repo: "stable",
|
||||||
|
digest: "test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"redis": [
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "apm-server",
|
||||||
|
version: "1.0.0",
|
||||||
|
repo: "stable",
|
||||||
|
digest: "test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "apm-server",
|
||||||
|
version: "0.0.9",
|
||||||
|
repo: "stable",
|
||||||
|
digest: "test"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
case "experiment":
|
||||||
|
return Promise.resolve({
|
||||||
|
"fairwind": [
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "fairwind",
|
||||||
|
version: "0.0.1",
|
||||||
|
repo: "experiment",
|
||||||
|
digest: "test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "fairwind",
|
||||||
|
version: "0.0.2",
|
||||||
|
repo: "experiment",
|
||||||
|
digest: "test",
|
||||||
|
deprecated: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
case "bitnami":
|
||||||
|
return Promise.resolve({
|
||||||
|
"hotdog": [
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "hotdog",
|
||||||
|
version: "1.0.1",
|
||||||
|
repo: "bitnami",
|
||||||
|
digest: "test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "hotdog",
|
||||||
|
version: "1.0.2",
|
||||||
|
repo: "bitnami",
|
||||||
|
digest: "test",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pretzel": [
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "pretzel",
|
||||||
|
version: "1.0",
|
||||||
|
repo: "bitnami",
|
||||||
|
digest: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "pretzel",
|
||||||
|
version: "1.0.1",
|
||||||
|
repo: "bitnami",
|
||||||
|
digest: "test"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return Promise.resolve({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
104
src/main/helm/__tests__/helm-service.test.ts
Normal file
104
src/main/helm/__tests__/helm-service.test.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { helmService } from "../helm-service";
|
||||||
|
import { repoManager } from "../helm-repo-manager";
|
||||||
|
|
||||||
|
jest.spyOn(repoManager, "init").mockImplementation();
|
||||||
|
|
||||||
|
jest.mock("../helm-chart-manager");
|
||||||
|
|
||||||
|
describe("Helm Service tests", () => {
|
||||||
|
test("list charts without deprecated ones", async () => {
|
||||||
|
jest.spyOn(repoManager, "repositories").mockImplementation(async () => {
|
||||||
|
return [
|
||||||
|
{ name: "stable", url: "stableurl" },
|
||||||
|
{ name: "experiment", url: "experimenturl" }
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const charts = await helmService.listCharts();
|
||||||
|
|
||||||
|
expect(charts).toEqual({
|
||||||
|
stable: {
|
||||||
|
"apm-server": [
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "apm-server",
|
||||||
|
version: "2.1.7",
|
||||||
|
repo: "stable",
|
||||||
|
digest: "test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "apm-server",
|
||||||
|
version: "2.1.6",
|
||||||
|
repo: "stable",
|
||||||
|
digest: "test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"redis": [
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "apm-server",
|
||||||
|
version: "1.0.0",
|
||||||
|
repo: "stable",
|
||||||
|
digest: "test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "apm-server",
|
||||||
|
version: "0.0.9",
|
||||||
|
repo: "stable",
|
||||||
|
digest: "test"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
experiment: {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("list charts sorted by version in descending order", async () => {
|
||||||
|
jest.spyOn(repoManager, "repositories").mockImplementation(async () => {
|
||||||
|
return [
|
||||||
|
{ name: "bitnami", url: "bitnamiurl" }
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const charts = await helmService.listCharts();
|
||||||
|
|
||||||
|
expect(charts).toEqual({
|
||||||
|
bitnami: {
|
||||||
|
"hotdog": [
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "hotdog",
|
||||||
|
version: "1.0.2",
|
||||||
|
repo: "bitnami",
|
||||||
|
digest: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "hotdog",
|
||||||
|
version: "1.0.1",
|
||||||
|
repo: "bitnami",
|
||||||
|
digest: "test"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"pretzel": [
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "pretzel",
|
||||||
|
version: "1.0.1",
|
||||||
|
repo: "bitnami",
|
||||||
|
digest: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiVersion: "3.0.0",
|
||||||
|
name: "pretzel",
|
||||||
|
version: "1.0",
|
||||||
|
repo: "bitnami",
|
||||||
|
digest: "test"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -4,9 +4,10 @@ import { HelmRepo, HelmRepoManager } from "./helm-repo-manager";
|
|||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
import { promiseExec } from "../promise-exec";
|
import { promiseExec } from "../promise-exec";
|
||||||
import { helmCli } from "./helm-cli";
|
import { helmCli } from "./helm-cli";
|
||||||
|
import type { RepoHelmChartList } from "../../renderer/api/endpoints/helm-charts.api";
|
||||||
|
|
||||||
type CachedYaml = {
|
type CachedYaml = {
|
||||||
entries: any; // todo: types
|
entries: RepoHelmChartList
|
||||||
};
|
};
|
||||||
|
|
||||||
export class HelmChartManager {
|
export class HelmChartManager {
|
||||||
@ -24,15 +25,15 @@ export class HelmChartManager {
|
|||||||
return charts[name];
|
return charts[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
public async charts(): Promise<any> {
|
public async charts(): Promise<RepoHelmChartList> {
|
||||||
try {
|
try {
|
||||||
const cachedYaml = await this.cachedYaml();
|
const cachedYaml = await this.cachedYaml();
|
||||||
|
|
||||||
return cachedYaml["entries"];
|
return cachedYaml["entries"];
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
logger.error(error);
|
logger.error("HELM-CHART-MANAGER]: failed to list charts", { error });
|
||||||
|
|
||||||
return [];
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
|
import semver from "semver";
|
||||||
import { Cluster } from "../cluster";
|
import { Cluster } from "../cluster";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
import { repoManager } from "./helm-repo-manager";
|
import { repoManager } from "./helm-repo-manager";
|
||||||
import { HelmChartManager } from "./helm-chart-manager";
|
import { HelmChartManager } from "./helm-chart-manager";
|
||||||
import { releaseManager } from "./helm-release-manager";
|
import { releaseManager } from "./helm-release-manager";
|
||||||
|
import { HelmChartList, RepoHelmChartList } from "../../renderer/api/endpoints/helm-charts.api";
|
||||||
|
|
||||||
class HelmService {
|
class HelmService {
|
||||||
public async installChart(cluster: Cluster, data: { chart: string; values: {}; name: string; namespace: string; version: string }) {
|
public async installChart(cluster: Cluster, data: { chart: string; values: {}; name: string; namespace: string; version: string }) {
|
||||||
@ -10,7 +12,7 @@ class HelmService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async listCharts() {
|
public async listCharts() {
|
||||||
const charts: any = {};
|
const charts: HelmChartList = {};
|
||||||
|
|
||||||
await repoManager.init();
|
await repoManager.init();
|
||||||
const repositories = await repoManager.repositories();
|
const repositories = await repoManager.repositories();
|
||||||
@ -18,14 +20,10 @@ class HelmService {
|
|||||||
for (const repo of repositories) {
|
for (const repo of repositories) {
|
||||||
charts[repo.name] = {};
|
charts[repo.name] = {};
|
||||||
const manager = new HelmChartManager(repo);
|
const manager = new HelmChartManager(repo);
|
||||||
let entries = await manager.charts();
|
const sortedCharts = this.sortChartsByVersion(await manager.charts());
|
||||||
|
const enabledCharts = this.excludeDeprecatedChartGroups(sortedCharts);
|
||||||
|
|
||||||
entries = this.excludeDeprecated(entries);
|
charts[repo.name] = enabledCharts;
|
||||||
|
|
||||||
for (const key in entries) {
|
|
||||||
entries[key] = entries[key][0];
|
|
||||||
}
|
|
||||||
charts[repo.name] = entries;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return charts;
|
return charts;
|
||||||
@ -96,20 +94,30 @@ class HelmService {
|
|||||||
return { message: output };
|
return { message: output };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected excludeDeprecated(entries: any) {
|
private excludeDeprecatedChartGroups(chartGroups: RepoHelmChartList) {
|
||||||
for (const key in entries) {
|
const groups = new Map(Object.entries(chartGroups));
|
||||||
entries[key] = entries[key].filter((entry: any) => {
|
|
||||||
if (Array.isArray(entry)) {
|
|
||||||
return entry[0]["deprecated"] != true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry["deprecated"] != true;
|
for (const [chartName, charts] of groups) {
|
||||||
|
if (charts[0].deprecated) {
|
||||||
|
groups.delete(chartName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.fromEntries(groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sortChartsByVersion(chartGroups: RepoHelmChartList) {
|
||||||
|
for (const key in chartGroups) {
|
||||||
|
chartGroups[key] = chartGroups[key].sort((first, second) => {
|
||||||
|
const firstVersion = semver.coerce(first.version || 0);
|
||||||
|
const secondVersion = semver.coerce(second.version || 0);
|
||||||
|
|
||||||
|
return semver.compare(secondVersion, firstVersion);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries;
|
return chartGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const helmService = new HelmService();
|
export const helmService = new HelmService();
|
||||||
|
|||||||
@ -3,11 +3,8 @@ import { apiBase } from "../index";
|
|||||||
import { stringify } from "querystring";
|
import { stringify } from "querystring";
|
||||||
import { autobind } from "../../utils";
|
import { autobind } from "../../utils";
|
||||||
|
|
||||||
interface IHelmChartList {
|
export type RepoHelmChartList = Record<string, HelmChart[]>;
|
||||||
[repo: string]: {
|
export type HelmChartList = Record<string, RepoHelmChartList>;
|
||||||
[name: string]: HelmChart;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IHelmChartDetails {
|
export interface IHelmChartDetails {
|
||||||
readme: string;
|
readme: string;
|
||||||
@ -22,12 +19,12 @@ const endpoint = compile(`/v2/charts/:repo?/:name?`) as (params?: {
|
|||||||
export const helmChartsApi = {
|
export const helmChartsApi = {
|
||||||
list() {
|
list() {
|
||||||
return apiBase
|
return apiBase
|
||||||
.get<IHelmChartList>(endpoint())
|
.get<HelmChartList>(endpoint())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
return Object
|
return Object
|
||||||
.values(data)
|
.values(data)
|
||||||
.reduce((allCharts, repoCharts) => allCharts.concat(Object.values(repoCharts)), [])
|
.reduce((allCharts, repoCharts) => allCharts.concat(Object.values(repoCharts)), [])
|
||||||
.map(HelmChart.create);
|
.map(([chart]) => HelmChart.create(chart));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -272,6 +272,7 @@ export class KubeApi<T extends KubeObject = any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected parseResponse(data: KubeJsonApiData | KubeJsonApiData[] | KubeJsonApiDataList, namespace?: string): any {
|
protected parseResponse(data: KubeJsonApiData | KubeJsonApiData[] | KubeJsonApiDataList, namespace?: string): any {
|
||||||
|
if (!data) return;
|
||||||
const KubeObjectConstructor = this.objectConstructor;
|
const KubeObjectConstructor = this.objectConstructor;
|
||||||
|
|
||||||
if (KubeObject.isJsonApiData(data)) {
|
if (KubeObject.isJsonApiData(data)) {
|
||||||
|
|||||||
@ -82,9 +82,9 @@ export class HelmChartDetails extends Component<Props> {
|
|||||||
<div className="intro-contents box grow">
|
<div className="intro-contents box grow">
|
||||||
<div className="description flex align-center justify-space-between">
|
<div className="description flex align-center justify-space-between">
|
||||||
{selectedChart.getDescription()}
|
{selectedChart.getDescription()}
|
||||||
<Button primary label={`Install`} onClick={this.install} />
|
<Button primary label="Install" onClick={this.install} />
|
||||||
</div>
|
</div>
|
||||||
<DrawerItem name={`Version`} className="version" onClick={stopPropagation}>
|
<DrawerItem name="Version" className="version" onClick={stopPropagation}>
|
||||||
<Select
|
<Select
|
||||||
themeName="outlined"
|
themeName="outlined"
|
||||||
menuPortalTarget={null}
|
menuPortalTarget={null}
|
||||||
@ -93,16 +93,16 @@ export class HelmChartDetails extends Component<Props> {
|
|||||||
onChange={onVersionChange}
|
onChange={onVersionChange}
|
||||||
/>
|
/>
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name={`Home`}>
|
<DrawerItem name="Home">
|
||||||
<a href={selectedChart.getHome()} target="_blank" rel="noreferrer">{selectedChart.getHome()}</a>
|
<a href={selectedChart.getHome()} target="_blank" rel="noreferrer">{selectedChart.getHome()}</a>
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name={`Maintainers`} className="maintainers">
|
<DrawerItem name="Maintainers" className="maintainers">
|
||||||
{selectedChart.getMaintainers().map(({ name, email, url }) =>
|
{selectedChart.getMaintainers().map(({ name, email, url }) =>
|
||||||
<a key={name} href={url || `mailto:${email}`} target="_blank" rel="noreferrer">{name}</a>
|
<a key={name} href={url || `mailto:${email}`} target="_blank" rel="noreferrer">{name}</a>
|
||||||
)}
|
)}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
{selectedChart.getKeywords().length > 0 && (
|
{selectedChart.getKeywords().length > 0 && (
|
||||||
<DrawerItem name={`Keywords`} labelsOnly>
|
<DrawerItem name="Keywords" labelsOnly>
|
||||||
{selectedChart.getKeywords().map(key => <Badge key={key} label={key} />)}
|
{selectedChart.getKeywords().map(key => <Badge key={key} label={key} />)}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -72,11 +72,8 @@ export class HelmCharts extends Component<Props> {
|
|||||||
(chart: HelmChart) => chart.getAppVersion(),
|
(chart: HelmChart) => chart.getAppVersion(),
|
||||||
(chart: HelmChart) => chart.getKeywords(),
|
(chart: HelmChart) => chart.getKeywords(),
|
||||||
]}
|
]}
|
||||||
filterItems={[
|
|
||||||
(items: HelmChart[]) => items.filter(item => !item.deprecated)
|
|
||||||
]}
|
|
||||||
customizeHeader={() => (
|
customizeHeader={() => (
|
||||||
<SearchInputUrl placeholder={`Search Helm Charts`} />
|
<SearchInputUrl placeholder="Search Helm Charts" />
|
||||||
)}
|
)}
|
||||||
renderTableHeader={[
|
renderTableHeader={[
|
||||||
{ className: "icon", showWithColumn: columnId.name },
|
{ className: "icon", showWithColumn: columnId.name },
|
||||||
|
|||||||
@ -111,7 +111,7 @@ export class ReleaseDetails extends Component<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="values">
|
<div className="values">
|
||||||
<DrawerTitle title={`Values`}/>
|
<DrawerTitle title="Values"/>
|
||||||
<div className="flex column gaps">
|
<div className="flex column gaps">
|
||||||
<AceEditor
|
<AceEditor
|
||||||
mode="yaml"
|
mode="yaml"
|
||||||
@ -120,7 +120,7 @@ export class ReleaseDetails extends Component<Props> {
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
label={`Save`}
|
label="Save"
|
||||||
waiting={saving}
|
waiting={saving}
|
||||||
onClick={this.updateValues}
|
onClick={this.updateValues}
|
||||||
/>
|
/>
|
||||||
@ -200,7 +200,7 @@ export class ReleaseDetails extends Component<Props> {
|
|||||||
<span>{release.getChart()}</span>
|
<span>{release.getChart()}</span>
|
||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
label={`Upgrade`}
|
label="Upgrade"
|
||||||
className="box right upgrade"
|
className="box right upgrade"
|
||||||
onClick={this.upgradeVersion}
|
onClick={this.upgradeVersion}
|
||||||
/>
|
/>
|
||||||
@ -226,9 +226,9 @@ export class ReleaseDetails extends Component<Props> {
|
|||||||
/>
|
/>
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
{this.renderValues()}
|
{this.renderValues()}
|
||||||
<DrawerTitle title={`Notes`}/>
|
<DrawerTitle title="Notes"/>
|
||||||
{this.renderNotes()}
|
{this.renderNotes()}
|
||||||
<DrawerTitle title={`Resources`}/>
|
<DrawerTitle title="Resources"/>
|
||||||
{this.renderResources()}
|
{this.renderResources()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export class HelmReleaseMenu extends React.Component<Props> {
|
|||||||
<>
|
<>
|
||||||
{hasRollback && (
|
{hasRollback && (
|
||||||
<MenuItem onClick={this.rollback}>
|
<MenuItem onClick={this.rollback}>
|
||||||
<Icon material="history" interactive={toolbar} title={`Rollback`}/>
|
<Icon material="history" interactive={toolbar} title="Rollback"/>
|
||||||
<span className="title">Rollback</span>
|
<span className="title">Rollback</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -143,7 +143,7 @@ export const ClusterPieCharts = observer(() => {
|
|||||||
<div className="chart flex column align-center box grow">
|
<div className="chart flex column align-center box grow">
|
||||||
<PieChart
|
<PieChart
|
||||||
data={cpuData}
|
data={cpuData}
|
||||||
title={`CPU`}
|
title="CPU"
|
||||||
legendColors={["#c93dce", "#4caf50", "#3d90ce", defaultColor]}
|
legendColors={["#c93dce", "#4caf50", "#3d90ce", defaultColor]}
|
||||||
/>
|
/>
|
||||||
{cpuLimitsOverload && renderLimitWarning()}
|
{cpuLimitsOverload && renderLimitWarning()}
|
||||||
@ -151,7 +151,7 @@ export const ClusterPieCharts = observer(() => {
|
|||||||
<div className="chart flex column align-center box grow">
|
<div className="chart flex column align-center box grow">
|
||||||
<PieChart
|
<PieChart
|
||||||
data={memoryData}
|
data={memoryData}
|
||||||
title={`Memory`}
|
title="Memory"
|
||||||
legendColors={["#c93dce", "#4caf50", "#3d90ce", defaultColor]}
|
legendColors={["#c93dce", "#4caf50", "#3d90ce", defaultColor]}
|
||||||
/>
|
/>
|
||||||
{memoryLimitsOverload && renderLimitWarning()}
|
{memoryLimitsOverload && renderLimitWarning()}
|
||||||
@ -159,7 +159,7 @@ export const ClusterPieCharts = observer(() => {
|
|||||||
<div className="chart flex column align-center box grow">
|
<div className="chart flex column align-center box grow">
|
||||||
<PieChart
|
<PieChart
|
||||||
data={podsData}
|
data={podsData}
|
||||||
title={`Pods`}
|
title="Pods"
|
||||||
legendColors={["#4caf50", defaultColor]}
|
legendColors={["#4caf50", defaultColor]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -63,21 +63,21 @@ export class LimitRangeDetails extends React.Component<Props> {
|
|||||||
<KubeObjectMeta object={limitRange}/>
|
<KubeObjectMeta object={limitRange}/>
|
||||||
|
|
||||||
{containerLimits.length > 0 &&
|
{containerLimits.length > 0 &&
|
||||||
<DrawerItem name={`Container Limits`} labelsOnly>
|
<DrawerItem name="Container Limits" labelsOnly>
|
||||||
{
|
{
|
||||||
renderLimitDetails(containerLimits, [Resource.CPU, Resource.MEMORY, Resource.EPHEMERAL_STORAGE])
|
renderLimitDetails(containerLimits, [Resource.CPU, Resource.MEMORY, Resource.EPHEMERAL_STORAGE])
|
||||||
}
|
}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
}
|
}
|
||||||
{podLimits.length > 0 &&
|
{podLimits.length > 0 &&
|
||||||
<DrawerItem name={`Pod Limits`} labelsOnly>
|
<DrawerItem name="Pod Limits" labelsOnly>
|
||||||
{
|
{
|
||||||
renderLimitDetails(podLimits, [Resource.CPU, Resource.MEMORY, Resource.EPHEMERAL_STORAGE])
|
renderLimitDetails(podLimits, [Resource.CPU, Resource.MEMORY, Resource.EPHEMERAL_STORAGE])
|
||||||
}
|
}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
}
|
}
|
||||||
{pvcLimits.length > 0 &&
|
{pvcLimits.length > 0 &&
|
||||||
<DrawerItem name={`Persistent Volume Claim Limits`} labelsOnly>
|
<DrawerItem name="Persistent Volume Claim Limits" labelsOnly>
|
||||||
{
|
{
|
||||||
renderLimitDetails(pvcLimits, [Resource.STORAGE])
|
renderLimitDetails(pvcLimits, [Resource.STORAGE])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -146,7 +146,7 @@ export class AddQuotaDialog extends React.Component<Props> {
|
|||||||
<div className="flex gaps">
|
<div className="flex gaps">
|
||||||
<Input
|
<Input
|
||||||
required autoFocus
|
required autoFocus
|
||||||
placeholder={`ResourceQuota name`}
|
placeholder="ResourceQuota name"
|
||||||
validators={systemName}
|
validators={systemName}
|
||||||
value={this.quotaName} onChange={v => this.quotaName = v.toLowerCase()}
|
value={this.quotaName} onChange={v => this.quotaName = v.toLowerCase()}
|
||||||
className="box grow"
|
className="box grow"
|
||||||
@ -156,7 +156,7 @@ export class AddQuotaDialog extends React.Component<Props> {
|
|||||||
<SubTitle title="Namespace" />
|
<SubTitle title="Namespace" />
|
||||||
<NamespaceSelect
|
<NamespaceSelect
|
||||||
value={this.namespace}
|
value={this.namespace}
|
||||||
placeholder={`Namespace`}
|
placeholder="Namespace"
|
||||||
themeName="light"
|
themeName="light"
|
||||||
className="box grow"
|
className="box grow"
|
||||||
onChange={({ value }) => this.namespace = value}
|
onChange={({ value }) => this.namespace = value}
|
||||||
@ -167,14 +167,14 @@ export class AddQuotaDialog extends React.Component<Props> {
|
|||||||
<Select
|
<Select
|
||||||
className="quota-select"
|
className="quota-select"
|
||||||
themeName="light"
|
themeName="light"
|
||||||
placeholder={`Select a quota..`}
|
placeholder="Select a quota.."
|
||||||
options={this.quotaOptions}
|
options={this.quotaOptions}
|
||||||
value={this.quotaSelectValue}
|
value={this.quotaSelectValue}
|
||||||
onChange={({ value }) => this.quotaSelectValue = value}
|
onChange={({ value }) => this.quotaSelectValue = value}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
maxLength={10}
|
maxLength={10}
|
||||||
placeholder={`Value`}
|
placeholder="Value"
|
||||||
value={this.quotaInputValue}
|
value={this.quotaInputValue}
|
||||||
onChange={v => this.quotaInputValue = v}
|
onChange={v => this.quotaInputValue = v}
|
||||||
onKeyDown={this.onInputQuota}
|
onKeyDown={this.onInputQuota}
|
||||||
@ -183,7 +183,7 @@ export class AddQuotaDialog extends React.Component<Props> {
|
|||||||
<Button round primary onClick={this.setQuota}>
|
<Button round primary onClick={this.setQuota}>
|
||||||
<Icon
|
<Icon
|
||||||
material={this.quotas[this.quotaSelectValue] ? "edit" : "add"}
|
material={this.quotas[this.quotaSelectValue] ? "edit" : "add"}
|
||||||
tooltip={`Set quota`}
|
tooltip="Set quota"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -133,7 +133,7 @@ export class AddSecretDialog extends React.Component<Props> {
|
|||||||
<SubTitle compact className="fields-title" title={upperFirst(field.toString())}>
|
<SubTitle compact className="fields-title" title={upperFirst(field.toString())}>
|
||||||
<Icon
|
<Icon
|
||||||
small
|
small
|
||||||
tooltip={`Add field`}
|
tooltip="Add field"
|
||||||
material="add_circle_outline"
|
material="add_circle_outline"
|
||||||
onClick={() => this.addField(field)}
|
onClick={() => this.addField(field)}
|
||||||
/>
|
/>
|
||||||
@ -146,7 +146,7 @@ export class AddSecretDialog extends React.Component<Props> {
|
|||||||
<div key={index} className="secret-field flex gaps auto align-center">
|
<div key={index} className="secret-field flex gaps auto align-center">
|
||||||
<Input
|
<Input
|
||||||
className="key"
|
className="key"
|
||||||
placeholder={`Name`}
|
placeholder="Name"
|
||||||
title={key}
|
title={key}
|
||||||
tabIndex={required ? -1 : 0}
|
tabIndex={required ? -1 : 0}
|
||||||
readOnly={required}
|
readOnly={required}
|
||||||
@ -156,7 +156,7 @@ export class AddSecretDialog extends React.Component<Props> {
|
|||||||
multiLine maxRows={5}
|
multiLine maxRows={5}
|
||||||
required={required}
|
required={required}
|
||||||
className="value"
|
className="value"
|
||||||
placeholder={`Value`}
|
placeholder="Value"
|
||||||
value={value} onChange={v => item.value = v}
|
value={value} onChange={v => item.value = v}
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
@ -194,7 +194,7 @@ export class AddSecretDialog extends React.Component<Props> {
|
|||||||
<SubTitle title="Secret name" />
|
<SubTitle title="Secret name" />
|
||||||
<Input
|
<Input
|
||||||
autoFocus required
|
autoFocus required
|
||||||
placeholder={`Name`}
|
placeholder="Name"
|
||||||
validators={systemName}
|
validators={systemName}
|
||||||
value={name} onChange={v => this.name = v}
|
value={name} onChange={v => this.name = v}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -69,7 +69,7 @@ export class SecretDetails extends React.Component<Props> {
|
|||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
{!isEmpty(this.data) && (
|
{!isEmpty(this.data) && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title={`Data`}/>
|
<DrawerTitle title="Data"/>
|
||||||
{
|
{
|
||||||
Object.entries(this.data).map(([name, value]) => {
|
Object.entries(this.data).map(([name, value]) => {
|
||||||
const revealSecret = this.revealSecret[name];
|
const revealSecret = this.revealSecret[name];
|
||||||
@ -107,7 +107,7 @@ export class SecretDetails extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
label={`Save`} waiting={this.isSaving}
|
label="Save" waiting={this.isSaving}
|
||||||
className="save-btn"
|
className="save-btn"
|
||||||
onClick={this.saveSecret}
|
onClick={this.saveSecret}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -72,7 +72,7 @@ export class AddNamespaceDialog extends React.Component<Props> {
|
|||||||
<Input
|
<Input
|
||||||
required autoFocus
|
required autoFocus
|
||||||
iconLeft="layers"
|
iconLeft="layers"
|
||||||
placeholder={`Namespace`}
|
placeholder="Namespace"
|
||||||
validators={systemName}
|
validators={systemName}
|
||||||
value={namespace} onChange={v => this.namespace = v.toLowerCase()}
|
value={namespace} onChange={v => this.namespace = v.toLowerCase()}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -60,7 +60,7 @@ export class NamespaceDetails extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name={`Limit Ranges`}>
|
<DrawerItem name="Limit Ranges">
|
||||||
{!this.limitranges && limitRangeStore.isLoading && <Spinner/>}
|
{!this.limitranges && limitRangeStore.isLoading && <Spinner/>}
|
||||||
{this.limitranges.map(limitrange => {
|
{this.limitranges.map(limitrange => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export class NamespaceSelectFilter extends React.Component {
|
|||||||
if (namespace) {
|
if (namespace) {
|
||||||
namespaceStore.toggleContext(namespace);
|
namespaceStore.toggleContext(namespace);
|
||||||
} else {
|
} else {
|
||||||
namespaceStore.resetContext(); // "All namespaces" clicked, empty list considered as "all"
|
namespaceStore.toggleAll(false); // "All namespaces" clicked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export class NamespaceSelect extends React.Component<Props> {
|
|||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
kubeWatchApi.subscribeStores([namespaceStore], {
|
kubeWatchApi.subscribeStores([namespaceStore], {
|
||||||
preload: true,
|
preload: true,
|
||||||
|
loadOnce: true, // skip reloading namespaces on every render / page visit
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { Namespace, namespacesApi } from "../../api/endpoints/namespaces.api";
|
|||||||
import { createPageParam } from "../../navigation";
|
import { createPageParam } from "../../navigation";
|
||||||
import { apiManager } from "../../api/api-manager";
|
import { apiManager } from "../../api/api-manager";
|
||||||
|
|
||||||
const storage = createStorage<string[]>("context_namespaces", []);
|
const storage = createStorage<string[]>("context_namespaces");
|
||||||
|
|
||||||
export const namespaceUrlParam = createPageParam<string[]>({
|
export const namespaceUrlParam = createPageParam<string[]>({
|
||||||
name: "namespaces",
|
name: "namespaces",
|
||||||
@ -74,11 +74,11 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
|||||||
@computed
|
@computed
|
||||||
private get initialNamespaces(): string[] {
|
private get initialNamespaces(): string[] {
|
||||||
const namespaces = new Set(this.allowedNamespaces);
|
const namespaces = new Set(this.allowedNamespaces);
|
||||||
const prevSelected = storage.get().filter(namespace => namespaces.has(namespace));
|
const prevSelectedNamespaces = storage.get();
|
||||||
|
|
||||||
// return previously saved namespaces from local-storage
|
// return previously saved namespaces from local-storage (if any)
|
||||||
if (prevSelected.length > 0) {
|
if (prevSelectedNamespaces) {
|
||||||
return prevSelected;
|
return prevSelectedNamespaces.filter(namespace => namespaces.has(namespace));
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise select "default" or first allowed namespace
|
// otherwise select "default" or first allowed namespace
|
||||||
@ -120,7 +120,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
|||||||
protected async loadItems(params: KubeObjectStoreLoadingParams) {
|
protected async loadItems(params: KubeObjectStoreLoadingParams) {
|
||||||
const { allowedNamespaces } = this;
|
const { allowedNamespaces } = this;
|
||||||
|
|
||||||
let namespaces = await super.loadItems(params);
|
let namespaces = (await super.loadItems(params)) || [];
|
||||||
|
|
||||||
namespaces = namespaces.filter(namespace => allowedNamespaces.includes(namespace.getName()));
|
namespaces = namespaces.filter(namespace => allowedNamespaces.includes(namespace.getName()));
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
|||||||
if (showAll) {
|
if (showAll) {
|
||||||
this.setContext(this.allowedNamespaces);
|
this.setContext(this.allowedNamespaces);
|
||||||
} else {
|
} else {
|
||||||
this.contextNs.clear();
|
this.resetContext(); // empty context considered as "All namespaces"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.toggleAll(!this.hasAllContexts);
|
this.toggleAll(!this.hasAllContexts);
|
||||||
|
|||||||
@ -117,7 +117,7 @@ export class NetworkPolicyDetails extends React.Component<Props> {
|
|||||||
|
|
||||||
{ingress && (
|
{ingress && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title={`Ingress`}/>
|
<DrawerTitle title="Ingress"/>
|
||||||
{ingress.map((ingress, i) => {
|
{ingress.map((ingress, i) => {
|
||||||
const { ports } = ingress;
|
const { ports } = ingress;
|
||||||
|
|
||||||
|
|||||||
@ -50,7 +50,7 @@ export class ServiceDetails extends React.Component<Props> {
|
|||||||
{spec.sessionAffinity}
|
{spec.sessionAffinity}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
|
|
||||||
<DrawerTitle title={`Connection`}/>
|
<DrawerTitle title="Connection"/>
|
||||||
|
|
||||||
<DrawerItem name="Cluster IP">
|
<DrawerItem name="Cluster IP">
|
||||||
{spec.clusterIP}
|
{spec.clusterIP}
|
||||||
@ -77,7 +77,7 @@ export class ServiceDetails extends React.Component<Props> {
|
|||||||
{spec.loadBalancerIP}
|
{spec.loadBalancerIP}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
)}
|
)}
|
||||||
<DrawerTitle title={`Endpoint`}/>
|
<DrawerTitle title="Endpoint"/>
|
||||||
|
|
||||||
<ServiceDetailsEndpoint endpoint={endpoint}/>
|
<ServiceDetailsEndpoint endpoint={endpoint}/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export class ServicePortComponent extends React.Component<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("ServicePortComponent", { waiting: this.waiting })}>
|
<div className={cssNames("ServicePortComponent", { waiting: this.waiting })}>
|
||||||
<span title={`Open in a browser`} onClick={() => this.portForward() }>
|
<span title="Open in a browser" onClick={() => this.portForward() }>
|
||||||
{port.toString()}
|
{port.toString()}
|
||||||
{this.waiting && (
|
{this.waiting && (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
|||||||
@ -111,7 +111,7 @@ export class AddHelmRepoDialog extends React.Component<Props> {
|
|||||||
<>
|
<>
|
||||||
<SubTitle title="Security settings" />
|
<SubTitle title="Security settings" />
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={`Skip TLS certificate checks for the repository`}
|
label="Skip TLS certificate checks for the repository"
|
||||||
value={this.helmRepo.insecureSkipTlsVerify}
|
value={this.helmRepo.insecureSkipTlsVerify}
|
||||||
onChange={v => this.helmRepo.insecureSkipTlsVerify = v}
|
onChange={v => this.helmRepo.insecureSkipTlsVerify = v}
|
||||||
/>
|
/>
|
||||||
@ -120,12 +120,12 @@ export class AddHelmRepoDialog extends React.Component<Props> {
|
|||||||
{this.renderFileInput(`Cerificate file`, FileType.CertFile, AddHelmRepoDialog.certExtensions)}
|
{this.renderFileInput(`Cerificate file`, FileType.CertFile, AddHelmRepoDialog.certExtensions)}
|
||||||
<SubTitle title="Chart Repository Credentials" />
|
<SubTitle title="Chart Repository Credentials" />
|
||||||
<Input
|
<Input
|
||||||
placeholder={`Username`}
|
placeholder="Username"
|
||||||
value={this.helmRepo.username} onChange= {v => this.helmRepo.username = v}
|
value={this.helmRepo.username} onChange= {v => this.helmRepo.username = v}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder={`Password`}
|
placeholder="Password"
|
||||||
value={this.helmRepo.password} onChange={v => this.helmRepo.password = v}
|
value={this.helmRepo.password} onChange={v => this.helmRepo.password = v}
|
||||||
/>
|
/>
|
||||||
</>);
|
</>);
|
||||||
@ -148,13 +148,13 @@ export class AddHelmRepoDialog extends React.Component<Props> {
|
|||||||
<div className="flex column gaps">
|
<div className="flex column gaps">
|
||||||
<Input
|
<Input
|
||||||
autoFocus required
|
autoFocus required
|
||||||
placeholder={`Helm repo name`}
|
placeholder="Helm repo name"
|
||||||
validators={systemName}
|
validators={systemName}
|
||||||
value={this.helmRepo.name} onChange={v => this.helmRepo.name = v}
|
value={this.helmRepo.name} onChange={v => this.helmRepo.name = v}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
required
|
required
|
||||||
placeholder={`URL`}
|
placeholder="URL"
|
||||||
validators={isUrl}
|
validators={isUrl}
|
||||||
value={this.helmRepo.url} onChange={v => this.helmRepo.url = v}
|
value={this.helmRepo.url} onChange={v => this.helmRepo.url = v}
|
||||||
/>
|
/>
|
||||||
@ -162,7 +162,7 @@ export class AddHelmRepoDialog extends React.Component<Props> {
|
|||||||
More
|
More
|
||||||
<Icon
|
<Icon
|
||||||
small
|
small
|
||||||
tooltip={`More`}
|
tooltip="More"
|
||||||
material={this.showOptions ? "remove" : "add"}
|
material={this.showOptions ? "remove" : "add"}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -122,7 +122,7 @@ export class Preferences extends React.Component {
|
|||||||
<h2>HTTP Proxy</h2>
|
<h2>HTTP Proxy</h2>
|
||||||
<Input
|
<Input
|
||||||
theme="round-black"
|
theme="round-black"
|
||||||
placeholder={`Type HTTP proxy url (example: http://proxy.acme.org:8080)`}
|
placeholder="Type HTTP proxy url (example: http://proxy.acme.org:8080)"
|
||||||
value={this.httpProxy}
|
value={this.httpProxy}
|
||||||
onChange={v => this.httpProxy = v}
|
onChange={v => this.httpProxy = v}
|
||||||
onBlur={() => preferences.httpsProxy = this.httpProxy}
|
onBlur={() => preferences.httpsProxy = this.httpProxy}
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export class StorageClassDetails extends React.Component<Props> {
|
|||||||
)}
|
)}
|
||||||
{parameters && (
|
{parameters && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title={`Parameters`}/>
|
<DrawerTitle title="Parameters"/>
|
||||||
{
|
{
|
||||||
Object.entries(parameters).map(([name, value]) => (
|
Object.entries(parameters).map(([name, value]) => (
|
||||||
<DrawerItem key={name + value} name={startCase(name)}>
|
<DrawerItem key={name + value} name={startCase(name)}>
|
||||||
|
|||||||
@ -71,7 +71,7 @@ export class PersistentVolumeClaimDetails extends React.Component<Props> {
|
|||||||
{volumeClaim.getStatus()}
|
{volumeClaim.getStatus()}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
|
|
||||||
<DrawerTitle title={`Selector`}/>
|
<DrawerTitle title="Selector"/>
|
||||||
|
|
||||||
<DrawerItem name="Match Labels" labelsOnly>
|
<DrawerItem name="Match Labels" labelsOnly>
|
||||||
{volumeClaim.getMatchLabels().map(label => <Badge key={label} label={label}/>)}
|
{volumeClaim.getMatchLabels().map(label => <Badge key={label} label={label}/>)}
|
||||||
|
|||||||
@ -205,7 +205,7 @@ export class AddRoleBindingDialog extends React.Component<Props> {
|
|||||||
<Select
|
<Select
|
||||||
key={this.selectedRoleId}
|
key={this.selectedRoleId}
|
||||||
themeName="light"
|
themeName="light"
|
||||||
placeholder={`Select role..`}
|
placeholder="Select role.."
|
||||||
isDisabled={this.isEditing}
|
isDisabled={this.isEditing}
|
||||||
options={this.roleOptions}
|
options={this.roleOptions}
|
||||||
value={this.selectedRoleId}
|
value={this.selectedRoleId}
|
||||||
@ -224,7 +224,7 @@ export class AddRoleBindingDialog extends React.Component<Props> {
|
|||||||
!this.useRoleForBindingName && (
|
!this.useRoleForBindingName && (
|
||||||
<Input
|
<Input
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder={`Name`}
|
placeholder="Name"
|
||||||
disabled={this.isEditing}
|
disabled={this.isEditing}
|
||||||
value={this.bindingName}
|
value={this.bindingName}
|
||||||
onChange={v => this.bindingName = v}
|
onChange={v => this.bindingName = v}
|
||||||
@ -239,7 +239,7 @@ export class AddRoleBindingDialog extends React.Component<Props> {
|
|||||||
<Select
|
<Select
|
||||||
isMulti
|
isMulti
|
||||||
themeName="light"
|
themeName="light"
|
||||||
placeholder={`Select service accounts`}
|
placeholder="Select service accounts"
|
||||||
autoConvertOptions={false}
|
autoConvertOptions={false}
|
||||||
options={this.serviceAccountOptions}
|
options={this.serviceAccountOptions}
|
||||||
onChange={(opts: BindingSelectOption[]) => {
|
onChange={(opts: BindingSelectOption[]) => {
|
||||||
|
|||||||
@ -117,8 +117,8 @@ export class RoleBindingDetails extends React.Component<Props> {
|
|||||||
<AddRemoveButtons
|
<AddRemoveButtons
|
||||||
onAdd={() => AddRoleBindingDialog.open(roleBinding)}
|
onAdd={() => AddRoleBindingDialog.open(roleBinding)}
|
||||||
onRemove={selectedSubjects.length ? this.removeSelectedSubjects : null}
|
onRemove={selectedSubjects.length ? this.removeSelectedSubjects : null}
|
||||||
addTooltip="Add bindings to {name}"
|
addTooltip={`Add bindings to ${roleRef.name}`}
|
||||||
removeTooltip="Remove selected bindings from ${name}"
|
removeTooltip={`Remove selected bindings from ${roleRef.name}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -50,8 +50,8 @@ export class RoleBindings extends React.Component<Props> {
|
|||||||
renderTableContents={(binding: RoleBinding) => [
|
renderTableContents={(binding: RoleBinding) => [
|
||||||
binding.getName(),
|
binding.getName(),
|
||||||
<KubeObjectStatusIcon key="icon" object={binding} />,
|
<KubeObjectStatusIcon key="icon" object={binding} />,
|
||||||
binding.getSubjectNames(),
|
|
||||||
binding.getNs() || "-",
|
binding.getNs() || "-",
|
||||||
|
binding.getSubjectNames(),
|
||||||
binding.getAge(),
|
binding.getAge(),
|
||||||
]}
|
]}
|
||||||
addRemoveButtons={{
|
addRemoveButtons={{
|
||||||
|
|||||||
@ -71,7 +71,7 @@ export class AddRoleDialog extends React.Component<Props> {
|
|||||||
<SubTitle title="Role Name" />
|
<SubTitle title="Role Name" />
|
||||||
<Input
|
<Input
|
||||||
required autoFocus
|
required autoFocus
|
||||||
placeholder={`Name`}
|
placeholder="Name"
|
||||||
iconLeft="supervisor_account"
|
iconLeft="supervisor_account"
|
||||||
value={this.roleName}
|
value={this.roleName}
|
||||||
onChange={v => this.roleName = v}
|
onChange={v => this.roleName = v}
|
||||||
|
|||||||
@ -66,7 +66,7 @@ export class CreateServiceAccountDialog extends React.Component<Props> {
|
|||||||
<SubTitle title="Account Name" />
|
<SubTitle title="Account Name" />
|
||||||
<Input
|
<Input
|
||||||
autoFocus required
|
autoFocus required
|
||||||
placeholder={`Enter a name`}
|
placeholder="Enter a name"
|
||||||
validators={systemName}
|
validators={systemName}
|
||||||
value={name} onChange={v => this.name = v.toLowerCase()}
|
value={name} onChange={v => this.name = v.toLowerCase()}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -87,7 +87,7 @@ export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MenuItem onClick={() => CronJobTriggerDialog.open(object)}>
|
<MenuItem onClick={() => CronJobTriggerDialog.open(object)}>
|
||||||
<Icon material="play_circle_filled" title={`Trigger`} interactive={toolbar}/>
|
<Icon material="play_circle_filled" title="Trigger" interactive={toolbar}/>
|
||||||
<span className="title">Trigger</span>
|
<span className="title">Trigger</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) {
|
|||||||
Resume CronJob <b>{object.getName()}</b>?
|
Resume CronJob <b>{object.getName()}</b>?
|
||||||
</p>),
|
</p>),
|
||||||
})}>
|
})}>
|
||||||
<Icon material="play_circle_outline" title={`Resume`} interactive={toolbar}/>
|
<Icon material="play_circle_outline" title="Resume" interactive={toolbar}/>
|
||||||
<span className="title">Resume</span>
|
<span className="title">Resume</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) {
|
|||||||
Suspend CronJob <b>{object.getName()}</b>?
|
Suspend CronJob <b>{object.getName()}</b>?
|
||||||
</p>),
|
</p>),
|
||||||
})}>
|
})}>
|
||||||
<Icon material="pause_circle_filled" title={`Suspend`} interactive={toolbar}/>
|
<Icon material="pause_circle_filled" title="Suspend" interactive={toolbar}/>
|
||||||
<span className="title">Suspend</span>
|
<span className="title">Suspend</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,7 +104,7 @@ export function DeploymentMenu(props: KubeObjectMenuProps<Deployment>) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MenuItem onClick={() => DeploymentScaleDialog.open(object)}>
|
<MenuItem onClick={() => DeploymentScaleDialog.open(object)}>
|
||||||
<Icon material="open_with" title={`Scale`} interactive={toolbar}/>
|
<Icon material="open_with" title="Scale" interactive={toolbar}/>
|
||||||
<span className="title">Scale</span>
|
<span className="title">Scale</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={() => ConfirmDialog.open({
|
<MenuItem onClick={() => ConfirmDialog.open({
|
||||||
@ -126,7 +126,7 @@ export function DeploymentMenu(props: KubeObjectMenuProps<Deployment>) {
|
|||||||
</p>
|
</p>
|
||||||
),
|
),
|
||||||
})}>
|
})}>
|
||||||
<Icon material="autorenew" title={`Restart`} interactive={toolbar}/>
|
<Icon material="autorenew" title="Restart" interactive={toolbar}/>
|
||||||
<span className="title">Restart</span>
|
<span className="title">Restart</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -30,7 +30,11 @@ export const ContainerEnvironment = observer((props: Props) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
envFrom && envFrom.forEach(item => {
|
envFrom && envFrom.forEach(item => {
|
||||||
const { configMapRef } = item;
|
const { configMapRef, secretRef } = item;
|
||||||
|
|
||||||
|
if (secretRef && secretRef.name) {
|
||||||
|
secretsStore.load({ name: secretRef.name, namespace });
|
||||||
|
}
|
||||||
|
|
||||||
if (configMapRef && configMapRef.name) {
|
if (configMapRef && configMapRef.name) {
|
||||||
configMapsStore.load({ name: configMapRef.name, namespace });
|
configMapsStore.load({ name: configMapRef.name, namespace });
|
||||||
@ -89,21 +93,54 @@ export const ContainerEnvironment = observer((props: Props) => {
|
|||||||
|
|
||||||
const renderEnvFrom = () => {
|
const renderEnvFrom = () => {
|
||||||
const envVars = envFrom.map(vars => {
|
const envVars = envFrom.map(vars => {
|
||||||
if (!vars.configMapRef || !vars.configMapRef.name) return;
|
if (vars.configMapRef?.name) {
|
||||||
const configMap = configMapsStore.getByName(vars.configMapRef.name, namespace);
|
return renderEnvFromConfigMap(vars.configMapRef.name);
|
||||||
|
} else if (vars.secretRef?.name ) {
|
||||||
if (!configMap) return;
|
return renderEnvFromSecret(vars.secretRef.name);
|
||||||
|
}
|
||||||
return Object.entries(configMap.data).map(([name, value]) => (
|
|
||||||
<div className="variable" key={name}>
|
|
||||||
<span className="var-name">{name}</span>: {value}
|
|
||||||
</div>
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return _.flatten(envVars);
|
return _.flatten(envVars);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderEnvFromConfigMap = (configMapName: string) => {
|
||||||
|
const configMap = configMapsStore.getByName(configMapName, namespace);
|
||||||
|
|
||||||
|
if (!configMap) return;
|
||||||
|
|
||||||
|
return Object.entries(configMap.data).map(([name, value]) => (
|
||||||
|
<div className="variable" key={name}>
|
||||||
|
<span className="var-name">{name}</span>: {value}
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderEnvFromSecret = (secretName: string) => {
|
||||||
|
const secret = secretsStore.getByName(secretName, namespace);
|
||||||
|
|
||||||
|
if (!secret) return;
|
||||||
|
|
||||||
|
return Object.keys(secret.data).map(key => {
|
||||||
|
const secretKeyRef = {
|
||||||
|
name: secret.getName(),
|
||||||
|
key
|
||||||
|
};
|
||||||
|
|
||||||
|
const value = (
|
||||||
|
<SecretKey
|
||||||
|
reference={secretKeyRef}
|
||||||
|
namespace={namespace}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="variable" key={key}>
|
||||||
|
<span className="var-name">{key}</span>: {value}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DrawerItem name="Environment" className="ContainerEnvironment">
|
<DrawerItem name="Environment" className="ContainerEnvironment">
|
||||||
{env && renderEnv()}
|
{env && renderEnv()}
|
||||||
|
|||||||
@ -43,7 +43,7 @@ export class PodContainerPort extends React.Component<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("PodContainerPort", { waiting: this.waiting })}>
|
<div className={cssNames("PodContainerPort", { waiting: this.waiting })}>
|
||||||
<span title={`Open in a browser`} onClick={() => this.portForward() }>
|
<span title="Open in a browser" onClick={() => this.portForward() }>
|
||||||
{text}
|
{text}
|
||||||
{this.waiting && (
|
{this.waiting && (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
|||||||
@ -78,7 +78,7 @@ export function ReplicaSetMenu(props: KubeObjectMenuProps<ReplicaSet>) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MenuItem onClick={() => ReplicaSetScaleDialog.open(object)}>
|
<MenuItem onClick={() => ReplicaSetScaleDialog.open(object)}>
|
||||||
<Icon material="open_with" title={`Scale`} interactive={toolbar}/>
|
<Icon material="open_with" title="Scale" interactive={toolbar}/>
|
||||||
<span className="title">Scale</span>
|
<span className="title">Scale</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -83,7 +83,7 @@ export function StatefulSetMenu(props: KubeObjectMenuProps<StatefulSet>) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MenuItem onClick={() => StatefulSetScaleDialog.open(object)}>
|
<MenuItem onClick={() => StatefulSetScaleDialog.open(object)}>
|
||||||
<Icon material="open_with" title={`Scale`} interactive={toolbar}/>
|
<Icon material="open_with" title="Scale" interactive={toolbar}/>
|
||||||
<span className="title">Scale</span>
|
<span className="title">Scale</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -77,7 +77,7 @@ export class CreateResource extends React.Component<Props> {
|
|||||||
tabId={tabId}
|
tabId={tabId}
|
||||||
error={error}
|
error={error}
|
||||||
submit={create}
|
submit={create}
|
||||||
submitLabel={`Create`}
|
submitLabel="Create"
|
||||||
showNotifications={false}
|
showNotifications={false}
|
||||||
/>
|
/>
|
||||||
<EditorPanel
|
<EditorPanel
|
||||||
|
|||||||
@ -68,7 +68,7 @@ export class DockTab extends React.Component<DockTabProps> {
|
|||||||
{!pinned && (
|
{!pinned && (
|
||||||
<Icon
|
<Icon
|
||||||
small material="close"
|
small material="close"
|
||||||
title={`Close (Ctrl+W)`}
|
title="Close (Ctrl+W)"
|
||||||
onClick={prevDefault(this.close)}
|
onClick={prevDefault(this.close)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -98,8 +98,8 @@ export class EditResource extends React.Component<Props> {
|
|||||||
tabId={tabId}
|
tabId={tabId}
|
||||||
error={error}
|
error={error}
|
||||||
submit={save}
|
submit={save}
|
||||||
submitLabel={`Save`}
|
submitLabel="Save"
|
||||||
submittingMessage={`Applying..`}
|
submittingMessage="Applying.."
|
||||||
controls={(
|
controls={(
|
||||||
<div className="resource-info flex gaps align-center">
|
<div className="resource-info flex gaps align-center">
|
||||||
<span>Kind:</span> <Badge label={kind}/>
|
<span>Kind:</span> <Badge label={kind}/>
|
||||||
|
|||||||
@ -125,17 +125,17 @@ export class InstallChart extends Component<Props> {
|
|||||||
<div className="flex gaps align-center">
|
<div className="flex gaps align-center">
|
||||||
<Button
|
<Button
|
||||||
autoFocus primary
|
autoFocus primary
|
||||||
label={`View Helm Release`}
|
label="View Helm Release"
|
||||||
onClick={prevDefault(this.viewRelease)}
|
onClick={prevDefault(this.viewRelease)}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
plain active
|
plain active
|
||||||
label={`Show Notes`}
|
label="Show Notes"
|
||||||
onClick={() => this.showNotes = true}
|
onClick={() => this.showNotes = true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<LogsDialog
|
<LogsDialog
|
||||||
title={`Helm Chart Install`}
|
title="Helm Chart Install"
|
||||||
isOpen={this.showNotes}
|
isOpen={this.showNotes}
|
||||||
close={() => this.showNotes = false}
|
close={() => this.showNotes = false}
|
||||||
logs={this.releaseDetails.log}
|
logs={this.releaseDetails.log}
|
||||||
@ -148,7 +148,7 @@ export class InstallChart extends Component<Props> {
|
|||||||
const panelControls = (
|
const panelControls = (
|
||||||
<div className="install-controls flex gaps align-center">
|
<div className="install-controls flex gaps align-center">
|
||||||
<span>Chart</span>
|
<span>Chart</span>
|
||||||
<Badge label={`${repo}/${name}`} title={`Repo/Name`} />
|
<Badge label={`${repo}/${name}`} title="Repo/Name" />
|
||||||
<span>Version</span>
|
<span>Version</span>
|
||||||
<Select
|
<Select
|
||||||
className="chart-version"
|
className="chart-version"
|
||||||
@ -167,8 +167,8 @@ export class InstallChart extends Component<Props> {
|
|||||||
onChange={this.onNamespaceChange}
|
onChange={this.onNamespaceChange}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
placeholder={`Name (optional)`}
|
placeholder="Name (optional)"
|
||||||
title={`Release name`}
|
title="Release name"
|
||||||
maxLength={50}
|
maxLength={50}
|
||||||
value={releaseName}
|
value={releaseName}
|
||||||
onChange={this.onReleaseNameChange}
|
onChange={this.onReleaseNameChange}
|
||||||
@ -183,8 +183,8 @@ export class InstallChart extends Component<Props> {
|
|||||||
controls={panelControls}
|
controls={panelControls}
|
||||||
error={this.error}
|
error={this.error}
|
||||||
submit={install}
|
submit={install}
|
||||||
submitLabel={`Install`}
|
submitLabel="Install"
|
||||||
submittingMessage={`Installing...`}
|
submittingMessage="Installing..."
|
||||||
showSubmitClose={false}
|
showSubmitClose={false}
|
||||||
/>
|
/>
|
||||||
<EditorPanel
|
<EditorPanel
|
||||||
|
|||||||
@ -68,13 +68,13 @@ export const LogSearch = observer((props: Props) => {
|
|||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
material="keyboard_arrow_up"
|
material="keyboard_arrow_up"
|
||||||
tooltip={`Previous`}
|
tooltip="Previous"
|
||||||
onClick={onPrevOverlay}
|
onClick={onPrevOverlay}
|
||||||
disabled={jumpDisabled}
|
disabled={jumpDisabled}
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
material="keyboard_arrow_down"
|
material="keyboard_arrow_down"
|
||||||
tooltip={`Next`}
|
tooltip="Next"
|
||||||
onClick={onNextOverlay}
|
onClick={onNextOverlay}
|
||||||
disabled={jumpDisabled}
|
disabled={jumpDisabled}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -123,8 +123,8 @@ export class UpgradeChart extends React.Component<Props> {
|
|||||||
tabId={tabId}
|
tabId={tabId}
|
||||||
error={error}
|
error={error}
|
||||||
submit={upgrade}
|
submit={upgrade}
|
||||||
submitLabel={`Upgrade`}
|
submitLabel="Upgrade"
|
||||||
submittingMessage={`Updating..`}
|
submittingMessage="Updating.."
|
||||||
controls={controlsAndInfo}
|
controls={controlsAndInfo}
|
||||||
/>
|
/>
|
||||||
<EditorPanel
|
<EditorPanel
|
||||||
|
|||||||
@ -61,7 +61,7 @@ export class ErrorBoundary extends React.Component<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
className="box self-flex-start"
|
className="box self-flex-start"
|
||||||
primary label={`Back`}
|
primary label="Back"
|
||||||
onClick={this.back}
|
onClick={this.back}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -44,6 +44,11 @@
|
|||||||
|
|
||||||
> main {
|
> main {
|
||||||
display: contents;
|
display: contents;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
grid-area: main;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
|
|||||||
@ -106,13 +106,13 @@ export class MenuActions extends React.Component<MenuActionsProps> {
|
|||||||
{children}
|
{children}
|
||||||
{updateAction && (
|
{updateAction && (
|
||||||
<MenuItem onClick={updateAction}>
|
<MenuItem onClick={updateAction}>
|
||||||
<Icon material="edit" interactive={toolbar} title={`Edit`}/>
|
<Icon material="edit" interactive={toolbar} title="Edit"/>
|
||||||
<span className="title">Edit</span>
|
<span className="title">Edit</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{removeAction && (
|
{removeAction && (
|
||||||
<MenuItem onClick={this.remove}>
|
<MenuItem onClick={this.remove}>
|
||||||
<Icon material="delete" interactive={toolbar} title={`Delete`}/>
|
<Icon material="delete" interactive={toolbar} title="Delete"/>
|
||||||
<span className="title">Remove</span>
|
<span className="title">Remove</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -2,7 +2,17 @@
|
|||||||
|
|
||||||
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
|
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
|
||||||
|
|
||||||
## 4.1.2 (current version)
|
## 4.1.3 (current version)
|
||||||
|
|
||||||
|
- Don't reset selected namespaces to defaults in case of "All namespaces" on page reload
|
||||||
|
- Fix loading all namespaces for users with limited cluster access
|
||||||
|
- Display environment variables coming from secret in pod details
|
||||||
|
- Fix deprecated helm chart filtering
|
||||||
|
- Fix RoleBindings Namespace and Bindings field not displaying the correct data
|
||||||
|
- Fix RoleBindingDetails not rendering the name of the role binding
|
||||||
|
- Fix auto update on quit with newer version
|
||||||
|
|
||||||
|
## 4.1.2
|
||||||
|
|
||||||
**Upgrade note:** Where have all my pods gone? Namespaced Kubernetes resources are now initially shown only for the "default" namespace. Use the namespaces selector to add more.
|
**Upgrade note:** Where have all my pods gone? Namespaced Kubernetes resources are now initially shown only for the "default" namespace. Use the namespaces selector to add more.
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user