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

Fixed merge conflict in mkdocs.yml and fixed typo in contributing/promotion.md

Signed-off-by: Paul Williams <pawilliams@mirantis.com>
This commit is contained in:
Paul Williams 2020-11-10 12:27:24 -08:00
commit c273c0b57c
38 changed files with 282 additions and 182 deletions

View File

@ -8,6 +8,9 @@ jobs:
build:
name: Deploy docs
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- name: Set up Python 3.7
uses: actions/setup-python@v2
@ -32,6 +35,17 @@ jobs:
git config --local user.name "GitHub Action"
git pull
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Generate Extensions API Reference using typedocs
run: |
yarn install
yarn typedocs-extensions-api
for filename in docs/extensions/api/**/*.md; do [ -e "$filename" ] || continue; sed -i '1s/^/---\ntitle: API Reference\n---\n/' $filename; done
- name: mkdocs deploy latest
run: |

1
.gitignore vendored
View File

@ -15,3 +15,4 @@ src/extensions/*/*.d.ts
types/extension-api.d.ts
types/extension-renderer-api.d.ts
extensions/*/dist
docs/extensions/api

View File

@ -1,4 +1,4 @@
# Promotiion
# Promotion
Help promote Lens! If you are not a developer (or even if you are), you can still contribute to the project, a lot, by helping us promote it. As we are a free open source project, the community is our most important asset, so here are some ways that you can help the project continue to grow.

View File

@ -1,3 +0,0 @@
# Lens Extension API Reference
TBD

View File

@ -245,7 +245,7 @@ import { LensRendererExtension } from "@k8slens/extensions";
import { CustomKindDetails, CustomKindDetailsProps } from "./src/custom-kind-details"
export default class ExampleExtension extends LensRendererExtension {
kubeObjectMenuItems = [
kubeObjectDetailItems = [
{
kind: "CustomKind",
apiVersions: ["custom.acme.org/v1"],

View File

@ -35,7 +35,7 @@ nav:
- Testing extensions: extensions/testing-and-publishing/testing.md
- Publishing extensions: extensions/testing-and-publishing/publishing.md
- Bundling extensions: extensions/testing-and-publishing/bundling.md
- API reference: extensions/api/README.md
- API reference: extensions/api/modules/_src_extensions_extension_api_.md
- Contributing:
- Overview: contributing/README.md
- Development: contributing/development.md

View File

@ -2,7 +2,7 @@
"name": "kontena-lens",
"productName": "Lens",
"description": "Lens - The Kubernetes IDE",
"version": "4.0.0-alpha.4",
"version": "4.0.0-alpha.5",
"main": "static/build/main.js",
"copyright": "© 2020, Mirantis, Inc.",
"license": "MIT",
@ -38,7 +38,8 @@
"download:helm": "yarn run ts-node build/download_helm.ts",
"build:tray-icons": "yarn run ts-node build/build_tray_icon.ts",
"lint": "eslint $@ --ext js,ts,tsx --max-warnings=0 src/",
"mkdocs-serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest"
"mkdocs-serve-local": "yarn typedocs-extensions-api && docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest",
"typedocs-extensions-api": "npx typedoc --plugin typedoc-plugin-markdown --readme none --name @k8slens/extensions --ignoreCompilerErrors --out docs/extensions/api --mode modules --excludePrivate --includes src/ src/extensions/extension-api.ts"
},
"config": {
"bundledKubectlVersion": "1.17.11",
@ -280,7 +281,6 @@
"@types/electron-window-state": "^2.0.34",
"@types/fs-extra": "^9.0.1",
"@types/hapi": "^18.0.3",
"@types/hard-source-webpack-plugin": "^1.0.1",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/html-webpack-plugin": "^3.2.3",
"@types/http-proxy": "^1.17.4",
@ -342,7 +342,6 @@
"file-loader": "^6.0.0",
"flex.box": "^3.4.4",
"fork-ts-checker-webpack-plugin": "^5.0.0",
"hard-source-webpack-plugin": "^0.13.1",
"hoist-non-react-statics": "^3.3.2",
"html-webpack-plugin": "^4.3.0",
"identity-obj-proxy": "^3.0.0",
@ -382,6 +381,8 @@
"ts-loader": "^7.0.5",
"ts-node": "^8.10.2",
"type-fest": "^0.18.0",
"typedoc": "^0.19.2",
"typedoc-plugin-markdown": "^3.0.11",
"typeface-roboto": "^0.0.75",
"typescript": "^4.0.2",
"url-loader": "^4.1.0",
@ -389,6 +390,7 @@
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.0",
"webpack-node-externals": "^1.7.2",
"what-input": "^5.2.10",
"xterm": "^4.6.0",
"xterm-addon-fit": "^0.4.0"
}

View File

@ -57,11 +57,12 @@ describe("user store tests", () => {
expect(us.preferences.colorTheme).toBe('light')
})
it("correctly resets theme to default value", () => {
it("correctly resets theme to default value", async () => {
const us = UserStore.getInstance<UserStore>();
us.isLoaded = true;
us.preferences.colorTheme = "some other theme";
us.resetTheme();
await us.resetTheme();
expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme);
})

View File

@ -88,7 +88,8 @@ export class UserStore extends BaseStore<UserStoreModel> {
}
@action
resetTheme() {
async resetTheme() {
await this.whenLoaded;
this.preferences.colorTheme = UserStore.defaultTheme;
}

View File

@ -1,7 +1,8 @@
// Save file to electron app directory (e.g. "/Users/$USER/Library/Application Support/Lens" for MacOS)
import path from "path";
import { app, remote } from "electron";
import { ensureDirSync, writeFileSync, WriteFileOptions } from "fs-extra";
import { ensureDirSync, writeFileSync } from "fs-extra";
import { WriteFileOptions } from "fs"
export function saveToAppFiles(filePath: string, contents: any, options?: WriteFileOptions): string {
const absPath = path.resolve((app || remote.app).getPath("userData"), filePath);

View File

@ -9,7 +9,6 @@ import * as App from "./app"
import * as EventBus from "./event-bus"
import * as Store from "./stores"
import * as Util from "./utils"
import * as Registry from "../registries"
import * as ClusterFeature from "./cluster-feature"
// TODO: allow to expose windowManager.navigate() as Navigation.navigate() in runtime
@ -21,5 +20,4 @@ export {
ClusterFeature,
Store,
Util,
Registry,
}

View File

@ -29,3 +29,4 @@ export { Role, roleApi } from "../../renderer/api/endpoints";
export { RoleBinding, roleBindingApi } from "../../renderer/api/endpoints";
export { ClusterRole, clusterRoleApi } from "../../renderer/api/endpoints";
export { ClusterRoleBinding, clusterRoleBindingApi } from "../../renderer/api/endpoints";
export { CustomResourceDefinition, crdApi } from "../../renderer/api/endpoints";

View File

@ -14,7 +14,7 @@ export class ApiManager {
return this.apis.get(pathOrCallback) || this.apis.get(KubeApi.parseApi(pathOrCallback).apiBase);
}
return Array.from(this.apis.values()).find(pathOrCallback);
return Array.from(this.apis.values()).find(pathOrCallback ?? ((api: KubeApi) => true));
}
registerApi(apiBase: string, api: KubeApi) {

View File

@ -9,7 +9,7 @@ type AdditionalPrinterColumnsCommon = {
description: string;
}
type AdditionalPrinterColumnsV1 = AdditionalPrinterColumnsCommon & {
export type AdditionalPrinterColumnsV1 = AdditionalPrinterColumnsCommon & {
jsonPath: string;
}
@ -120,9 +120,9 @@ export class CustomResourceDefinition extends KubeObject {
return JSON.stringify(this.spec.conversion);
}
getPrinterColumns(ignorePriority = true) {
getPrinterColumns(ignorePriority = true): AdditionalPrinterColumnsV1[] {
const columns = this.spec.versions.find(a => this.getVersion() == a.name)?.additionalPrinterColumns
?? this.spec.additionalPrinterColumns?.map(({JSONPath, ...rest}) => ({ ...rest, jsonPath: JSONPath })) // map to V1 shape
?? this.spec.additionalPrinterColumns?.map(({ JSONPath, ...rest }) => ({ ...rest, jsonPath: JSONPath })) // map to V1 shape
?? [];
return columns
.filter(column => column.name != "Age")
@ -149,4 +149,3 @@ export class CustomResourceDefinition extends KubeObject {
export const crdApi = new VersionedKubeApi<CustomResourceDefinition>({
objectConstructor: CustomResourceDefinition
});

View File

@ -5,6 +5,7 @@ export * from "./cluster.api"
export * from "./cluster-role.api"
export * from "./cluster-role-binding.api"
export * from "./configmap.api"
export * from "./crd.api"
export * from "./cron-job.api"
export * from "./daemon-set.api"
export * from "./deployment.api"

View File

@ -12,67 +12,83 @@ import { KubeObjectDetailsProps } from "../kube-object";
import { crdStore } from "./crd.store";
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { Input } from "../input";
import { CustomResourceDefinition } from "../../api/endpoints/crd.api";
import { AdditionalPrinterColumnsV1, CustomResourceDefinition } from "../../api/endpoints/crd.api";
interface Props extends KubeObjectDetailsProps<CustomResourceDefinition> {
}
function CrdColumnValue({ value }: { value: any[] | {} | string }) {
function convertSpecValue(value: any): any {
if (Array.isArray(value)) {
return <>{value.map((item, index) => <CrdColumnValue key={index} value={item} />)}</>
return value.map(convertSpecValue)
}
if (typeof(value) === 'object') return (
<Input
readOnly
multiLine
theme="round-black"
className="box grow"
value={JSON.stringify(value, null, 2)}
/>
);
return <span>{value}</span>;
if (typeof value === "object") {
return (
<Input
readOnly
multiLine
theme="round-black"
className="box grow"
value={JSON.stringify(value, null, 2)}
/>
)
}
return value
}
@observer
export class CrdResourceDetails extends React.Component<Props> {
@computed get crd() {
return crdStore.getByObject(this.props.object);
}
renderAdditionalColumns(crd: CustomResourceDefinition, columns: AdditionalPrinterColumnsV1[]) {
return columns.map(({ name, jsonPath: jp }) => (
<DrawerItem key={name} name={name} renderBoolean>
{convertSpecValue(jsonPath.value(crd, jp.slice(1)))}
</DrawerItem>
))
}
renderStatus(crd: CustomResourceDefinition, columns: AdditionalPrinterColumnsV1[]) {
const showStatus = !columns.find(column => column.name == "Status") && crd.status?.conditions;
if (!showStatus) {
return null
}
const conditions = crd.status.conditions
.filter(({ type, reason }) => type || reason)
.map(({ type, reason, message, status }) => ({ kind: type || reason, message, status }))
.map(({ kind, message, status }, index) => (
<Badge
key={kind + index} label={kind}
className={cssNames({ disabled: status === "False" }, kind.toLowerCase())}
tooltip={message}
/>
))
return (
<DrawerItem name={<Trans>Status</Trans>} className="status" labelsOnly>
{conditions}
</DrawerItem>
)
}
render() {
const { object } = this.props;
const { crd } = this;
if (!object || !crd) return null;
const { props: { object }, crd } = 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;
return (
<div className={className}>
<KubeObjectMeta object={object}/>
{extraColumns.map(column => {
const { name } = column;
const value = jsonPath.query(object, (column.jsonPath).slice(1));
return (
<DrawerItem key={name} name={name}>
<CrdColumnValue value={value} />
</DrawerItem>
)
})}
{showStatus && (
<DrawerItem name={<Trans>Status</Trans>} className="status" labelsOnly>
{object.status.conditions.map((condition, index) => {
const { type, reason, message, status } = condition;
const kind = type || reason;
if (!kind) return null;
return (
<Badge
key={kind + index} label={kind}
className={cssNames({ disabled: status === "False" }, kind.toLowerCase())}
tooltip={message}
/>
);
})}
</DrawerItem>
)}
<KubeObjectMeta object={object} />
{this.renderAdditionalColumns(object, extraColumns)}
{this.renderStatus(object, extraColumns)}
</div>
)
}

View File

@ -56,11 +56,11 @@ export class CrdResources extends React.Component<Props> {
[sortBy.age]: (item: KubeObject) => item.metadata.creationTimestamp,
}
extraColumns.forEach(column => {
sortingCallbacks[column.name] = (item: KubeObject) => jsonPath.query(item, column.jsonPath.slice(1))
sortingCallbacks[column.name] = (item: KubeObject) => jsonPath.value(item, column.jsonPath.slice(1))
})
const ListView = KubeObjectListLayout;
return (
<ListView
<KubeObjectListLayout
className="CrdResources"
isClusterScoped={!isNamespaced}
store={store}
@ -85,9 +85,10 @@ export class CrdResources extends React.Component<Props> {
renderTableContents={(crdInstance: KubeObject) => [
crdInstance.getName(),
isNamespaced && crdInstance.getNs(),
...extraColumns.map(column => {
return jsonPath.query(crdInstance, (column.jsonPath).slice(1))
}),
...extraColumns.map(column => ({
renderBoolean: true,
children: jsonPath.value(crdInstance, column.jsonPath.slice(1)),
})),
crdInstance.getAge(),
]}
/>

View File

@ -23,7 +23,6 @@
--font-weight-thin: 300;
--font-weight-normal: 400;
--font-weight-bold: 500;
--mainBackground: #1e2124;
--main-layout-header: 40px;
--drag-region-height: 22px
}

View File

@ -38,7 +38,8 @@ import { webFrame } from "electron";
import { clusterPageRegistry } from "../../extensions/registries/page-registry";
import { DynamicPage } from "../../extensions/dynamic-page";
import { extensionLoader } from "../../extensions/extension-loader";
import { appEventBus } from "../../common/event-bus"
import { appEventBus } from "../../common/event-bus";
import whatInput from 'what-input';
@observer
export class App extends React.Component {
@ -57,6 +58,7 @@ export class App extends React.Component {
window.addEventListener("online", () => {
window.location.reload()
})
whatInput.ask() // Start to monitor user input device
}
get startURL() {

View File

@ -1,6 +1,6 @@
import "./drawer-item.scss";
import React from "react";
import { cssNames } from "../../utils";
import { cssNames, displayBooleans } from "../../utils";
export interface DrawerItemProps extends React.HTMLAttributes<any> {
name: React.ReactNode;
@ -8,18 +8,21 @@ export interface DrawerItemProps extends React.HTMLAttributes<any> {
title?: string;
labelsOnly?: boolean;
hidden?: boolean;
renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean"
}
export class DrawerItem extends React.Component<DrawerItemProps> {
render() {
const { name, title, labelsOnly, children, hidden, ...elemProps } = this.props
let { className } = this.props;
const { name, title, labelsOnly, children, hidden, className, renderBoolean, ...elemProps } = this.props
if (hidden) return null
className = cssNames("DrawerItem", className, { labelsOnly });
const classNames = cssNames("DrawerItem", className, { labelsOnly });
const content = displayBooleans(renderBoolean, children)
return (
<div {...elemProps} className={className} title={title}>
<div {...elemProps} className={classNames} title={title}>
<span className="name">{name}</span>
<span className="value">{children}</span>
<span className="value">{content}</span>
</div>
)
}

View File

@ -3,6 +3,7 @@
--size: 50%;
--full-size: 75%;
--spacing: #{$padding * 3};
--icon-focus-color: white;
position: absolute;
background: $contentColor;

View File

@ -5,6 +5,7 @@
--big-size: 32px;
--color-active: #{$iconActiveColor};
--bgc-active: #{$iconActiveBackground};
--focus-color: var(--icon-focus-color, #{$lensBlue});
display: inline-flex;
flex-shrink: 0;
@ -106,7 +107,7 @@
&.active {
color: var(--color-active);
box-shadow: 0 0 0 3px $iconActiveBackground;
box-shadow: 0 0 0 2px $iconActiveBackground;
background-color: $iconActiveBackground;
}
@ -115,8 +116,16 @@
transition: 250ms color, 250ms opacity, 150ms background-color, 150ms box-shadow;
border-radius: 50%;
&.focusable:focus {
@extend .active;
&.focusable:focus:not(:hover) {
box-shadow: 0 0 0 2px var(--focus-color);
[data-whatintent='mouse'] & {
box-shadow: none;
&.active {
box-shadow: 0 0 0 2px $iconActiveBackground;
}
}
}
&:hover {

View File

@ -235,7 +235,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
cellProps.className = cssNames(cellProps.className, headCell.className);
}
}
return <TableCell key={index} {...cellProps}/>
return <TableCell key={index} {...cellProps} />
})
}
{renderItemMenu && (
@ -277,7 +277,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
if (!isReady || !filters.length || hideFilters || !userSettings.showAppliedFilters) {
return;
}
return <PageFiltersList filters={filters}/>
return <PageFiltersList filters={filters} />
}
renderNoItems() {
@ -297,7 +297,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
</NoItems>
)
}
return <NoItems/>
return <NoItems />
}
renderHeaderContent(placeholders: IHeaderPlaceholders): ReactNode {
@ -344,12 +344,12 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
title: <h5 className="title">{title}</h5>,
info: this.renderInfo(),
filters: <>
{!isClusterScoped && <NamespaceSelectFilter/>}
{!isClusterScoped && <NamespaceSelectFilter />}
<PageFiltersSelect allowEmpty disableFilters={{
[FilterType.NAMESPACE]: true, // namespace-select used instead
}}/>
}} />
</>,
search: <SearchInputUrl/>,
search: <SearchInputUrl />,
}
let header = this.renderHeaderContent(placeholders);
if (customizeHeader) {
@ -381,7 +381,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
return (
<div className="items box grow flex column">
{!isReady && (
<Spinner center/>
<Spinner center />
)}
{isReady && (
<Table
@ -406,8 +406,8 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
onClick={prevDefault(() => store.toggleSelectionAll(items))}
/>
)}
{renderTableHeader.map((cellProps, index) => <TableCell key={index} {...cellProps}/>)}
{renderItemMenu && <TableCell className="menu"/>}
{renderTableHeader.map((cellProps, index) => <TableCell key={index} {...cellProps} />)}
{renderItemMenu && <TableCell className="menu" />}
</TableHead>
)}
{

View File

@ -57,11 +57,11 @@ export class KubeObjectMenu extends React.Component<KubeObjectMenuProps> {
render() {
const { remove, update, renderRemoveMessage, isEditable, isRemovable } = this;
const { className, object, editable, removable, ...menuProps } = this.props;
const { className, object, editable, removable, toolbar, ...menuProps } = this.props;
if (!object) return null;
const menuItems = kubeObjectMenuRegistry.getItemsForKind(object.kind, object.apiVersion).map((item, index) => {
return <item.components.MenuItem object={object} key={`menu-item-${index}`} />
return <item.components.MenuItem object={object} key={`menu-item-${index}`} toolbar={toolbar} />
})
return (
<MenuActions
@ -69,6 +69,7 @@ export class KubeObjectMenu extends React.Component<KubeObjectMenuProps> {
updateAction={isEditable ? update : undefined}
removeAction={isRemovable ? remove : undefined}
removeConfirmationMessage={renderRemoveMessage}
toolbar={toolbar}
{...menuProps}
>
{menuItems}

View File

@ -38,6 +38,7 @@ export class MainLayout extends React.Component<MainLayoutProps> {
(sidebarWidth) => this.storage.merge({ sidebarWidth })
);
toggleSidebar = () => {
this.isPinned = !this.isPinned;
this.isAccessible = false;

View File

@ -27,8 +27,9 @@ import { crdStore } from "../+custom-resources/crd.store";
import { CrdList, crdResourcesRoute, crdRoute, crdURL } from "../+custom-resources";
import { CustomResources } from "../+custom-resources/custom-resources";
import { navigation } from "../../navigation";
import { isAllowedResource } from "../../../common/rbac"
import { clusterPageRegistry } from "../../../extensions/registries/page-registry";
import { isAllowedResource } from "../../../common/rbac";
import { Spinner } from "../spinner";
const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false });
type SidebarContextValue = {
@ -50,6 +51,10 @@ export class Sidebar extends React.Component<Props> {
}
renderCustomResources() {
if (crdStore.isLoading) {
return <Spinner centerHorizontal />
}
return Object.entries(crdStore.groups).map(([group, crds]) => {
const submenus = crds.map((crd) => {
return {
@ -80,7 +85,7 @@ export class Sidebar extends React.Component<Props> {
<div className={cssNames("Sidebar flex column", className, { pinned: isPinned })}>
<div className="header flex align-center">
<NavLink exact to="/" className="box grow">
<Icon svg="logo-lens" className="logo-icon"/>
<Icon svg="logo-lens" className="logo-icon" />
<div className="logo-text">Lens</div>
</NavLink>
<Icon
@ -97,14 +102,14 @@ export class Sidebar extends React.Component<Props> {
isHidden={!isAllowedResource("nodes")}
url={clusterURL()}
text={<Trans>Cluster</Trans>}
icon={<Icon svg="kube"/>}
icon={<Icon svg="kube" />}
/>
<SidebarNavItem
id="nodes"
isHidden={!isAllowedResource("nodes")}
url={nodesURL()}
text={<Trans>Nodes</Trans>}
icon={<Icon svg="nodes"/>}
icon={<Icon svg="nodes" />}
/>
<SidebarNavItem
id="workloads"
@ -113,7 +118,7 @@ export class Sidebar extends React.Component<Props> {
routePath={workloadsRoute.path}
subMenus={Workloads.tabRoutes}
text={<Trans>Workloads</Trans>}
icon={<Icon svg="workloads"/>}
icon={<Icon svg="workloads" />}
/>
<SidebarNavItem
id="config"
@ -122,7 +127,7 @@ export class Sidebar extends React.Component<Props> {
routePath={configRoute.path}
subMenus={Config.tabRoutes}
text={<Trans>Configuration</Trans>}
icon={<Icon material="list"/>}
icon={<Icon material="list" />}
/>
<SidebarNavItem
id="networks"
@ -131,7 +136,7 @@ export class Sidebar extends React.Component<Props> {
routePath={networkRoute.path}
subMenus={Network.tabRoutes}
text={<Trans>Network</Trans>}
icon={<Icon material="device_hub"/>}
icon={<Icon material="device_hub" />}
/>
<SidebarNavItem
id="storage"
@ -139,14 +144,14 @@ export class Sidebar extends React.Component<Props> {
url={storageURL({ query })}
routePath={storageRoute.path}
subMenus={Storage.tabRoutes}
icon={<Icon svg="storage"/>}
icon={<Icon svg="storage" />}
text={<Trans>Storage</Trans>}
/>
<SidebarNavItem
id="namespaces"
isHidden={!isAllowedResource("namespaces")}
url={namespacesURL()}
icon={<Icon material="layers"/>}
icon={<Icon material="layers" />}
text={<Trans>Namespaces</Trans>}
/>
<SidebarNavItem
@ -154,7 +159,7 @@ export class Sidebar extends React.Component<Props> {
isHidden={!isAllowedResource("events")}
url={eventsURL({ query })}
routePath={eventRoute.path}
icon={<Icon material="access_time"/>}
icon={<Icon material="access_time" />}
text={<Trans>Events</Trans>}
/>
<SidebarNavItem
@ -162,7 +167,7 @@ export class Sidebar extends React.Component<Props> {
url={appsURL({ query })}
subMenus={Apps.tabRoutes}
routePath={appsRoute.path}
icon={<Icon material="apps"/>}
icon={<Icon material="apps" />}
text={<Trans>Apps</Trans>}
/>
<SidebarNavItem
@ -170,7 +175,7 @@ export class Sidebar extends React.Component<Props> {
url={usersManagementURL({ query })}
routePath={usersManagementRoute.path}
subMenus={UserManagement.tabRoutes}
icon={<Icon material="security"/>}
icon={<Icon material="security" />}
text={<Trans>Access Control</Trans>}
/>
<SidebarNavItem
@ -179,7 +184,7 @@ export class Sidebar extends React.Component<Props> {
url={crdURL()}
subMenus={CustomResources.tabRoutes}
routePath={crdRoute.path}
icon={<Icon material="extension"/>}
icon={<Icon material="extension" />}
text={<Trans>Custom Resources</Trans>}
>
{this.renderCustomResources()}
@ -194,7 +199,7 @@ export class Sidebar extends React.Component<Props> {
url={url}
routePath={path}
text={title}
icon={<MenuIcon/>}
icon={<MenuIcon />}
/>
)
})}
@ -257,7 +262,7 @@ class SidebarNavItem extends React.Component<SidebarNavItemProps> {
<div className={cssNames("nav-item", { active: isActive })} onClick={this.toggleSubMenu}>
{icon}
<span className="link-text">{text}</span>
<Icon className="expand-icon" material={this.isExpanded ? "keyboard_arrow_up" : "keyboard_arrow_down"}/>
<Icon className="expand-icon" material={this.isExpanded ? "keyboard_arrow_up" : "keyboard_arrow_down"} />
</div>
<ul className={cssNames("sub-menu", { active: isActive })}>
{subMenus.map(({ title, url }) => (

View File

@ -34,6 +34,12 @@
margin-top: calc(var(--spinner-size) / -2);
}
&.centerHorizontal {
position: absolute;
left: 50%;
margin-left: calc(var(--spinner-size) / -2);
}
@keyframes rotate {
0% {
transform: rotate(0deg);

View File

@ -6,23 +6,19 @@ import { cssNames } from "../../utils";
export interface SpinnerProps extends React.HTMLProps<any> {
singleColor?: boolean;
center?: boolean;
centerHorizontal?: boolean;
}
export class Spinner extends React.Component<SpinnerProps, {}> {
private elem: HTMLElement;
static defaultProps = {
singleColor: true,
center: false,
};
render() {
const { center, singleColor, ...props } = this.props;
let { className } = this.props;
className = cssNames('Spinner', className, {
singleColor: singleColor,
center: center,
});
return <div {...props} className={className} ref={e => this.elem = e}/>;
const { center, singleColor, centerHorizontal, className, ...props } = this.props;
const classNames = cssNames('Spinner', className, { singleColor, center, centerHorizontal });
return <div {...props} className={classNames} />;
}
}

View File

@ -2,7 +2,7 @@ import "./table-cell.scss";
import type { TableSortBy, TableSortParams } from "./table";
import React, { ReactNode } from "react";
import { autobind, cssNames } from "../../utils";
import { autobind, cssNames, displayBooleans } from "../../utils";
import { Icon } from "../icon";
import { Checkbox } from "../checkbox";
@ -13,6 +13,7 @@ export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
title?: ReactNode;
checkbox?: boolean; // render cell with a checkbox
isChecked?: boolean; // mark checkbox as checked or not
renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean"
sortBy?: TableSortBy; // column name, must be same as key in sortable object <Table sortable={}/>
_sorting?: Partial<TableSortParams>; // <Table> sorting state, don't use this prop outside (!)
_sort?(sortBy: TableSortBy): void; // <Table> sort function, don't use this prop outside (!)
@ -52,20 +53,20 @@ export class TableCell extends React.Component<TableCellProps> {
const { checkbox, isChecked } = this.props;
const showCheckbox = isChecked !== undefined;
if (checkbox && showCheckbox) {
return <Checkbox value={isChecked}/>
return <Checkbox value={isChecked} />
}
}
render() {
const { className, checkbox, isChecked, sortBy, _sort, _sorting, _nowrap, children, title, ...cellProps } = this.props;
const { className, checkbox, isChecked, sortBy, _sort, _sorting, _nowrap, children, title, renderBoolean: displayBoolean, ...cellProps } = this.props;
const classNames = cssNames("TableCell", className, {
checkbox: checkbox,
nowrap: _nowrap,
sorting: this.isSortable,
});
const content = title || children;
const content = displayBooleans(displayBoolean, title || children)
return (
<div {...cellProps} className={classNames} onClick={this.onClick}>
<div {...cellProps} id={className} className={classNames} onClick={this.onClick}>
{this.renderCheckbox()}
{_nowrap ? <div className="content">{content}</div> : content}
{this.renderSortIcon()}

View File

@ -48,6 +48,7 @@ export class ThemeStore {
await this.loadTheme(themeId);
this.applyTheme();
} catch (err) {
logger.error(err);
userStore.resetTheme();
}
}, {
@ -79,7 +80,7 @@ export class ThemeStore {
}
return existingTheme;
} catch (err) {
logger.error(`Can't load theme "${themeId}": ${err}`);
throw new Error(`Can't load theme "${themeId}": ${err}`);
}
}
@ -90,7 +91,7 @@ export class ThemeStore {
document.head.prepend(this.styles);
}
const cssVars = Object.entries(theme.colors).map(([cssName, color]) => {
return `--${cssName}: ${color} !important;`
return `--${cssName}: ${color};`;
});
this.styles.textContent = `:root {\n${cssVars.join("\n")}}`;
// Adding universal theme flag which can be used in component styles

View File

@ -0,0 +1,18 @@
import React from "react"
import { displayBooleans } from "../display-booleans"
describe("displayBooleans tests", () => {
it("should not do anything to div's if shouldShow is false", () => {
expect(displayBooleans(false, <div></div>)).toStrictEqual(<div></div>)
})
it("should not do anything to booleans's if shouldShow is false", () => {
expect(displayBooleans(false, true)).toStrictEqual(true)
expect(displayBooleans(false, false)).toStrictEqual(false)
})
it("should stringify booleans when shouldShow is true", () => {
expect(displayBooleans(true, true)).toStrictEqual("true")
expect(displayBooleans(true, false)).toStrictEqual("false")
})
})

View File

@ -0,0 +1,15 @@
import React from "react"
export function displayBooleans(shouldShow: boolean, from: React.ReactNode): React.ReactNode {
if (shouldShow) {
if (typeof from === "boolean") {
return from.toString()
}
if (Array.isArray(from)) {
return from.map(node => displayBooleans(shouldShow, node))
}
}
return from
}

View File

@ -18,3 +18,4 @@ export * from "./isReactNode"
export * from "./convertMemory"
export * from "./convertCpu"
export * from "./metricUnitsToNumber"
export * from "./display-booleans"

View File

@ -2,15 +2,17 @@
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
## 4.0.0-alpha.4 (current version)
## 4.0.0-alpha.5 (current version)
- Extension API
- Improved pod logs
- Mechanism for users to specify accessible namespaces
- Tray icon
- Add last-status information for container
- Add LoadBalancer information to Ingress view
- Move tracker to an extension
- Add support page (as an extension)
- Ability to restart deployment
- Status bar visual fixes
- Fix proxy upgrade socket timeouts
- Fix UI staleness after network issues

View File

@ -4,7 +4,11 @@
"jsx": "react",
"target": "ES2017",
"module": "ESNext",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"lib": [
"ESNext",
"DOM",
"DOM.Iterable"
],
"moduleResolution": "Node",
"sourceMap": true,
"strict": false,

View File

@ -4,7 +4,6 @@ import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin"
import { isDevelopment, isProduction, mainDir, buildDir } from "./src/common/vars";
import nodeExternals from "webpack-node-externals";
import ProgressBarPlugin from "progress-bar-webpack-plugin";
import HardSourceWebpackPlugin from 'hard-source-webpack-plugin';
export default function (): webpack.Configuration {
console.info('WEBPACK:main', require("./src/common/vars"))
@ -48,7 +47,6 @@ export default function (): webpack.Configuration {
plugins: [
new ProgressBarPlugin(),
new ForkTsCheckerPlugin(),
isDevelopment && new HardSourceWebpackPlugin(),
].filter(Boolean)
}
}

View File

@ -6,7 +6,6 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin";
import TerserPlugin from "terser-webpack-plugin";
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin"
import ProgressBarPlugin from "progress-bar-webpack-plugin";
import HardSourceWebpackPlugin from 'hard-source-webpack-plugin';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'
export default [
@ -187,7 +186,6 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
filename: "[name].css",
}),
isDevelopment && new HardSourceWebpackPlugin(),
isDevelopment && new webpack.HotModuleReplacementPlugin(),
isDevelopment && new ReactRefreshWebpackPlugin(),

106
yarn.lock
View File

@ -1967,13 +1967,6 @@
"@types/podium" "*"
"@types/shot" "*"
"@types/hard-source-webpack-plugin@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/hard-source-webpack-plugin/-/hard-source-webpack-plugin-1.0.1.tgz#4aecca35bafb7939bcf318bbf5a2710c3163cdd4"
integrity sha512-5eTPERZQj5RZLADk5o2Ip/XRLwgxOUeKzlIM3+czrhwA9pnVJAUYOm2fovbxkrIEQhuozQwc17fyH1ZXnSR/8g==
dependencies:
"@types/webpack" "*"
"@types/history@*", "@types/history@^4.7.3":
version "4.7.6"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.6.tgz#ed8fc802c45b8e8f54419c2d054e55c9ea344356"
@ -5204,7 +5197,7 @@ detect-file@^1.0.0:
resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
detect-indent@^5.0.0, detect-indent@~5.0.0:
detect-indent@~5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
@ -6359,7 +6352,7 @@ finalhandler@~1.1.2:
statuses "~1.5.0"
unpipe "~1.0.0"
find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
find-cache-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
@ -7035,25 +7028,6 @@ har-validator@~5.1.3:
ajv "^6.5.5"
har-schema "^2.0.0"
hard-source-webpack-plugin@^0.13.1:
version "0.13.1"
resolved "https://registry.yarnpkg.com/hard-source-webpack-plugin/-/hard-source-webpack-plugin-0.13.1.tgz#a99071e25b232f1438a5bc3c99f10a3869e4428e"
integrity sha512-r9zf5Wq7IqJHdVAQsZ4OP+dcUSvoHqDMxJlIzaE2J0TZWn3UjMMrHqwDHR8Jr/pzPfG7XxSe36E7Y8QGNdtuAw==
dependencies:
chalk "^2.4.1"
find-cache-dir "^2.0.0"
graceful-fs "^4.1.11"
lodash "^4.15.0"
mkdirp "^0.5.1"
node-object-hash "^1.2.0"
parse-json "^4.0.0"
pkg-dir "^3.0.0"
rimraf "^2.6.2"
semver "^5.6.0"
tapable "^1.0.0-beta.5"
webpack-sources "^1.0.1"
write-json-file "^2.3.0"
harmony-reflect@^1.4.6:
version "1.6.1"
resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.1.tgz#c108d4f2bb451efef7a37861fdbdae72c9bdefa9"
@ -7168,6 +7142,11 @@ he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
highlight.js@^10.2.0:
version "10.3.2"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.3.2.tgz#135fd3619a00c3cbb8b4cd6dbc78d56bfcbc46f1"
integrity sha512-3jRT7OUYsVsKvukNKZCtnvRcFyCJqSEIuIMsEybAXRiFSwpt65qjPd/Pr+UOdYt7WJlt+lj3+ypUsHiySBp/Jw==
history@^4.10.1, history@^4.9.0:
version "4.10.1"
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
@ -9317,7 +9296,7 @@ lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.1
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
lodash@^4.15.0, lodash@^4.17.19:
lodash@^4.17.19, lodash@^4.17.20:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
@ -9402,6 +9381,11 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
lunr@^2.3.9:
version "2.3.9"
resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1"
integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==
lz-string@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
@ -9499,6 +9483,11 @@ marked@^1.1.0:
resolved "https://registry.yarnpkg.com/marked/-/marked-1.1.0.tgz#62504ad4d11550c942935ccc5e39d64e5a4c4e50"
integrity sha512-EkE7RW6KcXfMHy2PA7Jg0YJE1l8UPEZE8k45tylzmZM30/r1M1MUXWQfJlrSbsTeh7m/XTwHbWUENvAJZpp1YA==
marked@^1.1.1:
version "1.2.3"
resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.3.tgz#58817ba348a7c9398cb94d40d12e0d08df83af57"
integrity sha512-RQuL2i6I6Gn+9n81IDNGbL0VHnta4a+8ZhqvryXEniTb/hQNtf3i26hi1XWUhzb9BgVyWHKR3UO8MaHtKoYibw==
matcher@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca"
@ -9740,7 +9729,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
minimatch@^3.0.4, minimatch@~3.0.2:
minimatch@^3.0.0, minimatch@^3.0.4, minimatch@~3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@ -10150,11 +10139,6 @@ node-notifier@^7.0.0:
uuid "^7.0.3"
which "^2.0.2"
node-object-hash@^1.2.0:
version "1.4.2"
resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-1.4.2.tgz#385833d85b229902b75826224f6077be969a9e94"
integrity sha512-UdS4swXs85fCGWWf6t6DMGgpN/vnlKeSGEQ7hJcrs7PBFoxoKLmibc3QRb7fwiYsjdL7PX8iI/TMSlZ90dgHhQ==
node-pty@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0.tgz#8f9bcc0d1c5b970a3184ffd533d862c7eb6590a6"
@ -12921,7 +12905,7 @@ shell-env@^3.0.0:
execa "^1.0.0"
strip-ansi "^5.2.0"
shelljs@^0.8.2:
shelljs@^0.8.2, shelljs@^0.8.4:
version "0.8.4"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2"
integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==
@ -13673,7 +13657,7 @@ table@^5.2.3:
slice-ansi "^2.1.0"
string-width "^3.0.0"
tapable@^1.0.0, tapable@^1.0.0-beta.5, tapable@^1.1.3:
tapable@^1.0.0, tapable@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
@ -14196,6 +14180,35 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typedoc-default-themes@^0.11.4:
version "0.11.4"
resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.11.4.tgz#1bc55b7c8d1132844616ff6f570e1e2cd0eb7343"
integrity sha512-Y4Lf+qIb9NTydrexlazAM46SSLrmrQRqWiD52593g53SsmUFioAsMWt8m834J6qsp+7wHRjxCXSZeiiW5cMUdw==
typedoc-plugin-markdown@^3.0.11:
version "3.0.11"
resolved "https://registry.yarnpkg.com/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.0.11.tgz#358c32f4a0086c1dd2da7f56c4b46ade8a63204b"
integrity sha512-/BE/PqnIVbQJ525czM+T3CVaA1gVN9X1Le100z8TV/Lze8LZVkuAUiHRIgw9BKYFm9IQaB88W55k4EV6uUVwYQ==
dependencies:
handlebars "^4.7.6"
typedoc@^0.19.2:
version "0.19.2"
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.19.2.tgz#842a63a581f4920f76b0346bb80eb2a49afc2c28"
integrity sha512-oDEg1BLEzi1qvgdQXc658EYgJ5qJLVSeZ0hQ57Eq4JXy6Vj2VX4RVo18qYxRWz75ifAaYuYNBUCnbhjd37TfOg==
dependencies:
fs-extra "^9.0.1"
handlebars "^4.7.6"
highlight.js "^10.2.0"
lodash "^4.17.20"
lunr "^2.3.9"
marked "^1.1.1"
minimatch "^3.0.0"
progress "^2.0.3"
semver "^7.3.2"
shelljs "^0.8.4"
typedoc-default-themes "^0.11.4"
typeface-roboto@^0.0.75:
version "0.0.75"
resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-0.0.75.tgz#98d5ba35ec234bbc7172374c8297277099cc712b"
@ -14743,7 +14756,7 @@ webpack-node-externals@^1.7.2:
resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz#6e1ee79ac67c070402ba700ef033a9b8d52ac4e3"
integrity sha512-ajerHZ+BJKeCLviLUUmnyd5B4RavLF76uv3cs6KNuO8W+HuQaEs0y0L7o40NQxdPy5w0pcv8Ew7yPUAQG0UdCg==
webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
@ -14806,6 +14819,11 @@ wgxpath@~1.0.0:
resolved "https://registry.yarnpkg.com/wgxpath/-/wgxpath-1.0.0.tgz#eef8a4b9d558cc495ad3a9a2b751597ecd9af690"
integrity sha1-7vikudVYzEla06mit1FZfs2a9pA=
what-input@^5.2.10:
version "5.2.10"
resolved "https://registry.yarnpkg.com/what-input/-/what-input-5.2.10.tgz#f79f5b65cf95d75e55e6d580bb0a6b98174cad4e"
integrity sha512-7AQoIMGq7uU8esmKniOtZG3A+pzlwgeyFpkS3f/yzRbxknSL68tvn5gjE6bZ4OMFxCPjpaBd2udUTqlZ0HwrXQ==
whatwg-encoding@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
@ -14985,18 +15003,6 @@ write-file-atomic@^3.0.0, write-file-atomic@^3.0.3:
signal-exit "^3.0.2"
typedarray-to-buffer "^3.1.5"
write-json-file@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.3.0.tgz#2b64c8a33004d54b8698c76d585a77ceb61da32f"
integrity sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=
dependencies:
detect-indent "^5.0.0"
graceful-fs "^4.1.2"
make-dir "^1.0.0"
pify "^3.0.0"
sort-keys "^2.0.0"
write-file-atomic "^2.0.0"
write@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"