mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Lens app source code (#119)
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
936cbd53d4
commit
1d0815abd2
797 files changed
+73714
-215
No files matched your search
+12
@@ -0,0 +1,12 @@
|
||||
$cert-status-colors: (
|
||||
ready: $colorOk,
|
||||
);
|
||||
|
||||
@mixin cert-status-bgc {
|
||||
@each $status, $color in $cert-status-colors {
|
||||
&.#{$status} {
|
||||
background: $color;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
@import "cert-manager.mixins";
|
||||
|
||||
.CertificateDetails {
|
||||
.Badge {
|
||||
@include cert-status-bgc;
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
import "./certificate-details.scss"
|
||||
|
||||
import React from "react";
|
||||
import moment from "moment"
|
||||
import { observer } from "mobx-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { DrawerItem, DrawerTitle } from "../../drawer";
|
||||
import { Badge } from "../../badge";
|
||||
import { KubeEventDetails } from "../../+events/kube-event-details";
|
||||
import { KubeObjectDetailsProps } from "../../kube-object";
|
||||
import { Certificate, certificatesApi } from "../../../api/endpoints/cert-manager.api";
|
||||
import { cssNames } from "../../../utils";
|
||||
import { apiManager } from "../../../api/api-manager";
|
||||
import { KubeObjectMeta } from "../../kube-object/kube-object-meta";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<Certificate> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CertificateDetails extends React.Component<Props> {
|
||||
render() {
|
||||
const { object: cert, className } = this.props;
|
||||
if (!cert) return;
|
||||
const { spec, status } = cert;
|
||||
const { acme, isCA, commonName, secretName, dnsNames, duration, ipAddresses, keyAlgorithm, keySize, organization, renewBefore } = spec;
|
||||
const { lastFailureTime, notAfter } = status;
|
||||
return (
|
||||
<div className={cssNames("CertificateDetails", className)}>
|
||||
<KubeObjectMeta object={cert}/>
|
||||
|
||||
<DrawerItem name={<Trans>Issuer</Trans>}>
|
||||
<Link to={cert.getIssuerDetailsUrl()}>
|
||||
{cert.getIssuerName()}
|
||||
</Link>
|
||||
</DrawerItem>
|
||||
|
||||
<DrawerItem name={<Trans>Secret Name</Trans>}>
|
||||
<Link to={cert.getSecretDetailsUrl()}>
|
||||
{secretName}
|
||||
</Link>
|
||||
</DrawerItem>
|
||||
|
||||
<DrawerItem name="CA">
|
||||
{isCA ? <Trans>Yes</Trans> : <Trans>No</Trans>}
|
||||
</DrawerItem>
|
||||
|
||||
{commonName && (
|
||||
<DrawerItem name={<Trans>Common Name</Trans>}>
|
||||
{commonName}
|
||||
</DrawerItem>
|
||||
)}
|
||||
{dnsNames && (
|
||||
<DrawerItem name={<Trans>DNS names</Trans>} labelsOnly>
|
||||
{dnsNames.map(name => <Badge key={name} label={name}/>)}
|
||||
</DrawerItem>
|
||||
)}
|
||||
{ipAddresses && (
|
||||
<DrawerItem name={<Trans>IP addresses</Trans>}>
|
||||
{ipAddresses.join(", ")}
|
||||
</DrawerItem>
|
||||
)}
|
||||
{organization && (
|
||||
<DrawerItem name={<Trans>Organization</Trans>}>
|
||||
{organization.join(", ")}
|
||||
</DrawerItem>
|
||||
)}
|
||||
{duration && (
|
||||
<DrawerItem name={<Trans>Duration</Trans>}>
|
||||
{duration}
|
||||
</DrawerItem>
|
||||
)}
|
||||
{renewBefore && (
|
||||
<DrawerItem name={<Trans>Renew Before</Trans>}>
|
||||
{renewBefore}
|
||||
</DrawerItem>
|
||||
)}
|
||||
{keySize && (
|
||||
<DrawerItem name={<Trans>Key Size</Trans>}>
|
||||
{keySize}
|
||||
</DrawerItem>
|
||||
)}
|
||||
{keyAlgorithm && (
|
||||
<DrawerItem name={<Trans>Key Algorithm</Trans>}>
|
||||
{keyAlgorithm}
|
||||
</DrawerItem>
|
||||
)}
|
||||
|
||||
<DrawerItem name={<Trans>Not After</Trans>}>
|
||||
{moment(notAfter).format("LLL")}
|
||||
</DrawerItem>
|
||||
|
||||
{lastFailureTime && (
|
||||
<DrawerItem name={<Trans>Last Failure Time</Trans>}>
|
||||
{lastFailureTime}
|
||||
</DrawerItem>
|
||||
)}
|
||||
<DrawerItem name={<Trans>Status</Trans>} labelsOnly>
|
||||
{cert.getConditions().map(({ type, tooltip, isReady }) => {
|
||||
return (
|
||||
<Badge
|
||||
key={type}
|
||||
label={type}
|
||||
tooltip={tooltip}
|
||||
className={cssNames({ [type.toLowerCase()]: isReady })}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</DrawerItem>
|
||||
|
||||
{acme && (
|
||||
<>
|
||||
<DrawerTitle title="ACME"/>
|
||||
{acme.config.map(({ domains, http01, dns01 }, index) => {
|
||||
return (
|
||||
<div key={index} className="acme-config">
|
||||
<DrawerItem name={<Trans>Domains</Trans>} labelsOnly>
|
||||
{domains.map(domain => <Badge key={domain} label={domain}/>)}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Http01</Trans>}>
|
||||
{Object.entries(http01).map(([key, val]) => `${key}: ${val}`)[0]}
|
||||
</DrawerItem>
|
||||
{dns01 && (
|
||||
<DrawerItem name={<Trans>DNS Provider</Trans>} labelsOnly>
|
||||
{dns01.provider}
|
||||
</DrawerItem>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
<KubeEventDetails object={cert}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
apiManager.registerViews(certificatesApi, {
|
||||
Details: CertificateDetails
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
@import "cert-manager.mixins";
|
||||
|
||||
.Certificates {
|
||||
.TableCell {
|
||||
&.name {
|
||||
flex: 1.2;
|
||||
}
|
||||
|
||||
&.type {
|
||||
flex: .5;
|
||||
}
|
||||
|
||||
&.status {
|
||||
flex: .5;
|
||||
@include table-cell-labels-offsets;
|
||||
|
||||
.Badge {
|
||||
@include cert-status-bgc;
|
||||
}
|
||||
}
|
||||
|
||||
&.age {
|
||||
flex: .5;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import "./certificates.scss"
|
||||
|
||||
import * as React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { KubeObjectMenu, KubeObjectMenuProps } from "../../kube-object/kube-object-menu";
|
||||
import { KubeObjectListLayout, KubeObjectListLayoutProps } from "../../kube-object";
|
||||
import { Certificate, certificatesApi } from "../../../api/endpoints/cert-manager.api";
|
||||
import { cssNames, stopPropagation } from "../../../utils";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Badge } from "../../badge";
|
||||
import { apiManager } from "../../../api/api-manager";
|
||||
import { Spinner } from "../../spinner";
|
||||
|
||||
enum sortBy {
|
||||
name = "name",
|
||||
namespace = "namespace",
|
||||
age = "age",
|
||||
commonName = "common-name",
|
||||
secretName = "secret",
|
||||
issuer = "issuer",
|
||||
type = "type",
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Certificates extends React.Component<KubeObjectListLayoutProps> {
|
||||
render() {
|
||||
const { store = apiManager.getStore(certificatesApi), ...layoutProps } = this.props;
|
||||
if (!store) {
|
||||
return <Spinner center/>
|
||||
}
|
||||
return (
|
||||
<KubeObjectListLayout
|
||||
{...layoutProps}
|
||||
store={store}
|
||||
className="Certificates"
|
||||
sortingCallbacks={{
|
||||
[sortBy.name]: (item: Certificate) => item.getName(),
|
||||
[sortBy.namespace]: (item: Certificate) => item.getNs(),
|
||||
[sortBy.secretName]: (item: Certificate) => item.getSecretName(),
|
||||
[sortBy.commonName]: (item: Certificate) => item.getCommonName(),
|
||||
[sortBy.issuer]: (item: Certificate) => item.getIssuerName(),
|
||||
[sortBy.type]: (item: Certificate) => item.getType(),
|
||||
}}
|
||||
searchFilters={[
|
||||
(item: Certificate) => item.getSearchFields(),
|
||||
(item: Certificate) => item.getSecretName(),
|
||||
(item: Certificate) => item.getCommonName(),
|
||||
(item: Certificate) => item.getIssuerName(),
|
||||
(item: Certificate) => item.getType(),
|
||||
]}
|
||||
renderHeaderTitle={<Trans>Certificates</Trans>}
|
||||
renderTableHeader={[
|
||||
{ title: <Trans>Name</Trans>, className: "name", sortBy: sortBy.name },
|
||||
{ title: <Trans>Namespace</Trans>, className: "namespace", sortBy: sortBy.namespace },
|
||||
{ title: <Trans>Common Name</Trans>, className: "common-name", sortBy: sortBy.type },
|
||||
{ title: <Trans>Type</Trans>, className: "type", sortBy: sortBy.type },
|
||||
{ title: <Trans>Issuer</Trans>, className: "issuer", sortBy: sortBy.issuer },
|
||||
{ title: <Trans>Secret</Trans>, className: "secret", sortBy: sortBy.secretName },
|
||||
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
|
||||
{ title: <Trans>Status</Trans>, className: "status" },
|
||||
]}
|
||||
renderTableContents={(cert: Certificate) => {
|
||||
return [
|
||||
cert.getName(),
|
||||
cert.getNs(),
|
||||
cert.getCommonName(),
|
||||
cert.getType(),
|
||||
<Link to={cert.getIssuerDetailsUrl()} onClick={stopPropagation}>
|
||||
{cert.getIssuerName()}
|
||||
</Link>,
|
||||
<Link to={cert.getSecretDetailsUrl()} onClick={stopPropagation}>
|
||||
{cert.getSecretName()}
|
||||
</Link>,
|
||||
cert.getAge(),
|
||||
cert.getConditions().map(({ type, tooltip, isReady }) => {
|
||||
return (
|
||||
<Badge
|
||||
key={type}
|
||||
label={type}
|
||||
tooltip={tooltip}
|
||||
className={cssNames({ [type.toLowerCase()]: isReady })}
|
||||
/>
|
||||
)
|
||||
})
|
||||
]
|
||||
}}
|
||||
renderItemMenu={(item: Certificate) => {
|
||||
return <CertificateMenu object={item}/>
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function CertificateMenu(props: KubeObjectMenuProps<Certificate>) {
|
||||
return (
|
||||
<KubeObjectMenu {...props}/>
|
||||
)
|
||||
}
|
||||
|
||||
apiManager.registerViews(certificatesApi, {
|
||||
List: Certificates,
|
||||
Menu: CertificateMenu,
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./certificates"
|
||||
export * from "./certificate-details"
|
||||
export * from "./issuers"
|
||||
export * from "./issuer-details"
|
||||
@@ -0,0 +1,7 @@
|
||||
@import "cert-manager.mixins";
|
||||
|
||||
.IssuerDetails {
|
||||
.Badge {
|
||||
@include cert-status-bgc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import "./issuer-details.scss"
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { Link } from "react-router-dom";
|
||||
import { DrawerItem, DrawerTitle } from "../../drawer";
|
||||
import { Badge } from "../../badge";
|
||||
import { KubeEventDetails } from "../../+events/kube-event-details";
|
||||
import { KubeObjectDetailsProps } from "../../kube-object";
|
||||
import { clusterIssuersApi, Issuer, issuersApi } from "../../../api/endpoints/cert-manager.api";
|
||||
import { autobind, cssNames } from "../../../utils";
|
||||
import { getDetailsUrl } from "../../../navigation";
|
||||
import { secretsApi } from "../../../api/endpoints";
|
||||
import { apiManager } from "../../../api/api-manager";
|
||||
import { KubeObjectMeta } from "../../kube-object/kube-object-meta";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<Issuer> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class IssuerDetails extends React.Component<Props> {
|
||||
@autobind()
|
||||
renderSecretLink(secretName: string) {
|
||||
const namespace = this.props.object.getNs();
|
||||
if (!namespace) {
|
||||
return secretName;
|
||||
}
|
||||
const secretDetailsUrl = getDetailsUrl(secretsApi.getUrl({
|
||||
namespace: namespace,
|
||||
name: secretName,
|
||||
}));
|
||||
return (
|
||||
<Link to={secretDetailsUrl}>
|
||||
{secretName}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { object: issuer, className } = this.props;
|
||||
if (!issuer) return;
|
||||
const { renderSecretLink } = this;
|
||||
const { spec: { acme, ca, vault, venafi }, status } = issuer;
|
||||
return (
|
||||
<div className={cssNames("IssuerDetails", className)}>
|
||||
<KubeObjectMeta object={issuer}/>
|
||||
|
||||
<DrawerItem name={<Trans>Type</Trans>}>
|
||||
{issuer.getType()}
|
||||
</DrawerItem>
|
||||
|
||||
<DrawerItem name={<Trans>Status</Trans>} labelsOnly>
|
||||
{issuer.getConditions().map(({ type, tooltip, isReady }) => {
|
||||
return (
|
||||
<Badge
|
||||
key={type}
|
||||
label={type}
|
||||
tooltip={tooltip}
|
||||
className={cssNames({ [type.toLowerCase()]: isReady })}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</DrawerItem>
|
||||
|
||||
{acme && (() => {
|
||||
const { email, server, skipTLSVerify, privateKeySecretRef, solvers } = acme;
|
||||
return (
|
||||
<>
|
||||
<DrawerTitle title="ACME"/>
|
||||
<DrawerItem name={<Trans>E-mail</Trans>}>
|
||||
{email}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Server</Trans>}>
|
||||
{server}
|
||||
</DrawerItem>
|
||||
{status.acme && (
|
||||
<DrawerItem name={<Trans>Status URI</Trans>}>
|
||||
{status.acme.uri}
|
||||
</DrawerItem>
|
||||
)}
|
||||
<DrawerItem name={<Trans>Private Key Secret</Trans>}>
|
||||
{renderSecretLink(privateKeySecretRef.name)}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Skip TLS Verify</Trans>}>
|
||||
{skipTLSVerify ? <Trans>Yes</Trans> : <Trans>No</Trans>}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
|
||||
{ca && (() => {
|
||||
const { secretName } = ca;
|
||||
return (
|
||||
<>
|
||||
<DrawerTitle title="CA"/>
|
||||
<DrawerItem name={<Trans>Secret Name</Trans>}>
|
||||
{renderSecretLink(secretName)}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
|
||||
{vault && (() => {
|
||||
const { auth, caBundle, path, server } = vault;
|
||||
const { path: authPath, roleId, secretRef } = auth.appRole;
|
||||
return (
|
||||
<>
|
||||
<DrawerTitle title="Vault"/>
|
||||
<DrawerItem name={<Trans>Server</Trans>}>
|
||||
{server}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Path</Trans>}>
|
||||
{path}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>CA Bundle</Trans>} labelsOnly>
|
||||
<Badge label={caBundle}/>
|
||||
</DrawerItem>
|
||||
|
||||
<DrawerTitle title={<Trans>Auth App Role</Trans>}/>
|
||||
<DrawerItem name={<Trans>Path</Trans>}>
|
||||
{authPath}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Role ID</Trans>}>
|
||||
{roleId}
|
||||
</DrawerItem>
|
||||
{secretRef && (
|
||||
<DrawerItem name={<Trans>Secret</Trans>}>
|
||||
{renderSecretLink(secretRef.name)}
|
||||
</DrawerItem>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
|
||||
{venafi && (() => {
|
||||
const { zone, cloud, tpp } = venafi;
|
||||
return (
|
||||
<>
|
||||
<DrawerTitle title="CA"/>
|
||||
<DrawerItem name={<Trans>Zone</Trans>}>
|
||||
{zone}
|
||||
</DrawerItem>
|
||||
{cloud && (
|
||||
<DrawerItem name={<Trans>Cloud API Token Secret</Trans>}>
|
||||
{renderSecretLink(cloud.apiTokenSecretRef.name)}
|
||||
</DrawerItem>
|
||||
)}
|
||||
{tpp && (
|
||||
<>
|
||||
<DrawerTitle title="TPP"/>
|
||||
<DrawerItem name={<Trans>URL</Trans>}>
|
||||
{tpp.url}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>CA Bundle</Trans>} labelsOnly>
|
||||
<Badge label={tpp.caBundle}/>
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Credentials Ref</Trans>}>
|
||||
{renderSecretLink(tpp.credentialsRef.name)}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
|
||||
<KubeEventDetails object={issuer}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
apiManager.registerViews([issuersApi, clusterIssuersApi], {
|
||||
Details: IssuerDetails
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
@import "cert-manager.mixins";
|
||||
|
||||
.Issuers {
|
||||
.TableCell {
|
||||
&.name {
|
||||
flex: 1.2;
|
||||
}
|
||||
|
||||
&.labels {
|
||||
flex: 2;
|
||||
@include table-cell-labels-offsets;
|
||||
}
|
||||
|
||||
&.status {
|
||||
flex: .5;
|
||||
@include table-cell-labels-offsets;
|
||||
|
||||
.Badge {
|
||||
@include cert-status-bgc;
|
||||
}
|
||||
}
|
||||
|
||||
&.age {
|
||||
flex: .5;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import "./issuers.scss"
|
||||
|
||||
import * as React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { KubeObjectMenu, KubeObjectMenuProps } from "../../kube-object/kube-object-menu";
|
||||
import { KubeObjectListLayout, KubeObjectListLayoutProps } from "../../kube-object";
|
||||
import { clusterIssuersApi, Issuer, issuersApi } from "../../../api/endpoints/cert-manager.api";
|
||||
import { cssNames } from "../../../utils";
|
||||
import { Badge } from "../../badge";
|
||||
import { Spinner } from "../../spinner";
|
||||
import { apiManager } from "../../../api/api-manager";
|
||||
|
||||
enum sortBy {
|
||||
name = "name",
|
||||
namespace = "namespace",
|
||||
type = "type",
|
||||
labels = "labels",
|
||||
age = "age",
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterIssuers extends React.Component<KubeObjectListLayoutProps> {
|
||||
render() {
|
||||
const store = apiManager.getStore(clusterIssuersApi);
|
||||
return (
|
||||
<Issuers
|
||||
{...this.props}
|
||||
isClusterScoped={true}
|
||||
store={store}
|
||||
renderHeaderTitle={<Trans>Cluster Issuers</Trans>}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Issuers extends React.Component<KubeObjectListLayoutProps> {
|
||||
render() {
|
||||
const { store = apiManager.getStore(issuersApi), ...layoutProps } = this.props;
|
||||
if (!store) {
|
||||
return <Spinner center/>
|
||||
}
|
||||
return (
|
||||
<KubeObjectListLayout
|
||||
store={store}
|
||||
renderHeaderTitle={<Trans>Issuers</Trans>}
|
||||
{...layoutProps}
|
||||
className="Issuers"
|
||||
sortingCallbacks={{
|
||||
[sortBy.name]: (item: Issuer) => item.getName(),
|
||||
[sortBy.namespace]: (item: Issuer) => item.getNs(),
|
||||
[sortBy.type]: (item: Issuer) => item.getType(),
|
||||
[sortBy.labels]: (item: Issuer) => item.getLabels(),
|
||||
[sortBy.age]: (item: Issuer) => item.getAge(false),
|
||||
}}
|
||||
searchFilters={[
|
||||
(item: Issuer) => item.getSearchFields(),
|
||||
(item: Issuer) => item.getType(),
|
||||
]}
|
||||
renderTableHeader={[
|
||||
{ title: <Trans>Name</Trans>, className: "name", sortBy: sortBy.name },
|
||||
{ title: <Trans>Namespace</Trans>, className: "namespace", sortBy: sortBy.namespace },
|
||||
{ title: <Trans>Labels</Trans>, className: "labels", sortBy: sortBy.labels },
|
||||
{ title: <Trans>Type</Trans>, className: "type", sortBy: sortBy.type },
|
||||
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
|
||||
{ title: <Trans>Status</Trans>, className: "status" },
|
||||
]}
|
||||
renderTableContents={(issuer: Issuer) => [
|
||||
issuer.getName(),
|
||||
issuer.getNs(),
|
||||
issuer.getLabels().map(label => <Badge key={label} label={label} title={label}/>),
|
||||
issuer.getType(),
|
||||
issuer.getAge(),
|
||||
issuer.getConditions().map(({ type, tooltip, isReady }) => {
|
||||
return (
|
||||
<Badge
|
||||
key={type}
|
||||
label={type}
|
||||
tooltip={tooltip}
|
||||
className={cssNames({ [type.toLowerCase()]: isReady })}
|
||||
/>
|
||||
)
|
||||
})
|
||||
]}
|
||||
renderItemMenu={(item: Issuer) => {
|
||||
return <IssuerMenu object={item}/>
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function IssuerMenu(props: KubeObjectMenuProps<Issuer>) {
|
||||
return (
|
||||
<KubeObjectMenu {...props}/>
|
||||
)
|
||||
}
|
||||
|
||||
apiManager.registerViews([issuersApi, clusterIssuersApi], {
|
||||
List: Issuers,
|
||||
Menu: IssuerMenu,
|
||||
})
|
||||
@@ -0,0 +1,45 @@
|
||||
@import "crd.mixins";
|
||||
|
||||
.CRDDetails {
|
||||
.conditions {
|
||||
.Badge {
|
||||
@include crd-condition-bgc;
|
||||
}
|
||||
}
|
||||
|
||||
.Table {
|
||||
margin-left: -$margin * 2;
|
||||
margin-right: -$margin * 2;
|
||||
|
||||
.TableRow {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.printer-columns {
|
||||
.type {
|
||||
flex: 0.5;
|
||||
}
|
||||
|
||||
.description {
|
||||
flex: 1.5;
|
||||
|
||||
.Badge {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.json-path {
|
||||
flex: 3;
|
||||
|
||||
.Badge {
|
||||
white-space: pre-line;
|
||||
text-overflow: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.validation {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import "./crd-details.scss";
|
||||
|
||||
import * as React from "react";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { Link } from "react-router-dom";
|
||||
import { observer } from "mobx-react";
|
||||
import { apiManager } from "../../api/api-manager";
|
||||
import { crdApi, CustomResourceDefinition } from "../../api/endpoints/crd.api";
|
||||
import { cssNames } from "../../utils";
|
||||
import { AceEditor } from "../ace-editor";
|
||||
import { Badge } from "../badge";
|
||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||
import { KubeObjectDetailsProps } from "../kube-object";
|
||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||
import { Input } from "../input";
|
||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<CustomResourceDefinition> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CRDDetails extends React.Component<Props> {
|
||||
render() {
|
||||
const { object: crd } = this.props;
|
||||
if (!crd) return null;
|
||||
const { plural, singular, kind, listKind } = crd.getNames();
|
||||
const printerColumns = crd.getPrinterColumns();
|
||||
const validation = crd.getValidation();
|
||||
return (
|
||||
<div className="CRDDetails">
|
||||
<KubeObjectMeta object={crd}/>
|
||||
|
||||
<DrawerItem name={<Trans>Group</Trans>}>
|
||||
{crd.getGroup()}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Version</Trans>}>
|
||||
{crd.getVersion()}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Stored versions</Trans>}>
|
||||
{crd.getStoredVersions()}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Scope</Trans>}>
|
||||
{crd.getScope()}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Resource</Trans>}>
|
||||
<Link to={crd.getResourceUrl()}>
|
||||
{crd.getResourceTitle()}
|
||||
</Link>
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Conversion</Trans>} className="flex gaps align-flex-start">
|
||||
<Input
|
||||
multiLine
|
||||
theme="round-black"
|
||||
className="box grow"
|
||||
value={crd.getConversion()}
|
||||
readOnly
|
||||
/>
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Conditions</Trans>} className="conditions" labelsOnly>
|
||||
{
|
||||
crd.getConditions().map(condition => {
|
||||
const { type, message, lastTransitionTime, status } = condition
|
||||
return (
|
||||
<Badge
|
||||
key={type}
|
||||
label={type}
|
||||
className={cssNames({ disabled: status === "False" }, type)}
|
||||
tooltip={(
|
||||
<>
|
||||
<p>{message}</p>
|
||||
<p><Trans>Last transition time: {lastTransitionTime}</Trans></p>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</DrawerItem>
|
||||
<DrawerTitle title={<Trans>Names</Trans>}/>
|
||||
<Table selectable className="names box grow">
|
||||
<TableHead>
|
||||
<TableCell><Trans>plural</Trans></TableCell>
|
||||
<TableCell><Trans>singular</Trans></TableCell>
|
||||
<TableCell><Trans>kind</Trans></TableCell>
|
||||
<TableCell><Trans>listKind</Trans></TableCell>
|
||||
</TableHead>
|
||||
<TableRow>
|
||||
<TableCell>{plural}</TableCell>
|
||||
<TableCell>{singular}</TableCell>
|
||||
<TableCell>{kind}</TableCell>
|
||||
<TableCell>{listKind}</TableCell>
|
||||
</TableRow>
|
||||
</Table>
|
||||
{printerColumns.length > 0 &&
|
||||
<>
|
||||
<DrawerTitle title={<Trans>Additional Printer Columns</Trans>}/>
|
||||
<Table selectable className="printer-columns box grow">
|
||||
<TableHead>
|
||||
<TableCell className="name"><Trans>Name</Trans></TableCell>
|
||||
<TableCell className="type"><Trans>Type</Trans></TableCell>
|
||||
<TableCell className="json-path"><Trans>JSON Path</Trans></TableCell>
|
||||
</TableHead>
|
||||
{
|
||||
printerColumns.map((column, index) => {
|
||||
const { name, type, JSONPath } = column;
|
||||
return (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="name">{name}</TableCell>
|
||||
<TableCell className="type">{type}</TableCell>
|
||||
<TableCell className="json-path">
|
||||
<Badge label={JSONPath}/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Table>
|
||||
</>
|
||||
}
|
||||
{validation &&
|
||||
<>
|
||||
<DrawerTitle title={<Trans>Validation</Trans>}/>
|
||||
<AceEditor
|
||||
mode="json"
|
||||
className="validation"
|
||||
value={validation}
|
||||
readOnly
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
apiManager.registerViews(crdApi, {
|
||||
Details: CRDDetails
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
@import "crd.mixins";
|
||||
|
||||
.CrdList {
|
||||
.TableCell {
|
||||
&.kind {
|
||||
flex: 1.5;
|
||||
}
|
||||
|
||||
&.group {
|
||||
flex: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.group-select {
|
||||
.Select {
|
||||
&__placeholder {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import "./crd-list.scss"
|
||||
|
||||
import React from "react";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { computed } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { stopPropagation } from "../../utils";
|
||||
import { KubeObjectListLayout } from "../kube-object";
|
||||
import { crdStore } from "./crd.store";
|
||||
import { apiManager } from "../../api/api-manager";
|
||||
import { crdApi, CustomResourceDefinition } from "../../api/endpoints/crd.api";
|
||||
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu";
|
||||
import { Select, SelectOption } from "../select";
|
||||
import { navigation, setQueryParams } from "../../navigation";
|
||||
import { Icon } from "../icon";
|
||||
|
||||
enum sortBy {
|
||||
kind = "kind",
|
||||
group = "group",
|
||||
version = "version",
|
||||
scope = "scope",
|
||||
age = "age",
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CrdList extends React.Component {
|
||||
@computed get groups() {
|
||||
return navigation.searchParams.getAsArray("groups")
|
||||
}
|
||||
|
||||
onGroupChange(group: string) {
|
||||
const groups = [...this.groups];
|
||||
const index = groups.findIndex(item => item == group);
|
||||
if (index !== -1) groups.splice(index, 1);
|
||||
else groups.push(group);
|
||||
setQueryParams({ groups })
|
||||
}
|
||||
|
||||
render() {
|
||||
const selectedGroups = this.groups;
|
||||
const sortingCallbacks = {
|
||||
[sortBy.kind]: (crd: CustomResourceDefinition) => crd.getResourceKind(),
|
||||
[sortBy.group]: (crd: CustomResourceDefinition) => crd.getGroup(),
|
||||
[sortBy.version]: (crd: CustomResourceDefinition) => crd.getVersion(),
|
||||
[sortBy.scope]: (crd: CustomResourceDefinition) => crd.getScope(),
|
||||
};
|
||||
return (
|
||||
<KubeObjectListLayout
|
||||
className="CrdList"
|
||||
isClusterScoped={true}
|
||||
store={crdStore}
|
||||
sortingCallbacks={sortingCallbacks}
|
||||
searchFilters={Object.values(sortingCallbacks)}
|
||||
filterItems={[
|
||||
(items: CustomResourceDefinition[]) => {
|
||||
return selectedGroups.length ? items.filter(item => selectedGroups.includes(item.getGroup())) : items
|
||||
}
|
||||
]}
|
||||
renderHeaderTitle={<Trans>Custom Resources</Trans>}
|
||||
customizeHeader={() => {
|
||||
let placeholder = <Trans>All groups</Trans>;
|
||||
if (selectedGroups.length == 1) placeholder = <><Trans>Group</Trans>: {selectedGroups[0]}</>
|
||||
if (selectedGroups.length >= 2) placeholder = <><Trans>Groups</Trans>: {selectedGroups.join(", ")}</>
|
||||
return {
|
||||
// fixme: move to global filters
|
||||
filters: (
|
||||
<Select
|
||||
className="group-select"
|
||||
placeholder={placeholder}
|
||||
options={Object.keys(crdStore.groups)}
|
||||
onChange={({ value: group }: SelectOption) => this.onGroupChange(group)}
|
||||
controlShouldRenderValue={false}
|
||||
formatOptionLabel={({ value: group }: SelectOption) => {
|
||||
const isSelected = selectedGroups.includes(group);
|
||||
return (
|
||||
<div className="flex gaps align-center">
|
||||
<Icon small material="folder"/>
|
||||
<span>{group}</span>
|
||||
{isSelected && <Icon small material="check" className="box right"/>}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}}
|
||||
renderTableHeader={[
|
||||
{ title: <Trans>Resource</Trans>, className: "kind", sortBy: sortBy.kind },
|
||||
{ title: <Trans>Group</Trans>, className: "group", sortBy: sortBy.group },
|
||||
{ title: <Trans>Version</Trans>, className: "version", sortBy: sortBy.group },
|
||||
{ title: <Trans>Scope</Trans>, className: "scope", sortBy: sortBy.scope },
|
||||
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
|
||||
]}
|
||||
renderTableContents={(crd: CustomResourceDefinition) => {
|
||||
return [
|
||||
<Link to={crd.getResourceUrl()} onClick={stopPropagation}>
|
||||
{crd.getResourceTitle()}
|
||||
</Link>,
|
||||
crd.getGroup(),
|
||||
crd.getVersion(),
|
||||
crd.getScope(),
|
||||
crd.getAge(),
|
||||
]
|
||||
}}
|
||||
renderItemMenu={(item: CustomResourceDefinition) => {
|
||||
return <CRDMenu object={item}/>
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function CRDMenu(props: KubeObjectMenuProps<CustomResourceDefinition>) {
|
||||
return (
|
||||
<KubeObjectMenu {...props}/>
|
||||
)
|
||||
}
|
||||
|
||||
apiManager.registerViews(crdApi, {
|
||||
Menu: CRDMenu,
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
.CrdResourceDetails {
|
||||
.status {
|
||||
.value {
|
||||
.Badge {
|
||||
&.ready {
|
||||
color: white;
|
||||
background-color: $colorOk;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import "./crd-resource-details.scss";
|
||||
|
||||
import * as React from "react";
|
||||
import jsonPath from "jsonpath";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { observer } from "mobx-react";
|
||||
import { computed } from "mobx";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Badge } from "../badge";
|
||||
import { DrawerItem } from "../drawer";
|
||||
import { KubeObjectDetailsProps } from "../kube-object";
|
||||
import { apiManager } from "../../api/api-manager";
|
||||
import { crdStore } from "./crd.store";
|
||||
import { KubeObject } from "../../api/kube-object";
|
||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||
|
||||
// fixme: provide type-safe check for status
|
||||
interface Props extends KubeObjectDetailsProps<KubeObject & { status: any }> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CrdResourceDetails extends React.Component<Props> {
|
||||
@computed get crd() {
|
||||
return crdStore.getByObject(this.props.object);
|
||||
}
|
||||
|
||||
@computed get CustomDetailsViews() {
|
||||
return apiManager.getViews(this.props.object.selfLink).Details;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { object } = this.props;
|
||||
const { crd, CustomDetailsViews } = this;
|
||||
if (!object || !crd) return null;
|
||||
const className = cssNames("CrdResourceDetails", crd.getResourceKind());
|
||||
const extraColumns = crd.getPrinterColumns();
|
||||
const showStatus = !extraColumns.find(column => column.name == "Status") && object.status?.conditions;
|
||||
if (CustomDetailsViews) {
|
||||
return <CustomDetailsViews className={className} object={object}/>
|
||||
}
|
||||
return (
|
||||
<div className={className}>
|
||||
<KubeObjectMeta object={object}/>
|
||||
{extraColumns.map(column => {
|
||||
const { name } = column;
|
||||
return (
|
||||
<DrawerItem key={name} name={name}>
|
||||
{jsonPath.query(object, column.JSONPath.slice(1))}
|
||||
</DrawerItem>
|
||||
)
|
||||
})}
|
||||
{showStatus && (
|
||||
<DrawerItem name={<Trans>Status</Trans>} className="status" labelsOnly>
|
||||
{object.status.conditions.map((condition: { type: string; message: string; status: string }) => {
|
||||
const { type, message, status } = condition;
|
||||
return (
|
||||
<Badge
|
||||
key={type} label={type}
|
||||
className={cssNames({ disabled: status === "False" }, type.toLowerCase())}
|
||||
tooltip={message}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</DrawerItem>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { autobind } from "../../utils";
|
||||
import { KubeApi } from "../../api/kube-api";
|
||||
import { KubeObjectStore } from "../../kube-object.store";
|
||||
import { KubeObject } from "../../api/kube-object";
|
||||
|
||||
@autobind()
|
||||
export class CRDResourceStore<T extends KubeObject = any> extends KubeObjectStore<T> {
|
||||
api: KubeApi;
|
||||
|
||||
constructor(api: KubeApi<T>) {
|
||||
super();
|
||||
this.api = api;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.CrdResources {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import "./crd-resources.scss";
|
||||
|
||||
import React from "react";
|
||||
import jsonPath from "jsonpath";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { KubeObjectListLayout } from "../kube-object";
|
||||
import { KubeObject } from "../../api/kube-object";
|
||||
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu";
|
||||
import { ICRDRouteParams } from "./crd.route";
|
||||
import { autorun, computed } from "mobx";
|
||||
import { crdStore } from "./crd.store";
|
||||
import { SortingCallback } from "../table";
|
||||
import { apiManager } from "../../api/api-manager";
|
||||
|
||||
interface Props extends RouteComponentProps<ICRDRouteParams> {
|
||||
}
|
||||
|
||||
enum sortBy {
|
||||
name = "name",
|
||||
namespace = "namespace",
|
||||
age = "age",
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CrdResources extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
autorun(() => {
|
||||
const { store } = this;
|
||||
if (store && !store.isLoading && !store.isLoaded) {
|
||||
store.loadAll();
|
||||
}
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
@computed get crd() {
|
||||
const { group, name } = this.props.match.params;
|
||||
return crdStore.getByGroup(group, name);
|
||||
}
|
||||
|
||||
@computed get store() {
|
||||
if (!this.crd) return null
|
||||
return apiManager.getStore(this.crd.getResourceApiBase());
|
||||
}
|
||||
|
||||
render() {
|
||||
const { crd, store } = this;
|
||||
if (!crd) return null;
|
||||
const isNamespaced = crd.isNamespaced();
|
||||
const extraColumns = crd.getPrinterColumns(false); // Cols with priority bigger than 0 are shown in details
|
||||
const sortingCallbacks: { [sortBy: string]: SortingCallback } = {
|
||||
[sortBy.name]: (item: KubeObject) => item.getName(),
|
||||
[sortBy.namespace]: (item: KubeObject) => item.getNs(),
|
||||
[sortBy.age]: (item: KubeObject) => item.getAge(false),
|
||||
}
|
||||
extraColumns.forEach(column => {
|
||||
sortingCallbacks[column.name] = (item: KubeObject) => jsonPath.query(item, column.JSONPath.slice(1))
|
||||
})
|
||||
// todo: merge extra columns and other params to predefined view
|
||||
const { List } = apiManager.getViews(crd.getResourceApiBase());
|
||||
const ListView = List || KubeObjectListLayout;
|
||||
return (
|
||||
<ListView
|
||||
className="CrdResources"
|
||||
isClusterScoped={!isNamespaced}
|
||||
store={store}
|
||||
sortingCallbacks={sortingCallbacks}
|
||||
searchFilters={[
|
||||
(item: KubeObject) => item.getSearchFields(),
|
||||
]}
|
||||
renderHeaderTitle={crd.getResourceTitle()}
|
||||
renderTableHeader={[
|
||||
{ title: <Trans>Name</Trans>, className: "name", sortBy: sortBy.name },
|
||||
isNamespaced && { title: <Trans>Namespace</Trans>, className: "namespace", sortBy: sortBy.namespace },
|
||||
...extraColumns.map(column => {
|
||||
const { name } = column;
|
||||
return {
|
||||
title: name,
|
||||
className: name.toLowerCase(),
|
||||
sortBy: name
|
||||
}
|
||||
}),
|
||||
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
|
||||
]}
|
||||
renderTableContents={(crdInstance: KubeObject) => [
|
||||
crdInstance.getName(),
|
||||
isNamespaced && crdInstance.getNs(),
|
||||
...extraColumns.map(column =>
|
||||
jsonPath.query(crdInstance, column.JSONPath.slice(1))
|
||||
),
|
||||
crdInstance.getAge(),
|
||||
]}
|
||||
renderItemMenu={(item: KubeObject) => {
|
||||
return <CrdResourceMenu object={item}/>
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function CrdResourceMenu(props: KubeObjectMenuProps<KubeObject>) {
|
||||
const { Menu } = apiManager.getViews(props.object.selfLink);
|
||||
if (Menu) {
|
||||
return <Menu {...props}/>
|
||||
}
|
||||
return (
|
||||
<KubeObjectMenu {...props}/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// CRD conditions from here https://github.com/kubernetes/apiextensions-apiserver/blob/master/pkg/apis/apiextensions/types.go
|
||||
$crd-condition-colors: (
|
||||
Established: $colorSuccess,
|
||||
NamesAccepted: $colorOk,
|
||||
NonStructuralSchema: $colorError,
|
||||
Terminating: $colorTerminated,
|
||||
KubernetesAPIApprovalPolicyConformant: $colorWarning
|
||||
);
|
||||
|
||||
@mixin crd-condition-bgc {
|
||||
@each $status, $color in $crd-condition-colors {
|
||||
&.#{$status} {
|
||||
background: $color;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
|
||||
export const crdRoute: RouteProps = {
|
||||
path: "/crd"
|
||||
}
|
||||
|
||||
export const crdDefinitionsRoute: RouteProps = {
|
||||
path: crdRoute.path + "/definitions"
|
||||
}
|
||||
|
||||
export const crdResourcesRoute: RouteProps = {
|
||||
path: crdRoute.path + "/:group/:name"
|
||||
}
|
||||
|
||||
export interface ICRDListQuery {
|
||||
groups?: string;
|
||||
}
|
||||
|
||||
export interface ICRDRouteParams {
|
||||
group: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const crdURL = buildURL<{}, ICRDListQuery>(crdDefinitionsRoute.path);
|
||||
export const crdResourcesURL = buildURL<ICRDRouteParams>(crdResourcesRoute.path);
|
||||
@@ -0,0 +1,77 @@
|
||||
import { computed, reaction } from "mobx";
|
||||
import { KubeObjectStore } from "../../kube-object.store";
|
||||
import { autobind } from "../../utils";
|
||||
import { crdApi, CustomResourceDefinition } from "../../api/endpoints/crd.api";
|
||||
import { apiManager } from "../../api/api-manager";
|
||||
import { KubeApi } from "../../api/kube-api";
|
||||
import { CRDResourceStore } from "./crd-resource.store";
|
||||
import { KubeObject } from "../../api/kube-object";
|
||||
|
||||
@autobind()
|
||||
export class CRDStore extends KubeObjectStore<CustomResourceDefinition> {
|
||||
api = crdApi
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// auto-load crd-s for building sub-menus
|
||||
this.loadAll();
|
||||
|
||||
// auto-init stores for crd-s
|
||||
reaction(() => this.items.toJS(), items => {
|
||||
items.forEach(this.initStore);
|
||||
})
|
||||
}
|
||||
|
||||
protected sortItems(items: CustomResourceDefinition[]) {
|
||||
return super.sortItems(items, [
|
||||
crd => crd.getGroup(),
|
||||
crd => crd.getName(),
|
||||
])
|
||||
}
|
||||
|
||||
protected initStore(crd: CustomResourceDefinition) {
|
||||
const apiBase = crd.getResourceApiBase();
|
||||
let api = apiManager.getApi(apiBase);
|
||||
if (!api) {
|
||||
api = new KubeApi({
|
||||
apiBase: apiBase,
|
||||
kind: crd.getResourceKind(),
|
||||
isNamespaced: crd.isNamespaced(),
|
||||
});
|
||||
}
|
||||
let store = apiManager.getStore(api);
|
||||
if (!store) {
|
||||
store = new CRDResourceStore(api);
|
||||
apiManager.registerStore(api, store);
|
||||
}
|
||||
}
|
||||
|
||||
@computed get groups() {
|
||||
const groups: Record<string, CustomResourceDefinition[]> = {};
|
||||
return this.items.reduce((groups, crd) => {
|
||||
const group = crd.getGroup();
|
||||
if (!groups[group]) groups[group] = [];
|
||||
groups[group].push(crd);
|
||||
return groups;
|
||||
}, groups)
|
||||
}
|
||||
|
||||
getByGroup(group: string, pluralName: string) {
|
||||
const crdInGroup = this.groups[group];
|
||||
if (!crdInGroup) return null;
|
||||
return crdInGroup.find(crd => crd.getPluralName() === pluralName);
|
||||
}
|
||||
|
||||
getByObject(obj: KubeObject) {
|
||||
if (!obj) return null
|
||||
const { kind, apiVersion } = obj;
|
||||
return this.items.find(crd => {
|
||||
return kind === crd.getResourceKind() && apiVersion === `${crd.getGroup()}/${crd.getVersion()}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const crdStore = new CRDStore();
|
||||
|
||||
apiManager.registerStore(crdApi, crdStore);
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Redirect, Route, Switch } from "react-router";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { MainLayout, TabRoute } from "../layout/main-layout";
|
||||
import { crdResourcesRoute, crdRoute, crdURL, crdDefinitionsRoute } from "./crd.route";
|
||||
import { CrdList } from "./crd-list";
|
||||
import { CrdResources } from "./crd-resources";
|
||||
|
||||
// todo: next steps - customization via plugins
|
||||
// todo: list views (rows content), full details view and if possible chart/prometheus hooks
|
||||
|
||||
@observer
|
||||
export class CustomResources extends React.Component {
|
||||
static get tabRoutes(): TabRoute[] {
|
||||
return [
|
||||
{
|
||||
title: <Trans>Definitions</Trans>,
|
||||
component: CustomResources,
|
||||
url: crdURL(),
|
||||
path: crdRoute.path,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MainLayout>
|
||||
<Switch>
|
||||
<Route component={CrdList} {...crdDefinitionsRoute} exact/>
|
||||
<Route component={CrdResources} {...crdResourcesRoute}/>
|
||||
<Redirect to={crdURL()}/>
|
||||
</Switch>
|
||||
</MainLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export * from "./crd.route";
|
||||
export * from "./crd-list";
|
||||
export * from "./crd-details";
|
||||
export * from "./crd-resources";
|
||||
export * from "./crd-resource-details";
|
||||
|
||||
// customized crd-s
|
||||
export * from "./certmanager.k8s.io"
|
||||
Reference in new issue
Block a user