mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
* Add the ability to sync kube config files - Will update when the files changes - add KUBECONFIG_SYNC label - fix rebase and change to addObservableSource - move UI to user settings - support shallow folder watching - add some unit tests for the diff-er Signed-off-by: Sebastian Malton <sebastian@malton.name> * responding to review comments Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix tests and add try/catch Signed-off-by: Sebastian Malton <sebastian@malton.name> * always sync c&p folder, remove bad rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix preferences Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix settings saving and catalog view Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix unit tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix synced clusters not connectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * change to non-complete shallow watching Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix sizing Signed-off-by: Sebastian Malton <sebastian@malton.name> * Catch readStream errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * don't clear UserStore on non-existant preference field, fix unlinking not removing items from source Signed-off-by: Sebastian Malton <sebastian@malton.name> * change label to file Signed-off-by: Sebastian Malton <sebastian@malton.name>
177 lines
4.7 KiB
TypeScript
177 lines
4.7 KiB
TypeScript
import React from "react";
|
|
import { remote } from "electron";
|
|
import { Avatar, IconButton, List, ListItem, ListItemAvatar, ListItemSecondaryAction, ListItemText, Paper } from "@material-ui/core";
|
|
import { Description, Folder, Delete, HelpOutline } from "@material-ui/icons";
|
|
import { action, computed, observable, reaction } from "mobx";
|
|
import { disposeOnUnmount, observer } from "mobx-react";
|
|
import fse from "fs-extra";
|
|
import { KubeconfigSyncEntry, KubeconfigSyncValue, UserStore } from "../../../common/user-store";
|
|
import { Button } from "../button";
|
|
import { SubTitle } from "../layout/sub-title";
|
|
import { Spinner } from "../spinner";
|
|
import logger from "../../../main/logger";
|
|
import { iter } from "../../utils";
|
|
|
|
interface SyncInfo {
|
|
type: "file" | "folder" | "unknown";
|
|
}
|
|
|
|
interface Entry extends Value {
|
|
filePath: string;
|
|
}
|
|
|
|
interface Value {
|
|
data: KubeconfigSyncValue;
|
|
info: SyncInfo;
|
|
}
|
|
|
|
async function getMapEntry({ filePath, ...data}: KubeconfigSyncEntry): Promise<[string, Value]> {
|
|
try {
|
|
// stat follows the stat(2) linux syscall spec, namely it follows symlinks
|
|
const stats = await fse.stat(filePath);
|
|
|
|
if (stats.isFile()) {
|
|
return [filePath, { info: { type: "file" }, data }];
|
|
}
|
|
|
|
if (stats.isDirectory()) {
|
|
return [filePath, { info: { type: "folder" }, data }];
|
|
}
|
|
|
|
logger.warn("[KubeconfigSyncs]: unknown stat entry", { stats });
|
|
|
|
return [filePath, { info: { type: "unknown" }, data }];
|
|
} catch (error) {
|
|
logger.warn(`[KubeconfigSyncs]: failed to stat entry: ${error}`, { error });
|
|
|
|
return [filePath, { info: { type: "unknown" }, data }];
|
|
}
|
|
}
|
|
|
|
@observer
|
|
export class KubeconfigSyncs extends React.Component {
|
|
syncs = observable.map<string, Value>();
|
|
@observable loaded = false;
|
|
|
|
async componentDidMount() {
|
|
const mapEntries = await Promise.all(
|
|
iter.map(
|
|
UserStore.getInstance().syncKubeconfigEntries,
|
|
([filePath, ...value]) => getMapEntry({ filePath, ...value }),
|
|
),
|
|
);
|
|
|
|
this.syncs.replace(mapEntries);
|
|
this.loaded = true;
|
|
|
|
disposeOnUnmount(this, [
|
|
reaction(() => Array.from(this.syncs.entries(), ([filePath, { data }]) => [filePath, data]), syncs => {
|
|
UserStore.getInstance().syncKubeconfigEntries.replace(syncs);
|
|
})
|
|
]);
|
|
}
|
|
|
|
@computed get syncsList(): Entry[] | undefined {
|
|
if (!this.loaded) {
|
|
return undefined;
|
|
}
|
|
|
|
return Array.from(this.syncs.entries(), ([filePath, value]) => ({ filePath, ...value }));
|
|
}
|
|
|
|
@action
|
|
openFileDialog = async () => {
|
|
const { dialog, BrowserWindow } = remote;
|
|
const { canceled, filePaths } = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), {
|
|
properties: ["openFile", "showHiddenFiles", "multiSelections", "openDirectory"],
|
|
message: "Select kubeconfig file(s) and folder(s)",
|
|
buttonLabel: "Sync",
|
|
});
|
|
|
|
if (canceled) {
|
|
return;
|
|
}
|
|
|
|
const newEntries = await Promise.all(filePaths.map(filePath => getMapEntry({ filePath })));
|
|
|
|
for (const [filePath, info] of newEntries) {
|
|
this.syncs.set(filePath, info);
|
|
}
|
|
};
|
|
|
|
renderEntryIcon(entry: Entry) {
|
|
switch (entry.info.type) {
|
|
case "file":
|
|
return <Description />;
|
|
case "folder":
|
|
return <Folder />;
|
|
case "unknown":
|
|
return <HelpOutline />;
|
|
}
|
|
}
|
|
|
|
renderEntry = (entry: Entry) => {
|
|
return (
|
|
<Paper className="entry" key={entry.filePath} elevation={3}>
|
|
<ListItem>
|
|
<ListItemAvatar>
|
|
<Avatar>
|
|
{this.renderEntryIcon(entry)}
|
|
</Avatar>
|
|
</ListItemAvatar>
|
|
<ListItemText
|
|
primary={entry.filePath}
|
|
className="description"
|
|
/>
|
|
<ListItemSecondaryAction className="action">
|
|
<IconButton
|
|
edge="end"
|
|
aria-label="delete"
|
|
onClick={() => this.syncs.delete(entry.filePath)}
|
|
>
|
|
<Delete />
|
|
</IconButton>
|
|
</ListItemSecondaryAction>
|
|
</ListItem>
|
|
</Paper>
|
|
);
|
|
};
|
|
|
|
renderEntries() {
|
|
const entries = this.syncsList;
|
|
|
|
if (!entries) {
|
|
return (
|
|
<div className="loading-spinner">
|
|
<Spinner />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<List className="kubeconfig-sync-list">
|
|
{entries.map(this.renderEntry)}
|
|
</List>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<>
|
|
<section className="small">
|
|
<SubTitle title="Files and Folders to sync" />
|
|
<Button
|
|
primary
|
|
label="Sync file or folder"
|
|
onClick={() => void this.openFileDialog()}
|
|
/>
|
|
<div className="hint">
|
|
Sync an individual file or all files in a folder (non-recursive).
|
|
</div>
|
|
{this.renderEntries()}
|
|
</section>
|
|
</>
|
|
);
|
|
}
|
|
}
|