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

Rework extension API into parts (#2879)

* `Main`, `Common`, and `Renderer`
* move registry types to Common.Types

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2021-05-27 14:56:33 -04:00 committed by GitHub
parent db54a658a2
commit df1f6e128e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 227 additions and 161 deletions

View File

@ -0,0 +1,25 @@
{
"name": "kube-object-event-status",
"version": "0.1.0",
"description": "Adds kube object status from events",
"renderer": "dist/renderer.js",
"lens": {
"metadata": {},
"styles": []
},
"scripts": {
"build": "webpack && npm pack",
"dev": "webpack --watch",
"test": "echo NO TESTS"
},
"files": [
"dist/**/*"
],
"dependencies": {},
"devDependencies": {
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"ts-loader": "^8.0.4",
"typescript": "^4.0.3",
"webpack": "^4.44.2"
}
}

View File

@ -19,45 +19,45 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { LensRendererExtension, K8sApi } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
import { resolveStatus, resolveStatusForCronJobs, resolveStatusForPods } from "./src/resolver"; import { resolveStatus, resolveStatusForCronJobs, resolveStatusForPods } from "./src/resolver";
export default class EventResourceStatusRendererExtension extends LensRendererExtension { export default class EventResourceStatusRendererExtension extends Renderer.LensExtension {
kubeObjectStatusTexts = [ kubeObjectStatusTexts = [
{ {
kind: "Pod", kind: "Pod",
apiVersions: ["v1"], apiVersions: ["v1"],
resolve: (pod: K8sApi.Pod) => resolveStatusForPods(pod) resolve: (pod: Renderer.K8sApi.Pod) => resolveStatusForPods(pod)
}, },
{ {
kind: "ReplicaSet", kind: "ReplicaSet",
apiVersions: ["v1"], apiVersions: ["v1"],
resolve: (replicaSet: K8sApi.ReplicaSet) => resolveStatus(replicaSet) resolve: (replicaSet: Renderer.K8sApi.ReplicaSet) => resolveStatus(replicaSet)
}, },
{ {
kind: "Deployment", kind: "Deployment",
apiVersions: ["apps/v1"], apiVersions: ["apps/v1"],
resolve: (deployment: K8sApi.Deployment) => resolveStatus(deployment) resolve: (deployment: Renderer.K8sApi.Deployment) => resolveStatus(deployment)
}, },
{ {
kind: "StatefulSet", kind: "StatefulSet",
apiVersions: ["apps/v1"], apiVersions: ["apps/v1"],
resolve: (statefulSet: K8sApi.StatefulSet) => resolveStatus(statefulSet) resolve: (statefulSet: Renderer.K8sApi.StatefulSet) => resolveStatus(statefulSet)
}, },
{ {
kind: "DaemonSet", kind: "DaemonSet",
apiVersions: ["apps/v1"], apiVersions: ["apps/v1"],
resolve: (daemonSet: K8sApi.DaemonSet) => resolveStatus(daemonSet) resolve: (daemonSet: Renderer.K8sApi.DaemonSet) => resolveStatus(daemonSet)
}, },
{ {
kind: "Job", kind: "Job",
apiVersions: ["batch/v1"], apiVersions: ["batch/v1"],
resolve: (job: K8sApi.Job) => resolveStatus(job) resolve: (job: Renderer.K8sApi.Job) => resolveStatus(job)
}, },
{ {
kind: "CronJob", kind: "CronJob",
apiVersions: ["batch/v1"], apiVersions: ["batch/v1"],
resolve: (cronJob: K8sApi.CronJob) => resolveStatusForCronJobs(cronJob) resolve: (cronJob: Renderer.K8sApi.CronJob) => resolveStatusForCronJobs(cronJob)
}, },
]; ];
} }

View File

@ -19,11 +19,19 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { K8sApi } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
export function resolveStatus(object: K8sApi.KubeObject): K8sApi.KubeObjectStatus { const { apiManager, eventApi, KubeObjectStatusLevel } = Renderer.K8sApi;
const eventStore = K8sApi.apiManager.getStore(K8sApi.eventApi);
const events = (eventStore as K8sApi.EventStore).getEventsByObject(object); type KubeObject = Renderer.K8sApi.KubeObject;
type Pod = Renderer.K8sApi.Pod;
type CronJob = Renderer.K8sApi.CronJob;
type KubeObjectStatus = Renderer.K8sApi.KubeObjectStatus;
type EventStore = Renderer.K8sApi.EventStore;
export function resolveStatus(object: KubeObject): KubeObjectStatus {
const eventStore = apiManager.getStore(eventApi);
const events = (eventStore as EventStore).getEventsByObject(object);
const warnings = events.filter(evt => evt.isWarning()); const warnings = events.filter(evt => evt.isWarning());
if (!events.length || !warnings.length) { if (!events.length || !warnings.length) {
@ -32,18 +40,18 @@ export function resolveStatus(object: K8sApi.KubeObject): K8sApi.KubeObjectStatu
const event = [...warnings, ...events][0]; // get latest event const event = [...warnings, ...events][0]; // get latest event
return { return {
level: K8sApi.KubeObjectStatusLevel.WARNING, level: KubeObjectStatusLevel.WARNING,
text: `${event.message}`, text: `${event.message}`,
timestamp: event.metadata.creationTimestamp timestamp: event.metadata.creationTimestamp
}; };
} }
export function resolveStatusForPods(pod: K8sApi.Pod): K8sApi.KubeObjectStatus { export function resolveStatusForPods(pod: Pod): KubeObjectStatus {
if (!pod.hasIssues()) { if (!pod.hasIssues()) {
return null; return null;
} }
const eventStore = K8sApi.apiManager.getStore(K8sApi.eventApi); const eventStore = apiManager.getStore(eventApi);
const events = (eventStore as K8sApi.EventStore).getEventsByObject(pod); const events = (eventStore as EventStore).getEventsByObject(pod);
const warnings = events.filter(evt => evt.isWarning()); const warnings = events.filter(evt => evt.isWarning());
if (!events.length || !warnings.length) { if (!events.length || !warnings.length) {
@ -52,15 +60,15 @@ export function resolveStatusForPods(pod: K8sApi.Pod): K8sApi.KubeObjectStatus {
const event = [...warnings, ...events][0]; // get latest event const event = [...warnings, ...events][0]; // get latest event
return { return {
level: K8sApi.KubeObjectStatusLevel.WARNING, level: KubeObjectStatusLevel.WARNING,
text: `${event.message}`, text: `${event.message}`,
timestamp: event.metadata.creationTimestamp timestamp: event.metadata.creationTimestamp
}; };
} }
export function resolveStatusForCronJobs(cronJob: K8sApi.CronJob): K8sApi.KubeObjectStatus { export function resolveStatusForCronJobs(cronJob: CronJob): KubeObjectStatus {
const eventStore = K8sApi.apiManager.getStore(K8sApi.eventApi); const eventStore = apiManager.getStore(eventApi);
let events = (eventStore as K8sApi.EventStore).getEventsByObject(cronJob); let events = (eventStore as EventStore).getEventsByObject(cronJob);
const warnings = events.filter(evt => evt.isWarning()); const warnings = events.filter(evt => evt.isWarning());
if (cronJob.isNeverRun()) { if (cronJob.isNeverRun()) {
@ -73,7 +81,7 @@ export function resolveStatusForCronJobs(cronJob: K8sApi.CronJob): K8sApi.KubeOb
const event = [...warnings, ...events][0]; // get latest event const event = [...warnings, ...events][0]; // get latest event
return { return {
level: K8sApi.KubeObjectStatusLevel.WARNING, level: KubeObjectStatusLevel.WARNING,
text: `${event.message}`, text: `${event.message}`,
timestamp: event.metadata.creationTimestamp timestamp: event.metadata.creationTimestamp
}; };

View File

@ -20,10 +20,10 @@
*/ */
import React from "react"; import React from "react";
import { LensRendererExtension, Catalog } from "@k8slens/extensions"; import { Common, Renderer } from "@k8slens/extensions";
import { MetricsSettings } from "./src/metrics-settings"; import { MetricsSettings } from "./src/metrics-settings";
export default class ClusterMetricsFeatureExtension extends LensRendererExtension { export default class ClusterMetricsFeatureExtension extends Renderer.LensExtension {
entitySettings = [ entitySettings = [
{ {
apiVersions: ["entity.k8slens.dev/v1alpha1"], apiVersions: ["entity.k8slens.dev/v1alpha1"],
@ -31,7 +31,7 @@ export default class ClusterMetricsFeatureExtension extends LensRendererExtensio
title: "Lens Metrics", title: "Lens Metrics",
priority: 5, priority: 5,
components: { components: {
View: ({ entity = null }: { entity: Catalog.KubernetesCluster}) => { View: ({ entity = null }: { entity: Common.Catalog.KubernetesCluster}) => {
return ( return (
<MetricsSettings cluster={entity} /> <MetricsSettings cluster={entity} />
); );

View File

@ -19,10 +19,14 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { Catalog, K8sApi } from "@k8slens/extensions"; import { Renderer, Common } from "@k8slens/extensions";
import semver from "semver"; import semver from "semver";
import * as path from "path"; import * as path from "path";
const { ResourceStack, forCluster, StorageClass, Namespace } = Renderer.K8sApi;
type ResourceStack = Renderer.K8sApi.ResourceStack;
export interface MetricsConfiguration { export interface MetricsConfiguration {
// Placeholder for Metrics config structure // Placeholder for Metrics config structure
prometheus: { prometheus: {
@ -58,10 +62,10 @@ export class MetricsFeature {
name = "lens-metrics"; name = "lens-metrics";
latestVersion = "v2.26.0-lens1"; latestVersion = "v2.26.0-lens1";
protected stack: K8sApi.ResourceStack; protected stack: ResourceStack;
constructor(protected cluster: Catalog.KubernetesCluster) { constructor(protected cluster: Common.Catalog.KubernetesCluster) {
this.stack = new K8sApi.ResourceStack(cluster, this.name); this.stack = new ResourceStack(cluster, this.name);
} }
get resourceFolder() { get resourceFolder() {
@ -70,7 +74,7 @@ export class MetricsFeature {
async install(config: MetricsConfiguration): Promise<string> { async install(config: MetricsConfiguration): Promise<string> {
// Check if there are storageclasses // Check if there are storageclasses
const storageClassApi = K8sApi.forCluster(this.cluster, K8sApi.StorageClass); const storageClassApi = forCluster(this.cluster, StorageClass);
const scs = await storageClassApi.list(); const scs = await storageClassApi.list();
config.persistence.enabled = scs.some(sc => ( config.persistence.enabled = scs.some(sc => (
@ -91,7 +95,7 @@ export class MetricsFeature {
const status: MetricsStatus = { installed: false, canUpgrade: false}; const status: MetricsStatus = { installed: false, canUpgrade: false};
try { try {
const namespaceApi = K8sApi.forCluster(this.cluster, K8sApi.Namespace); const namespaceApi = forCluster(this.cluster, Namespace);
const namespace = await namespaceApi.get({name: "lens-metrics"}); const namespace = await namespaceApi.get({name: "lens-metrics"});
if (namespace?.kind) { if (namespace?.kind) {

View File

@ -19,13 +19,22 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import React from "react"; import React from "react";
import { Component, Catalog, K8sApi } from "@k8slens/extensions"; import { Common, Renderer } from "@k8slens/extensions";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { computed, observable, makeObservable } from "mobx"; import { computed, observable, makeObservable } from "mobx";
import { MetricsFeature, MetricsConfiguration } from "./metrics-feature"; import { MetricsFeature, MetricsConfiguration } from "./metrics-feature";
const {
K8sApi: {
forCluster, StatefulSet, DaemonSet, Deployment,
},
Component: {
SubTitle, FormSwitch, Switcher, Button,
}
} = Renderer;
interface Props { interface Props {
cluster: Catalog.KubernetesCluster; cluster: Common.Catalog.KubernetesCluster;
} }
@observer @observer
@ -102,7 +111,7 @@ export class MetricsSettings extends React.Component<Props> {
this.changed = true; this.changed = true;
} }
const statefulSet = K8sApi.forCluster(this.props.cluster, K8sApi.StatefulSet); const statefulSet = forCluster(this.props.cluster, StatefulSet);
try { try {
await statefulSet.get({name: "prometheus", namespace: "lens-metrics"}); await statefulSet.get({name: "prometheus", namespace: "lens-metrics"});
@ -115,7 +124,7 @@ export class MetricsSettings extends React.Component<Props> {
} }
} }
const deployment = K8sApi.forCluster(this.props.cluster, K8sApi.Deployment); const deployment = forCluster(this.props.cluster, Deployment);
try { try {
await deployment.get({name: "kube-state-metrics", namespace: "lens-metrics"}); await deployment.get({name: "kube-state-metrics", namespace: "lens-metrics"});
@ -128,7 +137,7 @@ export class MetricsSettings extends React.Component<Props> {
} }
} }
const daemonSet = K8sApi.forCluster(this.props.cluster, K8sApi.DaemonSet); const daemonSet = forCluster(this.props.cluster, DaemonSet);
try { try {
await daemonSet.get({name: "node-exporter", namespace: "lens-metrics"}); await daemonSet.get({name: "node-exporter", namespace: "lens-metrics"});
@ -211,10 +220,10 @@ export class MetricsSettings extends React.Component<Props> {
</section> </section>
)} )}
<section> <section>
<Component.SubTitle title="Prometheus" /> <SubTitle title="Prometheus" />
<Component.FormSwitch <FormSwitch
control={ control={
<Component.Switcher <Switcher
disabled={this.featureStates.kubeStateMetrics === undefined || !this.isTogglable} disabled={this.featureStates.kubeStateMetrics === undefined || !this.isTogglable}
checked={!!this.featureStates.prometheus && this.props.cluster.status.active} checked={!!this.featureStates.prometheus && this.props.cluster.status.active}
onChange={v => this.togglePrometheus(v.target.checked)} onChange={v => this.togglePrometheus(v.target.checked)}
@ -229,10 +238,10 @@ export class MetricsSettings extends React.Component<Props> {
</section> </section>
<section> <section>
<Component.SubTitle title="Kube State Metrics" /> <SubTitle title="Kube State Metrics" />
<Component.FormSwitch <FormSwitch
control={ control={
<Component.Switcher <Switcher
disabled={this.featureStates.kubeStateMetrics === undefined || !this.isTogglable} disabled={this.featureStates.kubeStateMetrics === undefined || !this.isTogglable}
checked={!!this.featureStates.kubeStateMetrics && this.props.cluster.status.active} checked={!!this.featureStates.kubeStateMetrics && this.props.cluster.status.active}
onChange={v => this.toggleKubeStateMetrics(v.target.checked)} onChange={v => this.toggleKubeStateMetrics(v.target.checked)}
@ -248,10 +257,10 @@ export class MetricsSettings extends React.Component<Props> {
</section> </section>
<section> <section>
<Component.SubTitle title="Node Exporter" /> <SubTitle title="Node Exporter" />
<Component.FormSwitch <FormSwitch
control={ control={
<Component.Switcher <Switcher
disabled={this.featureStates.nodeExporter === undefined || !this.isTogglable} disabled={this.featureStates.nodeExporter === undefined || !this.isTogglable}
checked={!!this.featureStates.nodeExporter && this.props.cluster.status.active} checked={!!this.featureStates.nodeExporter && this.props.cluster.status.active}
onChange={v => this.toggleNodeExporter(v.target.checked)} onChange={v => this.toggleNodeExporter(v.target.checked)}
@ -267,7 +276,7 @@ export class MetricsSettings extends React.Component<Props> {
</section> </section>
<section> <section>
<Component.Button <Button
label={this.buttonLabel} label={this.buttonLabel}
waiting={this.inProgress} waiting={this.inProgress}
onClick={() => this.save()} onClick={() => this.save()}

View File

@ -19,11 +19,11 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { LensRendererExtension } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
import React from "react"; import React from "react";
import { NodeMenu, NodeMenuProps } from "./src/node-menu"; import { NodeMenu, NodeMenuProps } from "./src/node-menu";
export default class NodeMenuRendererExtension extends LensRendererExtension { export default class NodeMenuRendererExtension extends Renderer.LensExtension {
kubeObjectMenuItems = [ kubeObjectMenuItems = [
{ {
kind: "Node", kind: "Node",

View File

@ -20,9 +20,23 @@
*/ */
import React from "react"; import React from "react";
import { Component, K8sApi, Navigation} from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
export interface NodeMenuProps extends Component.KubeObjectMenuProps<K8sApi.Node> { type Node = Renderer.K8sApi.Node;
const {
Component: {
terminalStore,
createTerminalTab,
ConfirmDialog,
MenuItem,
Icon,
},
Navigation
} = Renderer;
export interface NodeMenuProps extends Renderer.Component.KubeObjectMenuProps<Node> {
} }
export function NodeMenu(props: NodeMenuProps) { export function NodeMenu(props: NodeMenuProps) {
@ -32,7 +46,7 @@ export function NodeMenu(props: NodeMenuProps) {
const nodeName = node.getName(); const nodeName = node.getName();
const sendToTerminal = (command: string) => { const sendToTerminal = (command: string) => {
Component.terminalStore.sendCommand(command, { terminalStore.sendCommand(command, {
enter: true, enter: true,
newTab: true, newTab: true,
}); });
@ -40,7 +54,7 @@ export function NodeMenu(props: NodeMenuProps) {
}; };
const shell = () => { const shell = () => {
Component.createTerminalTab({ createTerminalTab({
title: `Node: ${nodeName}`, title: `Node: ${nodeName}`,
node: nodeName, node: nodeName,
}); });
@ -58,7 +72,7 @@ export function NodeMenu(props: NodeMenuProps) {
const drain = () => { const drain = () => {
const command = `kubectl drain ${nodeName} --delete-local-data --ignore-daemonsets --force`; const command = `kubectl drain ${nodeName} --delete-local-data --ignore-daemonsets --force`;
Component.ConfirmDialog.open({ ConfirmDialog.open({
ok: () => sendToTerminal(command), ok: () => sendToTerminal(command),
labelOk: `Drain Node`, labelOk: `Drain Node`,
message: ( message: (
@ -71,26 +85,26 @@ export function NodeMenu(props: NodeMenuProps) {
return ( return (
<> <>
<Component.MenuItem onClick={shell}> <MenuItem onClick={shell}>
<Component.Icon svg="ssh" interactive={toolbar} title="Node shell"/> <Icon svg="ssh" interactive={toolbar} title="Node shell"/>
<span className="title">Shell</span> <span className="title">Shell</span>
</Component.MenuItem> </MenuItem>
{!node.isUnschedulable() && ( {!node.isUnschedulable() && (
<Component.MenuItem onClick={cordon}> <MenuItem onClick={cordon}>
<Component.Icon material="pause_circle_filled" title="Cordon" interactive={toolbar}/> <Icon material="pause_circle_filled" title="Cordon" interactive={toolbar}/>
<span className="title">Cordon</span> <span className="title">Cordon</span>
</Component.MenuItem> </MenuItem>
)} )}
{node.isUnschedulable() && ( {node.isUnschedulable() && (
<Component.MenuItem onClick={unCordon}> <MenuItem onClick={unCordon}>
<Component.Icon material="play_circle_filled" title="Uncordon" interactive={toolbar}/> <Icon material="play_circle_filled" title="Uncordon" interactive={toolbar}/>
<span className="title">Uncordon</span> <span className="title">Uncordon</span>
</Component.MenuItem> </MenuItem>
)} )}
<Component.MenuItem onClick={drain}> <MenuItem onClick={drain}>
<Component.Icon material="delete_sweep" title="Drain" interactive={toolbar}/> <Icon material="delete_sweep" title="Drain" interactive={toolbar}/>
<span className="title">Drain</span> <span className="title">Drain</span>
</Component.MenuItem> </MenuItem>
</> </>
); );
} }

View File

@ -19,12 +19,12 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { LensRendererExtension } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
import { PodShellMenu, PodShellMenuProps } from "./src/shell-menu"; import { PodShellMenu, PodShellMenuProps } from "./src/shell-menu";
import { PodLogsMenu, PodLogsMenuProps } from "./src/logs-menu"; import { PodLogsMenu, PodLogsMenuProps } from "./src/logs-menu";
import React from "react"; import React from "react";
export default class PodMenuRendererExtension extends LensRendererExtension { export default class PodMenuRendererExtension extends Renderer.LensExtension {
kubeObjectMenuItems = [ kubeObjectMenuItems = [
{ {
kind: "Pod", kind: "Pod",

View File

@ -20,17 +20,34 @@
*/ */
import React from "react"; import React from "react";
import { Component, K8sApi, Util, Navigation } from "@k8slens/extensions"; import { Renderer, Common } from "@k8slens/extensions";
export interface PodLogsMenuProps extends Component.KubeObjectMenuProps<K8sApi.Pod> { type Pod = Renderer.K8sApi.Pod;
type IPodContainer = Renderer.K8sApi.IPodContainer;
const {
Component: {
logTabStore,
MenuItem,
Icon,
SubMenu,
StatusBrick,
},
Navigation,
} = Renderer;
const {
Util,
} = Common;
export interface PodLogsMenuProps extends Renderer.Component.KubeObjectMenuProps<Pod> {
} }
export class PodLogsMenu extends React.Component<PodLogsMenuProps> { export class PodLogsMenu extends React.Component<PodLogsMenuProps> {
showLogs(container: K8sApi.IPodContainer) { showLogs(container: IPodContainer) {
Navigation.hideDetails(); Navigation.hideDetails();
const pod = this.props.object; const pod = this.props.object;
Component.logTabStore.createPodTab({ logTabStore.createPodTab({
selectedPod: pod, selectedPod: pod,
selectedContainer: container, selectedContainer: container,
}); });
@ -44,35 +61,35 @@ export class PodLogsMenu extends React.Component<PodLogsMenuProps> {
if (!containers.length) return null; if (!containers.length) return null;
return ( return (
<Component.MenuItem onClick={Util.prevDefault(() => this.showLogs(containers[0]))}> <MenuItem onClick={Util.prevDefault(() => this.showLogs(containers[0]))}>
<Component.Icon material="subject" title="Logs" interactive={toolbar}/> <Icon material="subject" title="Logs" interactive={toolbar}/>
<span className="title">Logs</span> <span className="title">Logs</span>
{containers.length > 1 && ( {containers.length > 1 && (
<> <>
<Component.Icon className="arrow" material="keyboard_arrow_right"/> <Icon className="arrow" material="keyboard_arrow_right"/>
<Component.SubMenu> <SubMenu>
{ {
containers.map(container => { containers.map(container => {
const { name } = container; const { name } = container;
const status = statuses.find(status => status.name === name); const status = statuses.find(status => status.name === name);
const brick = status ? ( const brick = status ? (
<Component.StatusBrick <StatusBrick
className={Util.cssNames(Object.keys(status.state)[0], { ready: status.ready })} className={Util.cssNames(Object.keys(status.state)[0], { ready: status.ready })}
/> />
) : null; ) : null;
return ( return (
<Component.MenuItem key={name} onClick={Util.prevDefault(() => this.showLogs(container))} className="flex align-center"> <MenuItem key={name} onClick={Util.prevDefault(() => this.showLogs(container))} className="flex align-center">
{brick} {brick}
<span>{name}</span> <span>{name}</span>
</Component.MenuItem> </MenuItem>
); );
}) })
} }
</Component.SubMenu> </SubMenu>
</> </>
)} )}
</Component.MenuItem> </MenuItem>
); );
} }
} }

View File

@ -22,9 +22,26 @@
import React from "react"; import React from "react";
import { Component, K8sApi, Util, Navigation } from "@k8slens/extensions"; import { Renderer, Common } from "@k8slens/extensions";
export interface PodShellMenuProps extends Component.KubeObjectMenuProps<K8sApi.Pod> { type Pod = Renderer.K8sApi.Pod;
const {
Component: {
createTerminalTab,
terminalStore,
MenuItem,
Icon,
SubMenu,
StatusBrick,
},
Navigation,
} = Renderer;
const {
Util,
} = Common;
export interface PodShellMenuProps extends Renderer.Component.KubeObjectMenuProps<Pod> {
} }
export class PodShellMenu extends React.Component<PodShellMenuProps> { export class PodShellMenu extends React.Component<PodShellMenuProps> {
@ -44,11 +61,11 @@ export class PodShellMenu extends React.Component<PodShellMenuProps> {
command = `${command} sh -c "clear; (bash || ash || sh)"`; command = `${command} sh -c "clear; (bash || ash || sh)"`;
} }
const shell = Component.createTerminalTab({ const shell = createTerminalTab({
title: `Pod: ${pod.getName()} (namespace: ${pod.getNs()})` title: `Pod: ${pod.getName()} (namespace: ${pod.getNs()})`
}); });
Component.terminalStore.sendCommand(command, { terminalStore.sendCommand(command, {
enter: true, enter: true,
tabId: shell.id tabId: shell.id
}); });
@ -61,29 +78,29 @@ export class PodShellMenu extends React.Component<PodShellMenuProps> {
if (!containers.length) return null; if (!containers.length) return null;
return ( return (
<Component.MenuItem onClick={Util.prevDefault(() => this.execShell(containers[0].name))}> <MenuItem onClick={Util.prevDefault(() => this.execShell(containers[0].name))}>
<Component.Icon svg="ssh" interactive={toolbar} title="Pod shell"/> <Icon svg="ssh" interactive={toolbar} title="Pod shell"/>
<span className="title">Shell</span> <span className="title">Shell</span>
{containers.length > 1 && ( {containers.length > 1 && (
<> <>
<Component.Icon className="arrow" material="keyboard_arrow_right"/> <Icon className="arrow" material="keyboard_arrow_right"/>
<Component.SubMenu> <SubMenu>
{ {
containers.map(container => { containers.map(container => {
const { name } = container; const { name } = container;
return ( return (
<Component.MenuItem key={name} onClick={Util.prevDefault(() => this.execShell(name))} className="flex align-center"> <MenuItem key={name} onClick={Util.prevDefault(() => this.execShell(name))} className="flex align-center">
<Component.StatusBrick/> <StatusBrick/>
<span>{name}</span> <span>{name}</span>
</Component.MenuItem> </MenuItem>
); );
}) })
} }
</Component.SubMenu> </SubMenu>
</> </>
)} )}
</Component.MenuItem> </MenuItem>
); );
} }
} }

View File

@ -25,6 +25,7 @@ import { catalogEntityRegistry as registry } from "../../main/catalog";
export { catalogCategoryRegistry as catalogCategories } from "../../common/catalog/catalog-category-registry"; export { catalogCategoryRegistry as catalogCategories } from "../../common/catalog/catalog-category-registry";
export * from "../../common/catalog-entities"; export * from "../../common/catalog-entities";
export * from "../../common/catalog/catalog-entity";
export class CatalogEntityRegistry { export class CatalogEntityRegistry {
getItemsForApiKind<T extends CatalogEntity>(apiVersion: string, kind: string): T[] { getItemsForApiKind<T extends CatalogEntity>(apiVersion: string, kind: string): T[] {

View File

@ -19,27 +19,19 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
// Lens-extensions api developer's kit
export { LensMainExtension } from "../lens-main-extension";
export { LensRendererExtension } from "../lens-renderer-extension";
// APIs // APIs
import * as App from "./app"; import * as App from "./app";
import * as EventBus from "./event-bus"; import * as EventBus from "./event-bus";
import * as Store from "./stores"; import * as Store from "./stores";
import * as Util from "./utils"; import * as Util from "./utils";
import * as Interface from "../interfaces";
import * as Catalog from "./catalog"; import * as Catalog from "./catalog";
import * as Types from "./types"; import * as Types from "./types";
import * as Ipc from "./ipc";
export { export {
App, App,
EventBus, EventBus,
Catalog, Catalog,
Interface,
Store, Store,
Types, Types,
Util, Util,
Ipc,
}; };

View File

@ -22,3 +22,5 @@
export type IpcMainInvokeEvent = Electron.IpcMainInvokeEvent; export type IpcMainInvokeEvent = Electron.IpcMainInvokeEvent;
export type IpcRendererEvent = Electron.IpcRendererEvent; export type IpcRendererEvent = Electron.IpcRendererEvent;
export type IpcMainEvent = Electron.IpcMainEvent; export type IpcMainEvent = Electron.IpcMainEvent;
export * from "./registrations";

View File

@ -1,23 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export { IpcMain as Main } from "../ipc/ipc-main";
export { IpcRegistrar as Registrar } from "../ipc/ipc-registrar";

View File

@ -22,5 +22,12 @@
// Extensions-api types bundle (main + renderer) // Extensions-api types bundle (main + renderer)
// Available for lens-extensions via NPM-package "@k8slens/extensions" // Available for lens-extensions via NPM-package "@k8slens/extensions"
export * from "./core-api"; import * as Common from "./common-api";
export * from "./renderer-api"; import * as Renderer from "./renderer-api";
import * as Main from "./main-api";
export {
Common,
Renderer,
Main,
};

View File

@ -337,7 +337,11 @@ export class ExtensionLoader extends Singleton {
try { try {
return __non_webpack_require__(extAbsolutePath).default; return __non_webpack_require__(extAbsolutePath).default;
} catch (error) { } catch (error) {
logger.error(`${logModule}: can't load extension main at ${extAbsolutePath}: ${error}`, { extension, error }); if (ipcRenderer) {
console.error(`${logModule}: can't load ${entryPointName} for "${extension.manifest.name}": ${error.stack || error}`, extension);
} else {
logger.error(`${logModule}: can't load ${entryPointName} for "${extension.manifest.name}": ${error}`, { extension });
}
} }
return null; return null;

View File

@ -1,22 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export * from "../../common/catalog/catalog-entity";

View File

@ -19,5 +19,10 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
export * from "./registrations"; import { IpcMain as Ipc } from "../ipc/ipc-main";
export * from "./catalog"; import { LensMainExtension as LensExtension } from "../lens-main-extension";
export {
Ipc,
LensExtension,
};

View File

@ -26,10 +26,14 @@ import * as Component from "./components";
import * as K8sApi from "./k8s-api"; import * as K8sApi from "./k8s-api";
import * as Navigation from "./navigation"; import * as Navigation from "./navigation";
import * as Theme from "./theming"; import * as Theme from "./theming";
import { IpcRenderer as Ipc } from "../ipc/ipc-renderer";
import { LensRendererExtension as LensExtension } from "../lens-renderer-extension";
export { export {
Component, Component,
K8sApi, K8sApi,
Navigation, Navigation,
Theme, Theme,
Ipc,
LensExtension,
}; };

View File

@ -23,7 +23,8 @@
import "../common/system-ca"; import "../common/system-ca";
import * as Mobx from "mobx"; import * as Mobx from "mobx";
import * as LensExtensionsCoreApi from "../extensions/core-api"; import * as LensExtensionsCommonApi from "../extensions/common-api";
import * as LensExtensionsMainApi from "../extensions/main-api";
import { app, autoUpdater, ipcMain, dialog, powerMonitor } from "electron"; import { app, autoUpdater, ipcMain, dialog, powerMonitor } from "electron";
import { appName, isMac, productName } from "../common/vars"; import { appName, isMac, productName } from "../common/vars";
import path from "path"; import path from "path";
@ -279,7 +280,8 @@ app.on("open-url", (event, rawUrl) => {
* e.g. global.Mobx, global.LensExtensions * e.g. global.Mobx, global.LensExtensions
*/ */
const LensExtensions = { const LensExtensions = {
...LensExtensionsCoreApi, Common: LensExtensionsCommonApi,
Main: LensExtensionsMainApi,
}; };
export { export {

View File

@ -24,7 +24,7 @@ import * as uuid from "uuid";
import { broadcastMessage } from "../../../common/ipc"; import { broadcastMessage } from "../../../common/ipc";
import { ProtocolHandlerExtension, ProtocolHandlerInternal } from "../../../common/protocol-handler"; import { ProtocolHandlerExtension, ProtocolHandlerInternal } from "../../../common/protocol-handler";
import { noop } from "../../../common/utils"; import { noop } from "../../../common/utils";
import { LensMainExtension } from "../../../extensions/core-api"; import { LensExtension } from "../../../extensions/main-api";
import { ExtensionLoader } from "../../../extensions/extension-loader"; import { ExtensionLoader } from "../../../extensions/extension-loader";
import { ExtensionsStore } from "../../../extensions/extensions-store"; import { ExtensionsStore } from "../../../extensions/extensions-store";
import { LensProtocolRouterMain } from "../router"; import { LensProtocolRouterMain } from "../router";
@ -78,7 +78,7 @@ describe("protocol router tests", () => {
it.only("should not throw when has valid host", async () => { it.only("should not throw when has valid host", async () => {
const extId = uuid.v4(); const extId = uuid.v4();
const ext = new LensMainExtension({ const ext = new LensExtension({
id: extId, id: extId,
manifestPath: "/foo/bar", manifestPath: "/foo/bar",
manifest: { manifest: {
@ -155,7 +155,7 @@ describe("protocol router tests", () => {
const lpr = LensProtocolRouterMain.getInstance(); const lpr = LensProtocolRouterMain.getInstance();
const extId = uuid.v4(); const extId = uuid.v4();
const ext = new LensMainExtension({ const ext = new LensExtension({
id: extId, id: extId,
manifestPath: "/foo/bar", manifestPath: "/foo/bar",
manifest: { manifest: {
@ -195,7 +195,7 @@ describe("protocol router tests", () => {
{ {
const extId = uuid.v4(); const extId = uuid.v4();
const ext = new LensMainExtension({ const ext = new LensExtension({
id: extId, id: extId,
manifestPath: "/foo/bar", manifestPath: "/foo/bar",
manifest: { manifest: {
@ -219,7 +219,7 @@ describe("protocol router tests", () => {
{ {
const extId = uuid.v4(); const extId = uuid.v4();
const ext = new LensMainExtension({ const ext = new LensExtension({
id: extId, id: extId,
manifestPath: "/foo/bar", manifestPath: "/foo/bar",
manifest: { manifest: {

View File

@ -26,7 +26,7 @@ import * as Mobx from "mobx";
import * as MobxReact from "mobx-react"; import * as MobxReact from "mobx-react";
import * as ReactRouter from "react-router"; import * as ReactRouter from "react-router";
import * as ReactRouterDom from "react-router-dom"; import * as ReactRouterDom from "react-router-dom";
import * as LensExtensionsCoreApi from "../extensions/core-api"; import * as LensExtensionsCommonApi from "../extensions/common-api";
import * as LensExtensionsRendererApi from "../extensions/renderer-api"; import * as LensExtensionsRendererApi from "../extensions/renderer-api";
import { render, unmountComponentAtNode } from "react-dom"; import { render, unmountComponentAtNode } from "react-dom";
import { delay } from "../common/utils"; import { delay } from "../common/utils";
@ -123,8 +123,8 @@ bootstrap(process.isMainFrame ? LensApp : App);
* e.g. Devtools -> Console -> window.LensExtensions (renderer) * e.g. Devtools -> Console -> window.LensExtensions (renderer)
*/ */
const LensExtensions = { const LensExtensions = {
...LensExtensionsCoreApi, Common: LensExtensionsCommonApi,
...LensExtensionsRendererApi, Renderer: LensExtensionsRendererApi,
}; };
export { export {