mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Add eslint rule padding-line-between-statements (#1593)
Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com>
This commit is contained in:
1 parent
7b77f18376
commit
dcf253e7d5
401 files changed
+2018
-40
No files matched your search
@@ -44,6 +44,7 @@ export class CreateResource extends React.Component<Props> {
|
||||
.filter(v => !!v); // skip empty documents if "---" pasted at the beginning or end
|
||||
const createdResources: string[] = [];
|
||||
const errors: string[] = [];
|
||||
|
||||
await Promise.all(
|
||||
resources.map(data => {
|
||||
return resourceApplierApi.update(data)
|
||||
@@ -51,6 +52,7 @@ export class CreateResource extends React.Component<Props> {
|
||||
.catch((err: JsonApiErrorParsed) => errors.push(err.toString()));
|
||||
})
|
||||
);
|
||||
|
||||
if (errors.length) {
|
||||
errors.forEach(Notifications.error);
|
||||
if (!createdResources.length) throw errors[0];
|
||||
@@ -61,13 +63,16 @@ export class CreateResource extends React.Component<Props> {
|
||||
<Trans><b>{createdResources.join(", ")}</b> successfully created</Trans>
|
||||
</p>
|
||||
);
|
||||
|
||||
Notifications.ok(successMessage);
|
||||
|
||||
return successMessage;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { tabId, data, error, create, onChange } = this;
|
||||
const { className } = this.props;
|
||||
|
||||
return (
|
||||
<div className={cssNames("CreateResource flex column", className)}>
|
||||
<InfoPanel
|
||||
|
||||
@@ -17,6 +17,7 @@ export class DockTabStore<T = any> {
|
||||
// auto-save to local-storage
|
||||
if (storageName) {
|
||||
const storage = createStorage<[TabId, T][]>(storageName, []);
|
||||
|
||||
this.data.replace(storage.get());
|
||||
reaction(() => this.serializeData(), (data: T | any) => storage.set(data));
|
||||
}
|
||||
@@ -24,6 +25,7 @@ export class DockTabStore<T = any> {
|
||||
// clear data for closed tabs
|
||||
autorun(() => {
|
||||
const currentTabs = dockStore.tabs.map(tab => tab.id);
|
||||
|
||||
Array.from(this.data.keys()).forEach(tabId => {
|
||||
if (!currentTabs.includes(tabId)) {
|
||||
this.clearData(tabId);
|
||||
@@ -34,8 +36,10 @@ export class DockTabStore<T = any> {
|
||||
|
||||
protected serializeData() {
|
||||
const { storageSerializer } = this.options;
|
||||
|
||||
return Array.from(this.data).map(([tabId, tabData]) => {
|
||||
if (storageSerializer) return [tabId, storageSerializer(tabData)];
|
||||
|
||||
return [tabId, tabData];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ export class DockTab extends React.Component<DockTabProps> {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tab
|
||||
{...tabProps}
|
||||
|
||||
@@ -51,6 +51,7 @@ export class DockStore {
|
||||
const mainLayoutMargin = 16;
|
||||
const dockTabs = 33;
|
||||
const preferedMax = window.innerHeight - mainLayoutHeader - mainLayoutTabs - mainLayoutMargin - dockTabs;
|
||||
|
||||
return Math.max(preferedMax, this.minHeight); // don't let max < min
|
||||
}
|
||||
|
||||
@@ -74,6 +75,7 @@ export class DockStore {
|
||||
if (!this.height) {
|
||||
this.setHeight(this.defaultHeight || this.minHeight);
|
||||
}
|
||||
|
||||
if (this.height > this.maxHeight) {
|
||||
this.setHeight(this.maxHeight);
|
||||
}
|
||||
@@ -94,6 +96,7 @@ export class DockStore {
|
||||
@action
|
||||
open(fullSize?: boolean) {
|
||||
this.isOpen = true;
|
||||
|
||||
if (typeof fullSize === "boolean") {
|
||||
this.fullSize = fullSize;
|
||||
}
|
||||
@@ -125,8 +128,10 @@ export class DockStore {
|
||||
.filter(tab => tab.kind === kind)
|
||||
.map(tab => {
|
||||
const tabNumber = +tab.title.match(/\d+/);
|
||||
|
||||
return tabNumber === 0 ? 1 : tabNumber; // tab without a number is first
|
||||
});
|
||||
|
||||
for (let i = 1; ; i++) {
|
||||
if (!tabNumbers.includes(i)) return i;
|
||||
}
|
||||
@@ -136,29 +141,36 @@ export class DockStore {
|
||||
createTab(anonTab: IDockTab, addNumber = true): IDockTab {
|
||||
const tabId = MD5(Math.random().toString() + Date.now()).toString();
|
||||
const tab: IDockTab = { id: tabId, ...anonTab };
|
||||
|
||||
if (addNumber) {
|
||||
const tabNumber = this.getNewTabNumber(tab.kind);
|
||||
|
||||
if (tabNumber > 1) tab.title += ` (${tabNumber})`;
|
||||
}
|
||||
this.tabs.push(tab);
|
||||
this.selectTab(tab.id);
|
||||
this.open();
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
@action
|
||||
async closeTab(tabId: TabId) {
|
||||
const tab = this.getTabById(tabId);
|
||||
|
||||
if (!tab || tab.pinned) {
|
||||
return;
|
||||
}
|
||||
this.tabs.remove(tab);
|
||||
|
||||
if (this.selectedTabId === tab.id) {
|
||||
if (this.tabs.length) {
|
||||
const newTab = this.tabs.slice(-1)[0]; // last
|
||||
|
||||
if (newTab.kind === TabKind.TERMINAL) {
|
||||
// close the dock when selected sibling inactive terminal tab
|
||||
const { terminalStore } = await import("./terminal.store");
|
||||
|
||||
if (!terminalStore.isConnected(newTab.id)) this.close();
|
||||
}
|
||||
this.selectTab(newTab.id);
|
||||
|
||||
@@ -33,11 +33,14 @@ interface Props {
|
||||
export class Dock extends React.Component<Props> {
|
||||
onKeydown = (evt: React.KeyboardEvent<HTMLElement>) => {
|
||||
const { close, closeTab, selectedTab } = dockStore;
|
||||
|
||||
if (!selectedTab) return;
|
||||
const { code, ctrlKey, shiftKey } = evt.nativeEvent;
|
||||
|
||||
if (shiftKey && code === "Escape") {
|
||||
close();
|
||||
}
|
||||
|
||||
if (ctrlKey && code === "KeyW") {
|
||||
if (selectedTab.pinned) close();
|
||||
else closeTab(selectedTab.id);
|
||||
@@ -46,6 +49,7 @@ export class Dock extends React.Component<Props> {
|
||||
|
||||
onChangeTab = (tab: IDockTab) => {
|
||||
const { open, selectTab } = dockStore;
|
||||
|
||||
open();
|
||||
selectTab(tab.id);
|
||||
};
|
||||
@@ -55,12 +59,15 @@ export class Dock extends React.Component<Props> {
|
||||
if (isTerminalTab(tab)) {
|
||||
return <TerminalTab value={tab} />;
|
||||
}
|
||||
|
||||
if (isCreateResourceTab(tab) || isEditResourceTab(tab)) {
|
||||
return <DockTab value={tab} icon="edit" />;
|
||||
}
|
||||
|
||||
if (isInstallChartTab(tab) || isUpgradeChartTab(tab)) {
|
||||
return <DockTab value={tab} icon={<Icon svg="install" />} />;
|
||||
}
|
||||
|
||||
if (isPodLogsTab(tab)) {
|
||||
return <DockTab value={tab} icon="subject" />;
|
||||
}
|
||||
@@ -68,7 +75,9 @@ export class Dock extends React.Component<Props> {
|
||||
|
||||
renderTabContent() {
|
||||
const { isOpen, height, selectedTab: tab } = dockStore;
|
||||
|
||||
if (!isOpen || !tab) return;
|
||||
|
||||
return (
|
||||
<div className="tab-content" style={{ flexBasis: height }}>
|
||||
{isCreateResourceTab(tab) && <CreateResource tab={tab} />}
|
||||
@@ -84,6 +93,7 @@ export class Dock extends React.Component<Props> {
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
const { isOpen, toggle, tabs, toggleFillSize, selectedTab, hasTabs, fullSize } = dockStore;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cssNames("Dock", className, { isOpen, fullSize })}
|
||||
|
||||
@@ -27,9 +27,11 @@ export class EditResourceStore extends DockTabStore<KubeEditResource> {
|
||||
}
|
||||
this.watchers.set(tabId, autorun(() => {
|
||||
const store = apiManager.getStore(resource);
|
||||
|
||||
if (store) {
|
||||
const isActiveTab = dockStore.isOpen && dockStore.selectedTabId === tabId;
|
||||
const obj = store.getByPath(resource);
|
||||
|
||||
// preload resource for editing
|
||||
if (!obj && !store.isLoaded && !store.isLoading && isActiveTab) {
|
||||
store.loadFromPath(resource).catch(noop);
|
||||
@@ -50,6 +52,7 @@ export class EditResourceStore extends DockTabStore<KubeEditResource> {
|
||||
const [tabId] = Array.from(this.data).find(([, { resource }]) => {
|
||||
return object.selfLink === resource;
|
||||
}) || [];
|
||||
|
||||
return dockStore.getTabById(tabId);
|
||||
}
|
||||
|
||||
@@ -67,10 +70,12 @@ export const editResourceStore = new EditResourceStore();
|
||||
export function editResourceTab(object: KubeObject, tabParams: Partial<IDockTab> = {}) {
|
||||
// use existing tab if already opened
|
||||
let tab = editResourceStore.getTabByResource(object);
|
||||
|
||||
if (tab) {
|
||||
dockStore.open();
|
||||
dockStore.selectTab(tab.id);
|
||||
}
|
||||
|
||||
// or create new tab
|
||||
if (!tab) {
|
||||
tab = dockStore.createTab({
|
||||
@@ -82,6 +87,7 @@ export function editResourceTab(object: KubeObject, tabParams: Partial<IDockTab>
|
||||
resource: object.selfLink,
|
||||
});
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export class EditResource extends React.Component<Props> {
|
||||
@disposeOnUnmount
|
||||
autoDumpResourceOnInit = autorun(() => {
|
||||
if (!this.tabData) return;
|
||||
|
||||
if (this.tabData.draft === undefined && this.resource) {
|
||||
this.saveDraft(this.resource);
|
||||
}
|
||||
@@ -44,6 +45,7 @@ export class EditResource extends React.Component<Props> {
|
||||
get resource(): KubeObject {
|
||||
const { resource } = this.tabData;
|
||||
const store = apiManager.getStore(resource);
|
||||
|
||||
if (store) {
|
||||
return store.getByPath(resource);
|
||||
}
|
||||
@@ -71,9 +73,11 @@ export class EditResource extends React.Component<Props> {
|
||||
const { resource, draft } = this.tabData;
|
||||
const store = apiManager.getStore(resource);
|
||||
const updatedResource = await store.update(this.resource, jsYaml.safeLoad(draft));
|
||||
|
||||
this.saveDraft(updatedResource); // update with new resourceVersion to avoid further errors on save
|
||||
const resourceType = updatedResource.kind;
|
||||
const resourceName = updatedResource.getName();
|
||||
|
||||
return (
|
||||
<p>
|
||||
<Trans>{resourceType} <b>{resourceName}</b> updated.</Trans>
|
||||
@@ -84,10 +88,12 @@ export class EditResource extends React.Component<Props> {
|
||||
render() {
|
||||
const { tabId, resource, tabData, error, onChange, save } = this;
|
||||
const { draft } = tabData;
|
||||
|
||||
if (!resource || draft === undefined) {
|
||||
return <Spinner center/>;
|
||||
}
|
||||
const { kind, getNs, getName } = resource;
|
||||
|
||||
return (
|
||||
<div className={cssNames("EditResource flex column", this.props.className)}>
|
||||
<InfoPanel
|
||||
|
||||
@@ -57,6 +57,7 @@ export class EditorPanel extends React.Component<Props> {
|
||||
|
||||
onChange = (value: string) => {
|
||||
this.validate(value);
|
||||
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(value, this.yamlError);
|
||||
}
|
||||
@@ -65,8 +66,10 @@ export class EditorPanel extends React.Component<Props> {
|
||||
render() {
|
||||
const { value, tabId } = this.props;
|
||||
let { className } = this.props;
|
||||
|
||||
className = cssNames("EditorPanel", className);
|
||||
const cursorPos = EditorPanel.cursorPos.getData(tabId);
|
||||
|
||||
return (
|
||||
<AceEditor
|
||||
autoFocus mode="yaml"
|
||||
|
||||
@@ -57,9 +57,12 @@ export class InfoPanel extends Component<Props> {
|
||||
|
||||
submit = async () => {
|
||||
const { showNotifications } = this.props;
|
||||
|
||||
this.waiting = true;
|
||||
|
||||
try {
|
||||
const result = await this.props.submit();
|
||||
|
||||
if (showNotifications) Notifications.ok(result);
|
||||
} catch (error) {
|
||||
if (showNotifications) Notifications.error(error.toString());
|
||||
@@ -81,6 +84,7 @@ export class InfoPanel extends Component<Props> {
|
||||
if (!this.props.showInlineInfo || !this.errorInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="error">
|
||||
<Icon material="error_outline" tooltip={this.errorInfo}/>
|
||||
@@ -92,6 +96,7 @@ export class InfoPanel extends Component<Props> {
|
||||
const { className, controls, submitLabel, disableSubmit, error, submittingMessage, showButtons, showSubmitClose } = this.props;
|
||||
const { submit, close, submitAndClose, waiting } = this;
|
||||
const isDisabled = !!(disableSubmit || waiting || error);
|
||||
|
||||
return (
|
||||
<div className={cssNames("InfoPanel flex gaps align-center", className)}>
|
||||
<div className="controls">
|
||||
|
||||
@@ -28,6 +28,7 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
||||
});
|
||||
autorun(() => {
|
||||
const { selectedTab, isOpen } = dockStore;
|
||||
|
||||
if (isInstallChartTab(selectedTab) && isOpen) {
|
||||
this.loadData()
|
||||
.catch(err => Notifications.error(String(err)));
|
||||
@@ -53,9 +54,11 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
||||
@action
|
||||
async loadVersions(tabId: TabId) {
|
||||
const { repo, name, version } = this.getData(tabId);
|
||||
|
||||
this.versions.clearData(tabId); // reset
|
||||
const charts = await helmChartsApi.get(repo, name, version);
|
||||
const versions = charts.versions.map(chartVersion => chartVersion.version);
|
||||
|
||||
this.versions.setData(tabId, versions);
|
||||
}
|
||||
|
||||
@@ -63,8 +66,8 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
||||
async loadValues(tabId: TabId, attempt = 0): Promise<void> {
|
||||
const data = this.getData(tabId);
|
||||
const { repo, name, version } = data;
|
||||
|
||||
const values = await helmChartsApi.getValues(repo, name, version);
|
||||
|
||||
if (values) {
|
||||
this.setData(tabId, { ...data, values });
|
||||
} else if (attempt < 4) {
|
||||
@@ -77,7 +80,6 @@ export const installChartStore = new InstallChartStore();
|
||||
|
||||
export function createInstallChartTab(chart: HelmChart, tabParams: Partial<IDockTab> = {}) {
|
||||
const { name, repo, version } = chart;
|
||||
|
||||
const tab = dockStore.createTab({
|
||||
kind: TabKind.INSTALL_CHART,
|
||||
title: _i18n._(t`Helm Install: ${repo}/${name}`),
|
||||
|
||||
@@ -54,6 +54,7 @@ export class InstallChart extends Component<Props> {
|
||||
@autobind()
|
||||
viewRelease() {
|
||||
const { release } = this.releaseDetails;
|
||||
|
||||
navigate(releaseURL({
|
||||
params: {
|
||||
name: release.name,
|
||||
@@ -66,12 +67,14 @@ export class InstallChart extends Component<Props> {
|
||||
@autobind()
|
||||
save(data: Partial<IChartInstallData>) {
|
||||
const chart = { ...this.chartData, ...data };
|
||||
|
||||
installChartStore.setData(this.tabId, chart);
|
||||
}
|
||||
|
||||
@autobind()
|
||||
onVersionChange(option: SelectOption) {
|
||||
const version = option.value;
|
||||
|
||||
this.save({ version, values: "" });
|
||||
installChartStore.loadValues(this.tabId);
|
||||
}
|
||||
@@ -99,7 +102,9 @@ export class InstallChart extends Component<Props> {
|
||||
chart: name,
|
||||
repo, namespace, version, values,
|
||||
});
|
||||
|
||||
installChartStore.details.setData(this.tabId, details);
|
||||
|
||||
return (
|
||||
<p><Trans>Chart Release <b>{details.release.name}</b> successfully created.</Trans></p>
|
||||
);
|
||||
@@ -107,6 +112,7 @@ export class InstallChart extends Component<Props> {
|
||||
|
||||
render() {
|
||||
const { tabId, chartData, values, versions, install } = this;
|
||||
|
||||
if (chartData?.values === undefined || !versions) {
|
||||
return <Spinner center />;
|
||||
}
|
||||
|
||||
@@ -39,11 +39,13 @@ export const PodLogControls = observer((props: Props) => {
|
||||
|
||||
const downloadLogs = () => {
|
||||
const fileName = selectedContainer ? selectedContainer.name : pod.getName();
|
||||
|
||||
saveFileDialog(`${fileName}.log`, logs.join("\n"), "text/plain");
|
||||
};
|
||||
|
||||
const onContainerChange = (option: SelectOption) => {
|
||||
const { containers, initContainers } = tabData;
|
||||
|
||||
save({
|
||||
selectedContainer: containers
|
||||
.concat(initContainers)
|
||||
@@ -54,6 +56,7 @@ export const PodLogControls = observer((props: Props) => {
|
||||
|
||||
const containerSelectOptions = () => {
|
||||
const { containers, initContainers } = tabData;
|
||||
|
||||
return [
|
||||
{
|
||||
label: _i18n._(t`Containers`),
|
||||
@@ -72,6 +75,7 @@ export const PodLogControls = observer((props: Props) => {
|
||||
|
||||
const formatOptionLabel = (option: SelectOption) => {
|
||||
const { value, label } = option;
|
||||
|
||||
return label || <><Icon small material="view_carousel"/> {value}</>;
|
||||
};
|
||||
|
||||
|
||||
@@ -41,24 +41,31 @@ export class PodLogList extends React.Component<Props> {
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const { logs, id } = this.props;
|
||||
|
||||
if (id != prevProps.id) {
|
||||
this.isLastLineVisible = true;
|
||||
|
||||
return;
|
||||
}
|
||||
if (logs == prevProps.logs || !this.virtualListDiv.current) return;
|
||||
const newLogsLoaded = prevProps.logs.length < logs.length;
|
||||
const scrolledToBeginning = this.virtualListDiv.current.scrollTop === 0;
|
||||
const fewLogsLoaded = logs.length < logRange;
|
||||
|
||||
if (this.isLastLineVisible) {
|
||||
this.scrollToBottom(); // Scroll down to keep user watching/reading experience
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (scrolledToBeginning && newLogsLoaded) {
|
||||
this.virtualListDiv.current.scrollTop = (logs.length - prevProps.logs.length) * this.lineHeight;
|
||||
}
|
||||
|
||||
if (fewLogsLoaded) {
|
||||
this.isJumpButtonVisible = false;
|
||||
}
|
||||
|
||||
if (!logs.length) {
|
||||
this.isLastLineVisible = false;
|
||||
}
|
||||
@@ -73,6 +80,7 @@ export class PodLogList extends React.Component<Props> {
|
||||
const offset = 100 * this.lineHeight;
|
||||
const { scrollHeight } = this.virtualListDiv.current;
|
||||
const { scrollOffset } = props;
|
||||
|
||||
if (scrollHeight - scrollOffset < offset) {
|
||||
this.isJumpButtonVisible = false;
|
||||
} else {
|
||||
@@ -88,6 +96,7 @@ export class PodLogList extends React.Component<Props> {
|
||||
setLastLineVisibility = (props: ListOnScrollProps) => {
|
||||
const { scrollHeight, clientHeight } = this.virtualListDiv.current;
|
||||
const { scrollOffset, scrollDirection } = props;
|
||||
|
||||
if (scrollDirection == "backward") {
|
||||
this.isLastLineVisible = false;
|
||||
} else {
|
||||
@@ -103,6 +112,7 @@ export class PodLogList extends React.Component<Props> {
|
||||
*/
|
||||
checkLoadIntent = (props: ListOnScrollProps) => {
|
||||
const { scrollOffset } = props;
|
||||
|
||||
if (scrollOffset === 0) {
|
||||
this.props.load();
|
||||
}
|
||||
@@ -136,6 +146,7 @@ export class PodLogList extends React.Component<Props> {
|
||||
const item = this.props.logs[rowIndex];
|
||||
const contents: React.ReactElement[] = [];
|
||||
const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi));
|
||||
|
||||
if (searchQuery) { // If search is enabled, replace keyword with backgrounded <span>
|
||||
// Case-insensitive search (lowercasing query and keywords in line)
|
||||
const regex = new RegExp(searchStore.escapeRegex(searchQuery), "gi");
|
||||
@@ -143,6 +154,7 @@ export class PodLogList extends React.Component<Props> {
|
||||
const modified = item.replace(regex, match => match.toLowerCase());
|
||||
// Splitting text line by keyword
|
||||
const pieces = modified.split(searchQuery.toLowerCase());
|
||||
|
||||
pieces.forEach((piece, index) => {
|
||||
const active = isActiveOverlay(rowIndex, index);
|
||||
const lastItem = index === pieces.length - 1;
|
||||
@@ -153,6 +165,7 @@ export class PodLogList extends React.Component<Props> {
|
||||
dangerouslySetInnerHTML={{ __html: ansiToHtml(overlayValue) }}
|
||||
/>
|
||||
: null;
|
||||
|
||||
contents.push(
|
||||
<React.Fragment key={piece + index}>
|
||||
<span dangerouslySetInnerHTML={{ __html: ansiToHtml(piece) }} />
|
||||
@@ -161,6 +174,7 @@ export class PodLogList extends React.Component<Props> {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cssNames("LogRow")}>
|
||||
{contents.length > 1 ? contents : (
|
||||
@@ -174,9 +188,11 @@ export class PodLogList extends React.Component<Props> {
|
||||
const { logs, isLoading } = this.props;
|
||||
const isInitLoading = isLoading && !logs.length;
|
||||
const rowHeights = new Array(logs.length).fill(this.lineHeight);
|
||||
|
||||
if (isInitLoading) {
|
||||
return <Spinner center/>;
|
||||
}
|
||||
|
||||
if (!logs.length) {
|
||||
return (
|
||||
<div className="PodLogList flex box grow align-center justify-center">
|
||||
@@ -184,6 +200,7 @@ export class PodLogList extends React.Component<Props> {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cssNames("PodLogList flex", { isLoading })}>
|
||||
<VirtualList
|
||||
|
||||
@@ -26,6 +26,7 @@ export const logRange = 500;
|
||||
export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
||||
private refresher = interval(10, () => {
|
||||
const id = dockStore.selectedTabId;
|
||||
|
||||
if (!this.logs.get(id)) return;
|
||||
this.loadMore(id);
|
||||
});
|
||||
@@ -39,6 +40,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
||||
});
|
||||
autorun(() => {
|
||||
const { selectedTab, isOpen } = dockStore;
|
||||
|
||||
if (isPodLogsTab(selectedTab) && isOpen) {
|
||||
this.refresher.start();
|
||||
} else {
|
||||
@@ -68,6 +70,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
||||
const logs = await this.loadLogs(tabId, {
|
||||
tailLines: this.lines + logRange
|
||||
});
|
||||
|
||||
this.refresher.start();
|
||||
this.logs.set(tabId, logs);
|
||||
} catch ({error}) {
|
||||
@@ -75,6 +78,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
||||
_i18n._(t`Failed to load logs: ${error.message}`),
|
||||
_i18n._(t`Reason: ${error.reason} (${error.code})`)
|
||||
];
|
||||
|
||||
this.refresher.stop();
|
||||
this.logs.set(tabId, message);
|
||||
}
|
||||
@@ -92,6 +96,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
||||
const logs = await this.loadLogs(tabId, {
|
||||
sinceTime: this.getLastSinceTime(tabId)
|
||||
});
|
||||
|
||||
// Add newly received logs to bottom
|
||||
this.logs.set(tabId, [...oldLogs, ...logs]);
|
||||
};
|
||||
@@ -109,6 +114,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
||||
const pod = new Pod(data.pod);
|
||||
const namespace = pod.getNs();
|
||||
const name = pod.getName();
|
||||
|
||||
return podsApi.getLogs({ namespace, name }, {
|
||||
...params,
|
||||
timestamps: true, // Always setting timestampt to separate old logs from new ones
|
||||
@@ -116,7 +122,9 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
||||
previous
|
||||
}).then(result => {
|
||||
const logs = [...result.split("\n")]; // Transform them into array
|
||||
|
||||
logs.pop(); // Remove last empty element
|
||||
|
||||
return logs;
|
||||
});
|
||||
};
|
||||
@@ -128,6 +136,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
||||
setNewLogSince(tabId: TabId) {
|
||||
if (!this.logs.has(tabId) || !this.logs.get(tabId).length || this.newLogSince.has(tabId)) return;
|
||||
const timestamp = this.getLastSinceTime(tabId);
|
||||
|
||||
this.newLogSince.set(tabId, timestamp.split(".")[0]); // Removing milliseconds from string
|
||||
}
|
||||
|
||||
@@ -139,6 +148,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
||||
get lines() {
|
||||
const id = dockStore.selectedTabId;
|
||||
const logs = this.logs.get(id);
|
||||
|
||||
return logs ? logs.length : 0;
|
||||
}
|
||||
|
||||
@@ -151,7 +161,9 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
||||
const logs = this.logs.get(tabId);
|
||||
const timestamps = this.getTimestamps(logs[logs.length - 1]);
|
||||
const stamp = new Date(timestamps ? timestamps[0] : null);
|
||||
|
||||
stamp.setSeconds(stamp.getSeconds() + 1); // avoid duplicates from last second
|
||||
|
||||
return stamp.toISOString();
|
||||
}
|
||||
|
||||
@@ -178,9 +190,11 @@ export const podLogsStore = new PodLogsStore();
|
||||
export function createPodLogsTab(data: IPodLogsData, tabParams: Partial<IDockTab> = {}) {
|
||||
const podId = data.pod.getId();
|
||||
let tab = dockStore.getTabById(podId);
|
||||
|
||||
if (tab) {
|
||||
dockStore.open();
|
||||
dockStore.selectTab(tab.id);
|
||||
|
||||
return;
|
||||
}
|
||||
// If no existent tab found
|
||||
@@ -191,6 +205,7 @@ export function createPodLogsTab(data: IPodLogsData, tabParams: Partial<IDockTab
|
||||
...tabParams
|
||||
}, false);
|
||||
podLogsStore.setData(tab.id, data);
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,12 +66,14 @@ export class PodLogs extends React.Component<Props> {
|
||||
@autobind()
|
||||
toOverlay() {
|
||||
const { activeOverlayLine } = searchStore;
|
||||
|
||||
if (!this.logListElement.current || activeOverlayLine === undefined) return;
|
||||
// Scroll vertically
|
||||
this.logListElement.current.scrollToItem(activeOverlayLine, "center");
|
||||
// Scroll horizontally in timeout since virtual list need some time to prepare its contents
|
||||
setTimeout(() => {
|
||||
const overlay = document.querySelector(".PodLogs .list span.active");
|
||||
|
||||
if (!overlay) return;
|
||||
overlay.scrollIntoViewIfNeeded();
|
||||
}, 100);
|
||||
@@ -87,9 +89,11 @@ export class PodLogs extends React.Component<Props> {
|
||||
const logs = podLogsStore.logs.get(this.tabId);
|
||||
const { getData, removeTimestamps } = podLogsStore;
|
||||
const { showTimestamps } = getData(this.tabId);
|
||||
|
||||
if (!showTimestamps) {
|
||||
return logs.map(item => removeTimestamps(item));
|
||||
}
|
||||
|
||||
return logs;
|
||||
}
|
||||
|
||||
@@ -107,6 +111,7 @@ export class PodLogs extends React.Component<Props> {
|
||||
toNextOverlay={this.toOverlay}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="PodLogs flex column">
|
||||
<InfoPanel
|
||||
|
||||
@@ -35,6 +35,7 @@ export class TerminalWindow extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cssNames("TerminalWindow", className, themeStore.activeTheme.type)}
|
||||
|
||||
@@ -32,7 +32,9 @@ export class TerminalStore {
|
||||
// connect active tab
|
||||
autorun(() => {
|
||||
const { selectedTab, isOpen } = dockStore;
|
||||
|
||||
if (!isTerminalTab(selectedTab)) return;
|
||||
|
||||
if (isOpen) {
|
||||
this.connect(selectedTab.id);
|
||||
}
|
||||
@@ -40,6 +42,7 @@ export class TerminalStore {
|
||||
// disconnect closed tabs
|
||||
autorun(() => {
|
||||
const currentTabs = dockStore.tabs.map(tab => tab.id);
|
||||
|
||||
for (const [tabId] of this.connections) {
|
||||
if (!currentTabs.includes(tabId)) this.disconnect(tabId);
|
||||
}
|
||||
@@ -56,6 +59,7 @@ export class TerminalStore {
|
||||
node: tab.node,
|
||||
});
|
||||
const terminal = new Terminal(tabId, api);
|
||||
|
||||
this.connections.set(tabId, api);
|
||||
this.terminals.set(tabId, terminal);
|
||||
}
|
||||
@@ -66,6 +70,7 @@ export class TerminalStore {
|
||||
}
|
||||
const terminal = this.terminals.get(tabId);
|
||||
const terminalApi = this.connections.get(tabId);
|
||||
|
||||
terminal.destroy();
|
||||
terminalApi.destroy();
|
||||
this.connections.delete(tabId);
|
||||
@@ -74,6 +79,7 @@ export class TerminalStore {
|
||||
|
||||
reconnect(tabId: TabId) {
|
||||
const terminalApi = this.connections.get(tabId);
|
||||
|
||||
if (terminalApi) terminalApi.connect();
|
||||
}
|
||||
|
||||
@@ -83,6 +89,7 @@ export class TerminalStore {
|
||||
|
||||
isDisconnected(tabId: TabId) {
|
||||
const terminalApi = this.connections.get(tabId);
|
||||
|
||||
if (terminalApi) {
|
||||
return terminalApi.readyState === WebSocketApiState.CLOSED;
|
||||
}
|
||||
@@ -91,12 +98,13 @@ export class TerminalStore {
|
||||
sendCommand(command: string, options: { enter?: boolean; newTab?: boolean; tabId?: TabId } = {}) {
|
||||
const { enter, newTab, tabId } = options;
|
||||
const { selectTab, getTabById } = dockStore;
|
||||
|
||||
const tab = tabId && getTabById(tabId);
|
||||
|
||||
if (tab) selectTab(tabId);
|
||||
if (newTab) createTerminalTab();
|
||||
|
||||
const terminalApi = this.connections.get(dockStore.selectedTabId);
|
||||
|
||||
if (terminalApi) {
|
||||
terminalApi.sendCommand(command + (enter ? "\r" : ""));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ export class Terminal {
|
||||
// terminal element must be in DOM before attaching via xterm.open(elem)
|
||||
// https://xtermjs.org/docs/api/terminal/classes/terminal/#open
|
||||
const pool = document.createElement("div");
|
||||
|
||||
pool.className = "terminal-init";
|
||||
pool.style.cssText = "position: absolute; top: 0; left: 0; height: 0; visibility: hidden; overflow: hidden";
|
||||
document.body.appendChild(pool);
|
||||
@@ -23,6 +24,7 @@ export class Terminal {
|
||||
static async preloadFonts() {
|
||||
const fontPath = require("../fonts/roboto-mono-nerd.ttf").default; // eslint-disable-line @typescript-eslint/no-var-requires
|
||||
const fontFace = new FontFace("RobotoMono", `url(${fontPath})`);
|
||||
|
||||
await fontFace.load();
|
||||
document.fonts.add(fontFace);
|
||||
}
|
||||
@@ -41,10 +43,13 @@ export class Terminal {
|
||||
.filter(([name]) => name.startsWith(colorPrefix))
|
||||
.reduce<any>((colors, [name, color]) => {
|
||||
const colorName = name.split("").slice(colorPrefix.length);
|
||||
|
||||
colorName[0] = colorName[0].toLowerCase();
|
||||
colors[colorName.join("")] = color;
|
||||
|
||||
return colors;
|
||||
}, {});
|
||||
|
||||
this.xterm.setOption("theme", terminalColors);
|
||||
}
|
||||
|
||||
@@ -62,6 +67,7 @@ export class Terminal {
|
||||
|
||||
get isActive() {
|
||||
const { isOpen, selectedTabId } = dockStore;
|
||||
|
||||
return isOpen && selectedTabId === this.tabId;
|
||||
}
|
||||
|
||||
@@ -95,6 +101,7 @@ export class Terminal {
|
||||
|
||||
// bind events
|
||||
const onDataHandler = this.xterm.onData(this.onData);
|
||||
|
||||
this.viewport.addEventListener("scroll", this.onScroll);
|
||||
this.api.onReady.addListener(this.onClear, { once: true }); // clear status logs (connecting..)
|
||||
this.api.onData.addListener(this.onApiData);
|
||||
@@ -125,6 +132,7 @@ export class Terminal {
|
||||
if (!this.isActive) return;
|
||||
this.fitAddon.fit();
|
||||
const { cols, rows } = this.xterm;
|
||||
|
||||
this.api.sendTerminalSize(cols, rows);
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
|
||||
|
||||
autorun(() => {
|
||||
const { selectedTab, isOpen } = dockStore;
|
||||
|
||||
if (!isUpgradeChartTab(selectedTab)) return;
|
||||
|
||||
if (isOpen) {
|
||||
this.loadData(selectedTab.id);
|
||||
}
|
||||
@@ -31,6 +33,7 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
|
||||
|
||||
autorun(() => {
|
||||
const objects = [...this.data.values()];
|
||||
|
||||
objects.forEach(({ releaseName }) => this.createReleaseWatcher(releaseName));
|
||||
});
|
||||
}
|
||||
@@ -41,13 +44,16 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
|
||||
}
|
||||
const dispose = reaction(() => {
|
||||
const release = releaseStore.getByName(releaseName);
|
||||
|
||||
if (release) return release.getRevision(); // watch changes only by revision
|
||||
},
|
||||
release => {
|
||||
const releaseTab = this.getTabByRelease(releaseName);
|
||||
|
||||
if (!releaseStore.isLoaded || !releaseTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
// auto-reload values if was loaded before
|
||||
if (release) {
|
||||
if (dockStore.selectedTab === releaseTab && this.values.getData(releaseTab.id)) {
|
||||
@@ -61,17 +67,20 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
|
||||
dockStore.closeTab(releaseTab.id);
|
||||
}
|
||||
});
|
||||
|
||||
this.watchers.set(releaseName, dispose);
|
||||
}
|
||||
|
||||
isLoading(tabId = dockStore.selectedTabId) {
|
||||
const values = this.values.getData(tabId);
|
||||
|
||||
return !releaseStore.isLoaded || values === undefined;
|
||||
}
|
||||
|
||||
@action
|
||||
async loadData(tabId: TabId) {
|
||||
const values = this.values.getData(tabId);
|
||||
|
||||
await Promise.all([
|
||||
!releaseStore.isLoaded && releaseStore.loadAll(),
|
||||
!values && this.loadValues(tabId)
|
||||
@@ -83,13 +92,16 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
|
||||
this.values.clearData(tabId); // reset
|
||||
const { releaseName, releaseNamespace } = this.getData(tabId);
|
||||
const values = await helmReleasesApi.getValues(releaseName, releaseNamespace);
|
||||
|
||||
this.values.setData(tabId, values);
|
||||
}
|
||||
|
||||
getTabByRelease(releaseName: string): IDockTab {
|
||||
const item = [...this.data].find(item => item[1].releaseName === releaseName);
|
||||
|
||||
if (item) {
|
||||
const [tabId] = item;
|
||||
|
||||
return dockStore.getTabById(tabId);
|
||||
}
|
||||
}
|
||||
@@ -99,10 +111,12 @@ export const upgradeChartStore = new UpgradeChartStore();
|
||||
|
||||
export function createUpgradeChartTab(release: HelmRelease, tabParams: Partial<IDockTab> = {}) {
|
||||
let tab = upgradeChartStore.getTabByRelease(release.getName());
|
||||
|
||||
if (tab) {
|
||||
dockStore.open();
|
||||
dockStore.selectTab(tab.id);
|
||||
}
|
||||
|
||||
if (!tab) {
|
||||
tab = dockStore.createTab({
|
||||
kind: TabKind.UPGRADE_CHART,
|
||||
@@ -115,6 +129,7 @@ export function createUpgradeChartTab(release: HelmRelease, tabParams: Partial<I
|
||||
releaseNamespace: release.getNs()
|
||||
});
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,9 @@ export class UpgradeChart extends React.Component<Props> {
|
||||
|
||||
get release(): HelmRelease {
|
||||
const tabData = upgradeChartStore.getData(this.tabId);
|
||||
|
||||
if (!tabData) return;
|
||||
|
||||
return releaseStore.getByName(tabData.releaseName);
|
||||
}
|
||||
|
||||
@@ -55,6 +57,7 @@ export class UpgradeChart extends React.Component<Props> {
|
||||
this.version = null;
|
||||
this.versions.clear();
|
||||
const versions = await helmChartStore.getVersions(this.release.getChart());
|
||||
|
||||
this.versions.replace(versions);
|
||||
this.version = this.versions[0];
|
||||
}
|
||||
@@ -69,11 +72,13 @@ export class UpgradeChart extends React.Component<Props> {
|
||||
const { version, repo } = this.version;
|
||||
const releaseName = this.release.getName();
|
||||
const releaseNs = this.release.getNs();
|
||||
|
||||
await releaseStore.update(releaseName, releaseNs, {
|
||||
chart: this.release.getChart(),
|
||||
values: this.value,
|
||||
repo, version,
|
||||
});
|
||||
|
||||
return (
|
||||
<p>
|
||||
<Trans>Release <b>{releaseName}</b> successfully upgraded to version <b>{version}</b></Trans>
|
||||
@@ -84,12 +89,14 @@ export class UpgradeChart extends React.Component<Props> {
|
||||
formatVersionLabel = ({ value }: SelectOption<IChartVersion>) => {
|
||||
const chartName = this.release.getChart();
|
||||
const { repo, version } = value;
|
||||
|
||||
return `${repo}/${chartName}-${version}`;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { tabId, release, value, error, onChange, upgrade, versions, version } = this;
|
||||
const { className } = this.props;
|
||||
|
||||
if (!release || upgradeChartStore.isLoading() || !version) {
|
||||
return <Spinner center/>;
|
||||
}
|
||||
@@ -111,6 +118,7 @@ export class UpgradeChart extends React.Component<Props> {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cssNames("UpgradeChart flex column", className)}>
|
||||
<InfoPanel
|
||||
|
||||
Reference in new issue
Block a user