From f74d365ee45f609cefa7452ade24c16709ef1c2a Mon Sep 17 00:00:00 2001 From: Pavel Ashevskiy Date: Fri, 12 Mar 2021 16:40:39 +0400 Subject: [PATCH] YAML Templates in Create Resource dock tab Signed-off-by: Pavel Ashevskiy --- .../components/dock/create-resource.scss | 25 +++++- .../components/dock/create-resource.store.ts | 85 +++++++++++++++++++ .../components/dock/create-resource.tsx | 69 ++++++++++++++- .../access-control/ClusterRole.yaml | 12 +++ .../access-control/ClusterRoleBinding.yaml | 13 +++ .../create-resource/access-control/Role.yaml | 9 ++ .../access-control/RoleBinding.yaml | 17 ++++ .../configuration/ConfigMap.yaml | 17 ++++ .../create-resource/configuration/Secret.yaml | 8 ++ .../k8s-workloads/CronJob.yaml | 19 +++++ .../k8s-workloads/DeamonSet.yaml | 44 ++++++++++ .../k8s-workloads/Deployment.yaml | 21 +++++ .../create-resource/k8s-workloads/Job.yaml | 13 +++ .../k8s-workloads/ReplicaSet.yaml | 21 +++++ .../k8s-workloads/ReplicationController.yaml | 19 +++++ .../k8s-workloads/StatefulSet.yaml | 34 ++++++++ .../create-resource/networking/Ingress.yaml | 17 ++++ .../networking/NetworkPolicy.yaml | 34 ++++++++ .../create-resource/networking/Service.yaml | 11 +++ .../storage/PersistentVolume.yaml | 18 ++++ .../storage/PersistentVolumeClaim.yaml | 17 ++++ 21 files changed, 521 insertions(+), 2 deletions(-) create mode 100644 templates/create-resource/access-control/ClusterRole.yaml create mode 100644 templates/create-resource/access-control/ClusterRoleBinding.yaml create mode 100644 templates/create-resource/access-control/Role.yaml create mode 100644 templates/create-resource/access-control/RoleBinding.yaml create mode 100644 templates/create-resource/configuration/ConfigMap.yaml create mode 100644 templates/create-resource/configuration/Secret.yaml create mode 100644 templates/create-resource/k8s-workloads/CronJob.yaml create mode 100644 templates/create-resource/k8s-workloads/DeamonSet.yaml create mode 100644 templates/create-resource/k8s-workloads/Deployment.yaml create mode 100644 templates/create-resource/k8s-workloads/Job.yaml create mode 100644 templates/create-resource/k8s-workloads/ReplicaSet.yaml create mode 100644 templates/create-resource/k8s-workloads/ReplicationController.yaml create mode 100644 templates/create-resource/k8s-workloads/StatefulSet.yaml create mode 100644 templates/create-resource/networking/Ingress.yaml create mode 100644 templates/create-resource/networking/NetworkPolicy.yaml create mode 100644 templates/create-resource/networking/Service.yaml create mode 100644 templates/create-resource/storage/PersistentVolume.yaml create mode 100644 templates/create-resource/storage/PersistentVolumeClaim.yaml diff --git a/src/renderer/components/dock/create-resource.scss b/src/renderer/components/dock/create-resource.scss index 027b37763d..469045fc8c 100644 --- a/src/renderer/components/dock/create-resource.scss +++ b/src/renderer/components/dock/create-resource.scss @@ -1,2 +1,25 @@ .CreateResource { -} \ No newline at end of file + .Select { + .Select__single-value { + width: 100% + } + &.TemplateSelect { + min-width: 200px; + } + } +} +.select-template-item{ + justify-content: space-between; + align-items: baseline; + + .select-template-badge { + text-transform: none; + font-weight: normal; + } +} + +.select-template-group { + span { + font-size: 13px + } +} diff --git a/src/renderer/components/dock/create-resource.store.ts b/src/renderer/components/dock/create-resource.store.ts index 8933ddf4d0..93154c901b 100644 --- a/src/renderer/components/dock/create-resource.store.ts +++ b/src/renderer/components/dock/create-resource.store.ts @@ -1,14 +1,99 @@ +import fs from "fs-extra"; +import path from "path"; +import os from "os" +import chokidar from "chokidar" +import filehound from "filehound" import { autobind } from "../../utils"; import { DockTabStore } from "./dock-tab.store"; import { dockStore, IDockTab, TabKind } from "./dock.store"; +import { GroupSelectOption, SelectOption} from "../select"; + +export interface BadgedSelectOption extends SelectOption { + badge?: string; +} + +export interface BadgedGroupSelectOption extends GroupSelectOption { + badge?: string; +} + @autobind() export class CreateResourceStore extends DockTabStore { + constructor() { super({ storageKey: "create_resource" }); } + + get lensTemplatesFolder():string { + return path.resolve(__static, "../templates/create-resource"); + } + + get userTemplatesFolder():string { + return path.join(os.homedir(), ".k8slens", "templates"); + } + + + async getTemplates(templatesPath: string):Promise{ + const ungroupedTemplates:SelectOption[] = []; + const templates:GroupSelectOption[] = []; + let groups = await fs.readdir(templatesPath); + await Promise.all(groups.map(async (group) =>{ + const groupAbsPath = path.join(templatesPath, group); + if((await fs.stat(groupAbsPath)).isDirectory()){ + const files = await fs.readdir(groupAbsPath); + const templateSelectOptions = await Promise.all(files.map( + (template) => ({ + value: path.join(groupAbsPath,template), + label: path.parse(template).name + }) + ) + ); + templates.push({label: group.replace("-", " "), options: templateSelectOptions}); + } else { + ungroupedTemplates.push({ value: groupAbsPath, label: path.parse(group).name}); + } + })) + if (ungroupedTemplates.length > 0){ + templates.push({label: "ungrouped", options: ungroupedTemplates}) + } + return templates; + } + + async getMergedTemplates():Promise { + const lensTemplates = await this.getTemplates(this.lensTemplatesFolder); + const userTemplates = await this.getTemplates(this.userTemplatesFolder); + const userGroupOptions:BadgedGroupSelectOption[] = userTemplates.filter( + ({label}) => !lensTemplates.map(({label}) => label.toString().toLowerCase()).includes(label.toString().toLowerCase()) + ).map(({label,options}) => ({label, badge:"user", options})); + // merge lens and user templates within lens groups, then merge lens and user groups + const badgedTemplates:BadgedGroupSelectOption[] = lensTemplates.map( ({label, options}) => { + const userOptions = userTemplates.find(userTemplate => userTemplate.label.toString().toLowerCase() == label.toString().toLowerCase()) + return { + label, + options: userOptions ? options.concat(userOptions.options.map((userOption) => ({label: userOption.label, badge: "user", value: userOption.value}))) : options + }; + }) + return this.orderTemplatesGroups(userGroupOptions).concat(this.orderTemplatesGroups(badgedTemplates)); + } + + async watchUserTemplates(calback: ()=> void){ + + chokidar.watch(this.userTemplatesFolder).on('all', () => { + calback(); + }); + } + + orderTemplatesGroups(templates:BadgedGroupSelectOption[]): BadgedGroupSelectOption[] { + const compare = (a:BadgedGroupSelectOption|BadgedSelectOption, b:BadgedGroupSelectOption|BadgedSelectOption) => { + return a.label.toString() > b.label.toString() ? 1 : a.label.toString() < b.label.toString() ? -1 : 0 + } + return templates.map( group => { + group.options = group.options.sort((a, b) => compare(a,b)); + return group; + }).sort((a, b) => compare(a,b)); + } } export const createResourceStore = new CreateResourceStore(); diff --git a/src/renderer/components/dock/create-resource.tsx b/src/renderer/components/dock/create-resource.tsx index 01e6002309..34fea2cd86 100644 --- a/src/renderer/components/dock/create-resource.tsx +++ b/src/renderer/components/dock/create-resource.tsx @@ -1,17 +1,20 @@ import "./create-resource.scss"; import React from "react"; +import fs from "fs-extra"; +import {Select, GroupSelectOption, SelectOption} from "../select"; import jsYaml from "js-yaml"; import { observable } from "mobx"; import { observer } from "mobx-react"; import { cssNames } from "../../utils"; -import { createResourceStore } from "./create-resource.store"; +import { createResourceStore, BadgedGroupSelectOption } from "./create-resource.store"; import { IDockTab } from "./dock.store"; import { EditorPanel } from "./editor-panel"; import { InfoPanel } from "./info-panel"; import { resourceApplierApi } from "../../api/endpoints/resource-applier.api"; import { JsonApiErrorParsed } from "../../api/json-api"; import { Notifications } from "../notifications"; +import { Badge } from "../badge"; interface Props { className?: string; @@ -21,6 +24,31 @@ interface Props { @observer export class CreateResource extends React.Component { @observable error = ""; + @observable templates:GroupSelectOption[] = []; + + async componentDidMount(){ + createResourceStore.watchUserTemplates(()=> { + this.templates = []; + createResourceStore.getMergedTemplates().then( + badgedTemplates => { + badgedTemplates.map((group) => { + this.templates.push(this.convertBadgedGroup(group)); + }) + }) + }) + } + + + convertBadgedGroup(badgedGroup: BadgedGroupSelectOption):GroupSelectOption { + const options = badgedGroup.options.map(({label, badge, value }) => ({ + label: this.renderSelectOption(label.toString(), badge), + value, + })); + return ({ + label: this.renderSelectGroup(badgedGroup.label.toString(), badgedGroup.badge), + options + }); + } get tabId() { return this.props.tab.id; @@ -35,6 +63,10 @@ export class CreateResource extends React.Component { this.error = error; }; + onSelectTemplate = (item: SelectOption) => { + fs.readFile(item.value,"utf8").then(v => createResourceStore.setData(this.tabId,v)); + }; + create = async () => { if (this.error) return; if (!this.data.trim()) return; // do not save when field is empty @@ -67,6 +99,40 @@ export class CreateResource extends React.Component { return successMessage; }; + renderSelectOption(label: string, badge?: string){ + return this.renderSelectItem(label,"select-template-option", badge ); + } + + renderSelectGroup(label: string, badge?: string){ + return this.renderSelectItem(label,"select-template-group", badge ); + } + + renderSelectItem(label: string, className: string, badge?: string) { + return( +
+ {label} + {badge && } +
+ ); + } + + renderControls(){ + return ( +
+