mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge remote-tracking branch 'origin/master' into add_cluster_from_custom_config
# Conflicts: # src/common/cluster-store.ts # src/migrations/cluster-store/3.6.0-beta.1.ts
This commit is contained in:
commit
01be6e449e
31 files changed
+494
-108
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import type { WorkspaceId } from "./workspace-store";
|
||||
import path from "path";
|
||||
import { app, ipcRenderer, remote } from "electron";
|
||||
import { app, remote, ipcRenderer } from "electron";
|
||||
import { unlink } from "fs-extra";
|
||||
import { action, computed, observable, toJS } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
|
||||
@@ -5,6 +5,10 @@ import { Cluster } from "../main/cluster";
|
||||
import { ClusterStore } from "./cluster-store";
|
||||
import { workspaceStore } from "./workspace-store";
|
||||
|
||||
const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png")
|
||||
|
||||
console.log("") // fix bug
|
||||
|
||||
let clusterStore: ClusterStore;
|
||||
|
||||
describe("empty config", () => {
|
||||
@@ -231,12 +235,13 @@ describe("pre 2.6.0 config with a cluster icon", () => {
|
||||
},
|
||||
cluster1: {
|
||||
kubeConfig: "foo",
|
||||
icon: "icon path",
|
||||
icon: "icon_path",
|
||||
preferences: {
|
||||
terminalCWD: "/tmp"
|
||||
}
|
||||
},
|
||||
})
|
||||
}),
|
||||
"icon_path": testDataIcon,
|
||||
}
|
||||
}
|
||||
mockFs(mockOpts);
|
||||
@@ -252,7 +257,7 @@ describe("pre 2.6.0 config with a cluster icon", () => {
|
||||
const storedClusterData = clusterStore.clustersList[0];
|
||||
expect(storedClusterData.hasOwnProperty('icon')).toBe(false);
|
||||
expect(storedClusterData.preferences.hasOwnProperty('icon')).toBe(true);
|
||||
expect(storedClusterData.preferences.icon).toBe("icon path");
|
||||
expect(storedClusterData.preferences.icon.startsWith("data:image/jpeg;base64,")).toBe(true);
|
||||
})
|
||||
})
|
||||
|
||||
@@ -269,7 +274,6 @@ describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
|
||||
},
|
||||
cluster1: {
|
||||
kubeConfig: "foo",
|
||||
icon: "icon path",
|
||||
preferences: {
|
||||
terminalCWD: "/tmp"
|
||||
}
|
||||
@@ -300,16 +304,20 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
||||
'lens-cluster-store.json': JSON.stringify({
|
||||
__internal__: {
|
||||
migrations: {
|
||||
version: "2.7.0"
|
||||
version: "3.5.0"
|
||||
}
|
||||
},
|
||||
clusters: [
|
||||
{
|
||||
id: 'cluster1',
|
||||
kubeConfig: 'kubeconfig content'
|
||||
kubeConfig: 'kubeconfig content',
|
||||
preferences: {
|
||||
icon: "store://icon_path",
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}),
|
||||
"icon_path": testDataIcon,
|
||||
}
|
||||
};
|
||||
mockFs(mockOpts);
|
||||
@@ -325,4 +333,9 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
||||
const config = clusterStore.clustersList[0].kubeConfigPath;
|
||||
expect(fs.readFileSync(config, "utf8")).toBe("kubeconfig content");
|
||||
})
|
||||
|
||||
it("migrates to modern format with icon not in file", async () => {
|
||||
const { icon } = clusterStore.clustersList[0].preferences;
|
||||
expect(icon.startsWith("data:image/jpeg;base64, ")).toBe(true);
|
||||
})
|
||||
})
|
||||
@@ -27,6 +27,7 @@ export interface ClusterState extends ClusterModel {
|
||||
online: boolean;
|
||||
disconnected: boolean;
|
||||
accessible: boolean;
|
||||
ready: boolean;
|
||||
failureReason: string;
|
||||
nodes: number;
|
||||
eventCount: number;
|
||||
@@ -47,6 +48,7 @@ export class Cluster implements ClusterModel {
|
||||
protected eventDisposers: Function[] = [];
|
||||
|
||||
whenInitialized = when(() => this.initialized);
|
||||
whenReady = when(() => this.ready);
|
||||
|
||||
@observable initialized = false;
|
||||
@observable contextName: string;
|
||||
@@ -56,6 +58,7 @@ export class Cluster implements ClusterModel {
|
||||
@observable kubeProxyUrl: string; // lens-proxy to kube-api url
|
||||
@observable online: boolean;
|
||||
@observable accessible: boolean;
|
||||
@observable ready: boolean;
|
||||
@observable disconnected: boolean;
|
||||
@observable failureReason: string;
|
||||
@observable nodes = 0;
|
||||
@@ -149,6 +152,7 @@ export class Cluster implements ClusterModel {
|
||||
this.disconnected = true;
|
||||
this.online = false;
|
||||
this.accessible = false;
|
||||
this.ready = false;
|
||||
this.pushState();
|
||||
}
|
||||
|
||||
@@ -172,6 +176,7 @@ export class Cluster implements ClusterModel {
|
||||
this.refreshEvents(),
|
||||
this.refreshAllowedResources(),
|
||||
]);
|
||||
this.ready = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,6 +375,7 @@ export class Cluster implements ClusterModel {
|
||||
initialized: this.initialized,
|
||||
apiUrl: this.apiUrl,
|
||||
online: this.online,
|
||||
ready: this.ready,
|
||||
disconnected: this.disconnected,
|
||||
accessible: this.accessible,
|
||||
failureReason: this.failureReason,
|
||||
|
||||
@@ -6,7 +6,7 @@ export default migration({
|
||||
run(store, log) {
|
||||
for (const value of store) {
|
||||
const clusterKey = value[0];
|
||||
if(clusterKey === "__internal__") continue
|
||||
if (clusterKey === "__internal__") continue
|
||||
const cluster = value[1];
|
||||
cluster.workspace = "default"
|
||||
store.set(clusterKey, cluster)
|
||||
|
||||
@@ -1,32 +1,71 @@
|
||||
// Move embedded kubeconfig into separate file and add reference to it to cluster settings
|
||||
// convert file path cluster icons to their base64 encoded versions
|
||||
|
||||
import path from "path"
|
||||
import { app, remote } from "electron"
|
||||
import { migration } from "../migration-wrapper";
|
||||
import { ensureDirSync } from "fs-extra"
|
||||
import fse from "fs-extra"
|
||||
import { ClusterModel, ClusterStore } from "../../common/cluster-store";
|
||||
import { loadConfig } from "../../common/kube-helpers";
|
||||
import makeSynchronous from "make-synchronous"
|
||||
|
||||
const AsyncFunction = Object.getPrototypeOf(async function () { return }).constructor;
|
||||
const getFileTypeFnString = `return require("file-type").fromBuffer(fileData)`;
|
||||
const getFileType = new AsyncFunction("fileData", getFileTypeFnString);
|
||||
|
||||
export default migration({
|
||||
version: "3.6.0-beta.1",
|
||||
run(store, printLog) {
|
||||
const migratedClusters: ClusterModel[] = []
|
||||
const storedClusters: ClusterModel[] = store.get("clusters");
|
||||
const userDataPath = (app || remote.app).getPath("userData")
|
||||
const kubeConfigBase = ClusterStore.getCustomKubeConfigPath("");
|
||||
const storedClusters: ClusterModel[] = store.get("clusters") || [];
|
||||
|
||||
if (!storedClusters) return;
|
||||
ensureDirSync(kubeConfigBase);
|
||||
if (!storedClusters.length) return;
|
||||
fse.ensureDirSync(kubeConfigBase);
|
||||
|
||||
printLog("Number of clusters to migrate: ", storedClusters.length)
|
||||
for (const cluster of storedClusters) {
|
||||
try {
|
||||
// take the embedded kubeconfig and dump it into a file]
|
||||
cluster.kubeConfigPath = ClusterStore.embedCustomKubeConfig(cluster.id, cluster.kubeConfig);
|
||||
cluster.contextName = loadConfig(cluster.kubeConfigPath).getCurrentContext();
|
||||
delete cluster.kubeConfig;
|
||||
migratedClusters.push(cluster)
|
||||
} catch (error) {
|
||||
printLog(`Failed to migrate Kubeconfig for cluster "${cluster.id}"`, error)
|
||||
}
|
||||
}
|
||||
const migratedClusters = storedClusters
|
||||
.map(cluster => {
|
||||
/**
|
||||
* migrate kubeconfig
|
||||
*/
|
||||
try {
|
||||
// take the embedded kubeconfig and dump it into a file
|
||||
cluster.kubeConfigPath = ClusterStore.embedCustomKubeConfig(cluster.id, cluster.kubeConfig);
|
||||
cluster.contextName = loadConfig(cluster.kubeConfigPath).getCurrentContext();
|
||||
delete cluster.kubeConfig;
|
||||
|
||||
} catch (error) {
|
||||
printLog(`Failed to migrate Kubeconfig for cluster "${cluster.id}", removing cluster...`, error)
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* migrate cluster icon
|
||||
*/
|
||||
try {
|
||||
if (cluster.preferences?.icon) {
|
||||
printLog(`migrating ${cluster.preferences.icon} for ${cluster.preferences.clusterName}`)
|
||||
const iconPath = cluster.preferences.icon.replace("store://", "")
|
||||
const fileData = fse.readFileSync(path.join(userDataPath, iconPath));
|
||||
const { mime = "" } = makeSynchronous(getFileType)(fileData);
|
||||
|
||||
if (!mime) {
|
||||
printLog(`mime type not detected for ${cluster.preferences.clusterName}'s icon: ${iconPath}`)
|
||||
}
|
||||
|
||||
cluster.preferences.icon = `data:${mime};base64, ${fileData.toString('base64')}`;
|
||||
} else {
|
||||
delete cluster.preferences?.icon;
|
||||
}
|
||||
} catch (error) {
|
||||
printLog(`Failed to migrate cluster icon for cluster "${cluster.id}"`, error)
|
||||
delete cluster.preferences.icon;
|
||||
}
|
||||
|
||||
return cluster;
|
||||
})
|
||||
.filter(c => c);
|
||||
|
||||
// "overwrite" the cluster configs
|
||||
if (migratedClusters.length > 0) {
|
||||
|
||||
@@ -32,6 +32,12 @@ export class CronJob extends KubeObject {
|
||||
jobTemplate: {
|
||||
metadata: {
|
||||
creationTimestamp?: string;
|
||||
labels?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
annotations?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
spec: {
|
||||
template: {
|
||||
@@ -53,7 +59,7 @@ export class CronJob extends KubeObject {
|
||||
failedJobsHistoryLimit: number;
|
||||
}
|
||||
status: {
|
||||
lastScheduleTime: string;
|
||||
lastScheduleTime?: string;
|
||||
}
|
||||
|
||||
getSuspendFlag() {
|
||||
@@ -61,6 +67,7 @@ export class CronJob extends KubeObject {
|
||||
}
|
||||
|
||||
getLastScheduleTime() {
|
||||
if (!this.status.lastScheduleTime) return "-"
|
||||
const diff = moment().diff(this.status.lastScheduleTime)
|
||||
return formatDuration(diff, true)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export class Job extends WorkloadKubeObject {
|
||||
parallelism?: number;
|
||||
completions?: number;
|
||||
backoffLimit?: number;
|
||||
selector: {
|
||||
selector?: {
|
||||
matchLabels: {
|
||||
[name: string]: string;
|
||||
};
|
||||
@@ -21,8 +21,11 @@ export class Job extends WorkloadKubeObject {
|
||||
template: {
|
||||
metadata: {
|
||||
creationTimestamp?: string;
|
||||
labels: {
|
||||
name: string;
|
||||
labels?: {
|
||||
[name: string]: string;
|
||||
};
|
||||
annotations?: {
|
||||
[name: string]: string;
|
||||
};
|
||||
};
|
||||
spec: {
|
||||
@@ -35,7 +38,7 @@ export class Job extends WorkloadKubeObject {
|
||||
nodeSelector?: {
|
||||
[selector: string]: string;
|
||||
};
|
||||
tolerations: {
|
||||
tolerations?: {
|
||||
key: string;
|
||||
operator: string;
|
||||
effect: string;
|
||||
|
||||
@@ -28,7 +28,7 @@ export class ClusterIconSetting extends React.Component<Props> {
|
||||
try {
|
||||
if (file) {
|
||||
const buf = Buffer.from(await file.arrayBuffer());
|
||||
cluster.preferences.icon = `data:image/jpeg;base64, ${buf.toString('base64')}`;
|
||||
cluster.preferences.icon = `data:${file.type};base64, ${buf.toString('base64')}`;
|
||||
} else {
|
||||
// this has to be done as a seperate branch (and not always) because `cluster`
|
||||
// is observable and triggers an update loop.
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
.LandingPage {
|
||||
height: 100%;
|
||||
background: #282b2f url(../../components/icon/crane.svg) no-repeat;
|
||||
background-position: 0 35%;
|
||||
background-size: 85%;
|
||||
background-clip: content-box;
|
||||
text-align: center;
|
||||
z-index: 0;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
background: url(../../components/icon/crane.svg) no-repeat;
|
||||
background-position: 0 35%;
|
||||
background-size: 85%;
|
||||
background-clip: content-box;
|
||||
opacity: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
||||
.theme-light & {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
.CronJobTriggerDialog {
|
||||
.Wizard {
|
||||
.header {
|
||||
span {
|
||||
color: #a0a0a0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.WizardStep {
|
||||
.step-content {
|
||||
min-height: 90px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import "./cronjob-trigger-dialog.scss";
|
||||
|
||||
import React, { Component } from "react";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { Dialog, DialogProps } from "../dialog";
|
||||
import { Wizard, WizardStep } from "../wizard";
|
||||
import { CronJob, cronJobApi, jobApi, Job } from "../../api/endpoints";
|
||||
import { Notifications } from "../notifications";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Input } from "../input";
|
||||
import { systemName, maxLength } from "../input/input.validators";
|
||||
|
||||
interface Props extends Partial<DialogProps> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CronJobTriggerDialog extends Component<Props> {
|
||||
@observable static isOpen = false;
|
||||
@observable static data: CronJob = null;
|
||||
|
||||
@observable jobName = "";
|
||||
|
||||
@observable ready = false;
|
||||
|
||||
static open(cronjob: CronJob) {
|
||||
CronJobTriggerDialog.isOpen = true;
|
||||
CronJobTriggerDialog.data = cronjob;
|
||||
}
|
||||
|
||||
static close() {
|
||||
CronJobTriggerDialog.isOpen = false;
|
||||
}
|
||||
|
||||
get cronjob() {
|
||||
return CronJobTriggerDialog.data;
|
||||
}
|
||||
|
||||
close = () => {
|
||||
CronJobTriggerDialog.close();
|
||||
}
|
||||
|
||||
onOpen = async () => {
|
||||
const { cronjob } = this;
|
||||
this.jobName = cronjob ? cronjob.getName() + "-manual-" + Math.random().toString(36).slice(2, 7) : "";
|
||||
this.jobName = this.jobName.slice(0, 63);
|
||||
this.ready = true;
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
this.ready = false;
|
||||
}
|
||||
|
||||
trigger = async () => {
|
||||
const { cronjob } = this;
|
||||
const { close } = this;
|
||||
try {
|
||||
const cronjobDefinition = await cronJobApi.get({
|
||||
name: cronjob.getName(),
|
||||
namespace: cronjob.getNs()
|
||||
});
|
||||
|
||||
await jobApi.create({
|
||||
name: this.jobName,
|
||||
namespace: cronjob.getNs()
|
||||
}, {
|
||||
spec: cronjobDefinition.spec.jobTemplate.spec
|
||||
});
|
||||
|
||||
close();
|
||||
} catch (err) {
|
||||
Notifications.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
renderContents() {
|
||||
return (
|
||||
<>
|
||||
<div className="flex gaps">
|
||||
<Trans>Job name</Trans>:
|
||||
</div>
|
||||
<div className="flex gaps">
|
||||
<Input
|
||||
required autoFocus
|
||||
placeholder={this.jobName}
|
||||
validators={[systemName, maxLength]}
|
||||
maxLength={63}
|
||||
value={this.jobName} onChange={v => this.jobName = v.toLowerCase()}
|
||||
className="box grow"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, ...dialogProps } = this.props;
|
||||
const cronjobName = this.cronjob ? this.cronjob.getName() : "";
|
||||
const header = (
|
||||
<h5>
|
||||
<Trans>Trigger CronJob <span>{cronjobName}</span></Trans>
|
||||
</h5>
|
||||
);
|
||||
return (
|
||||
<Dialog
|
||||
{...dialogProps}
|
||||
isOpen={CronJobTriggerDialog.isOpen}
|
||||
className={cssNames("CronJobTriggerDialog", className)}
|
||||
onOpen={this.onOpen}
|
||||
onClose={this.onClose}
|
||||
close={this.close}
|
||||
>
|
||||
<Wizard header={header} done={this.close}>
|
||||
<WizardStep
|
||||
contentClass="flex gaps column"
|
||||
next={this.trigger}
|
||||
nextLabel={<Trans>Trigger</Trans>}
|
||||
disabledNext={!this.ready}
|
||||
>
|
||||
{this.renderContents()}
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,13 @@ export class CronJobStore extends KubeObjectStore<CronJob> {
|
||||
api = cronJobApi
|
||||
|
||||
getStatuses(cronJobs?: CronJob[]) {
|
||||
const status = { failed: 0, running: 0 }
|
||||
const status = { suspended: 0, scheduled: 0 }
|
||||
cronJobs.forEach(cronJob => {
|
||||
if (cronJob.spec.suspend) {
|
||||
status.failed++
|
||||
status.suspended++
|
||||
}
|
||||
else {
|
||||
status.running++
|
||||
status.scheduled++
|
||||
}
|
||||
})
|
||||
return status
|
||||
|
||||
@@ -3,8 +3,10 @@ import "./cronjobs.scss";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import { CronJob, cronJobApi } from "../../api/endpoints/cron-job.api";
|
||||
import { MenuItem } from "../menu";
|
||||
import { Icon } from "../icon";
|
||||
import { cronJobStore } from "./cronjob.store";
|
||||
import { jobStore } from "../+workloads-jobs/job.store";
|
||||
import { eventStore } from "../+events/event.store";
|
||||
@@ -12,7 +14,9 @@ import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-
|
||||
import { ICronJobsRouteParams } from "../+workloads";
|
||||
import { KubeObjectListLayout } from "../kube-object";
|
||||
import { KubeEventIcon } from "../+events/kube-event-icon";
|
||||
import { _i18n } from "../../i18n";
|
||||
import { apiManager } from "../../api/api-manager";
|
||||
import { CronJobTriggerDialog } from "./cronjob-trigger-dialog";
|
||||
|
||||
enum sortBy {
|
||||
name = "name",
|
||||
@@ -79,8 +83,14 @@ export class CronJobs extends React.Component<Props> {
|
||||
}
|
||||
|
||||
export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) {
|
||||
const { object, toolbar } = props;
|
||||
return (
|
||||
<KubeObjectMenu {...props}/>
|
||||
<KubeObjectMenu {...props}>
|
||||
<MenuItem onClick={() => CronJobTriggerDialog.open(object)}>
|
||||
<Icon material="play_circle_filled" title={_i18n._(t`Trigger`)} interactive={toolbar}/>
|
||||
<span className="title"><Trans>Trigger</Trans></span>
|
||||
</MenuItem>
|
||||
</KubeObjectMenu>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
--workload-status-pending: #{$pod-status-pending-color};
|
||||
--workload-status-evicted: #{$pod-status-evicted-color};
|
||||
--workload-status-succeeded: #{$pod-status-succeeded-color};
|
||||
--workload-status-scheduled: #{$cronjob-scheduled};
|
||||
--workload-status-suspended: #{$cronjob-suspended};
|
||||
--workload-status-failed: #{$pod-status-failed-color};
|
||||
--workload-status-terminated: #{$pod-status-terminated-color};
|
||||
--workload-status-unknown: #{$pod-status-unknown-color};
|
||||
|
||||
@@ -26,6 +26,10 @@ $deployment-replicafailure: $colorError;
|
||||
$job-complete: $colorSuccess;
|
||||
$job-failed: $colorError;
|
||||
|
||||
// Cronjob
|
||||
$cronjob-scheduled: $colorSuccess;
|
||||
$cronjob-suspended: $colorTerminated;
|
||||
|
||||
// Pod Statuses
|
||||
$pod-status-color-list: (
|
||||
running: $pod-status-running-color,
|
||||
@@ -48,6 +52,12 @@ $job-condition-color-list: (
|
||||
failed: $job-failed,
|
||||
);
|
||||
|
||||
// Cronjob Conditions
|
||||
$cronjob-condition-color-list: (
|
||||
scheduled: $cronjob-scheduled,
|
||||
suspended: $cronjob-suspended,
|
||||
);
|
||||
|
||||
@mixin pod-status-bgs {
|
||||
@each $status, $color in $pod-status-color-list {
|
||||
&.#{$status} {
|
||||
|
||||
@@ -25,6 +25,7 @@ import { KubeObjectDetails } from "./kube-object/kube-object-details";
|
||||
import { AddRoleBindingDialog } from "./+user-management-roles-bindings";
|
||||
import { PodLogsDialog } from "./+workloads-pods/pod-logs-dialog";
|
||||
import { DeploymentScaleDialog } from "./+workloads-deployments/deployment-scale-dialog";
|
||||
import { CronJobTriggerDialog } from "./+workloads-cronjobs/cronjob-trigger-dialog";
|
||||
import { CustomResources } from "./+custom-resources/custom-resources";
|
||||
import { crdRoute } from "./+custom-resources";
|
||||
import { isAllowedResource } from "../../common/rbac";
|
||||
@@ -80,6 +81,7 @@ export class App extends React.Component {
|
||||
<AddRoleBindingDialog/>
|
||||
<PodLogsDialog/>
|
||||
<DeploymentScaleDialog/>
|
||||
<CronJobTriggerDialog/>
|
||||
</ErrorBoundary>
|
||||
</Router>
|
||||
</I18nProvider>
|
||||
|
||||
@@ -63,14 +63,14 @@ export class Chart extends React.Component<ChartProps> {
|
||||
this.renderChart()
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: ChartProps) {
|
||||
const { data, showChart, redraw } = this.props
|
||||
componentDidUpdate() {
|
||||
const { showChart, redraw } = this.props
|
||||
if (redraw) {
|
||||
this.chart.destroy()
|
||||
this.renderChart()
|
||||
return
|
||||
}
|
||||
if (!isEqual(prevProps.data, data) && showChart) {
|
||||
if (showChart) {
|
||||
if (!this.chart) this.renderChart()
|
||||
else this.updateChart()
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: -$padding * 1.5;
|
||||
margin: -$padding;
|
||||
font-size: $font-size-small;
|
||||
background: $colorError;
|
||||
color: white;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
font-size: $font-size-small;
|
||||
background-color: #3d90ce;
|
||||
padding: $spacing $padding;
|
||||
padding: $padding / 4 $padding;
|
||||
color: white;
|
||||
|
||||
#current-workspace {
|
||||
|
||||
@@ -11,7 +11,7 @@ export class BottomBar extends React.Component {
|
||||
const { currentWorkspace } = workspaceStore;
|
||||
return (
|
||||
<div className="BottomBar flex gaps">
|
||||
<div id="current-workspace" className="flex gaps align-center box right">
|
||||
<div id="current-workspace" className="flex gaps align-center box">
|
||||
<Icon small material="layers"/>
|
||||
<span className="workspace-name">{currentWorkspace.name}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
.ClustersMenu {
|
||||
@include hidden-scrollbar;
|
||||
$spacing: $padding * 2;
|
||||
|
||||
position: relative;
|
||||
text-align: center;
|
||||
padding: $spacing;
|
||||
background: $clusterMenuBackground;
|
||||
border-right: 1px solid $clusterMenuBorderColor;
|
||||
padding-bottom: $spacing;
|
||||
|
||||
.is-mac & {
|
||||
padding-top: $spacing * 2;
|
||||
@@ -23,8 +22,7 @@
|
||||
padding: $spacing;
|
||||
width: 320px;
|
||||
background: $bgc;
|
||||
z-index: 100;
|
||||
color: white;
|
||||
color: $textColorAccent;
|
||||
filter: drop-shadow(0 0px 2px #ffffff33);
|
||||
pointer-events: none;
|
||||
|
||||
@@ -38,11 +36,24 @@
|
||||
border-right: $arrowSize solid $bgc;
|
||||
right: 100%;
|
||||
}
|
||||
|
||||
.theme-light & {
|
||||
filter: drop-shadow(0 0px 2px #777);
|
||||
background: white;
|
||||
|
||||
&:before {
|
||||
border-right-color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.clusters {
|
||||
@include hidden-scrollbar;
|
||||
padding: 0 $spacing $spacing;
|
||||
}
|
||||
|
||||
> .add-cluster {
|
||||
position: relative;
|
||||
margin-top: $padding;
|
||||
min-width: 43px;
|
||||
|
||||
.Icon {
|
||||
|
||||
@@ -99,7 +99,7 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
const showStartupHint = this.showHint && isLanding && noClustersInScope; // fixme: broken, move to landing.tsx
|
||||
return (
|
||||
<div
|
||||
className={cssNames("ClustersMenu flex column gaps", className)}
|
||||
className={cssNames("ClustersMenu flex column", className)}
|
||||
onMouseEnter={() => this.showHint = false}
|
||||
>
|
||||
{showStartupHint && (
|
||||
@@ -112,18 +112,20 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{clusters.map(cluster => {
|
||||
return (
|
||||
<ClusterIcon
|
||||
key={cluster.id}
|
||||
showErrors={true}
|
||||
cluster={cluster}
|
||||
isActive={cluster.id === getMatchedClusterId()}
|
||||
onClick={() => this.showCluster(cluster.id)}
|
||||
onContextMenu={() => this.showContextMenu(cluster)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<div className="clusters flex column gaps">
|
||||
{clusters.map(cluster => {
|
||||
return (
|
||||
<ClusterIcon
|
||||
key={cluster.id}
|
||||
showErrors={true}
|
||||
cluster={cluster}
|
||||
isActive={cluster.id === getMatchedClusterId()}
|
||||
onClick={() => this.showCluster(cluster.id)}
|
||||
onContextMenu={() => this.showContextMenu(cluster)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="add-cluster" onClick={this.addCluster}>
|
||||
<Tooltip targetId="add-cluster-icon">
|
||||
<Trans>Add Cluster</Trans>
|
||||
|
||||
@@ -21,10 +21,10 @@ export async function initView(clusterId: ClusterId) {
|
||||
}
|
||||
logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`)
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
await cluster.whenInitialized;
|
||||
await cluster.whenReady;
|
||||
const parentElem = document.getElementById("lens-views");
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.name = cluster.preferences.clusterName;
|
||||
iframe.name = cluster.contextName;
|
||||
iframe.setAttribute("src", `//${clusterId}.${location.host}`)
|
||||
iframe.addEventListener("load", async () => {
|
||||
logger.info(`[LENS-VIEW]: loaded from ${iframe.src}`)
|
||||
|
||||
Reference in new issue
Block a user