/******************************************************************************* * Copyright (c) 2019, 2020 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v20.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.codewind.ui.internal.editors; import java.net.URL; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Optional; import org.eclipse.codewind.core.internal.CodewindApplication; import org.eclipse.codewind.core.internal.CodewindEclipseApplication; import org.eclipse.codewind.core.internal.Logger; import org.eclipse.codewind.core.internal.cli.ProjectLinks; import org.eclipse.codewind.core.internal.cli.ProjectLinks.LinkInfo; import org.eclipse.codewind.core.internal.connection.CodewindConnection; import org.eclipse.codewind.core.internal.connection.CodewindConnectionManager; import org.eclipse.codewind.core.internal.connection.LocalConnection; import org.eclipse.codewind.core.internal.console.CodewindConsoleFactory; import org.eclipse.codewind.core.internal.console.ProjectLogInfo; import org.eclipse.codewind.core.internal.console.SocketConsole; import org.eclipse.codewind.core.internal.constants.DetailedAppStatus; import org.eclipse.codewind.ui.CodewindUIPlugin; import org.eclipse.codewind.ui.internal.IDEUtil; import org.eclipse.codewind.ui.internal.UIConstants; import org.eclipse.codewind.ui.internal.actions.ManageLinksAction; import org.eclipse.codewind.ui.internal.actions.OpenAppAction; import org.eclipse.codewind.ui.internal.messages.Messages; import org.eclipse.codewind.ui.internal.prefs.CodewindPrefsParentPage; import org.eclipse.codewind.ui.internal.views.UpdateHandler.UpdateListener; import org.eclipse.codewind.ui.internal.views.UpdateHandler.UpdateType; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.TableColumnLayout; import org.eclipse.jface.preference.PreferenceDialog; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.browser.IWebBrowser; import org.eclipse.ui.browser.IWorkbenchBrowserSupport; import org.eclipse.ui.console.ConsolePlugin; import org.eclipse.ui.dialogs.PreferencesUtil; import org.eclipse.ui.forms.ManagedForm; import org.eclipse.ui.forms.events.HyperlinkAdapter; import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.events.IHyperlinkListener; import org.eclipse.ui.forms.widgets.ExpandableComposite; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.Hyperlink; import org.eclipse.ui.forms.widgets.ScrolledForm; import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.ide.FileStoreEditorInput; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.part.EditorPart; public class ApplicationOverviewEditorPart extends EditorPart implements UpdateListener { private static final String SETTINGS_FILE = ".cw-settings"; private static final String JSON_EDITOR_ID = "org.eclipse.wst.json.ui.JSONEditor"; private static final String CWSETTINGS_INFO_ID = "org.eclipse.codewind.ui.overview.ProjectSettingsInfo"; private Composite contents; private String appName; private String connectionId; private String connectionName; private String projectId; private ScrolledForm form = null; private Composite messageComp = null; private Label messageLabel = null; private Composite sectionComp = null; private ProjectInfoSection projectInfoSection = null; private ProjectStatusSection projectStatusSection = null; private ProjectLinkSection projectLinkSection = null; private AppInfoSection appInfoSection = null; private Font boldFont; @Override public void doSave(IProgressMonitor arg0) { // TODO Auto-generated method stub } @Override public void doSaveAs() { // TODO Auto-generated method stub } @Override public void init(IEditorSite site, IEditorInput input) throws PartInitException { ApplicationOverviewEditorInput appInput = null; if (input instanceof ApplicationOverviewEditorInput) { appInput = (ApplicationOverviewEditorInput)input; if ((appInput.connectionId == null && appInput.connectionUri == null) || appInput.projectID == null) { Logger.logError("Invalid editor input for application overview. Connection id/uri or project id is null." + input.getClass()); //$NON-NLS-1$ throw new PartInitException(NLS.bind(Messages.AppOverviewEditorCreateError, input)); } } else { Logger.logError("The editor input is not valid for the application overview: " + input.getClass()); //$NON-NLS-1$ throw new PartInitException(NLS.bind(Messages.AppOverviewEditorCreateError, input)); } setSite(site); setInput(input); // Support old mementos by defaulting to local connection appName = appInput.projectName; connectionId = appInput.connectionId; if (connectionId == null) { connectionId = LocalConnection.DEFAULT_ID; } connectionName = appInput.connectionName; if (connectionName == null) { connectionName = LocalConnection.DEFAULT_NAME; } projectId = appInput.projectID; setPartName(NLS.bind(Messages.AppOverviewEditorPartName, new String[] {appName, connectionName})); CodewindUIPlugin.getUpdateHandler().addUpdateListener(this); } @Override public void dispose() { if (boldFont != null) { boldFont.dispose(); } CodewindUIPlugin.getUpdateHandler().removeUpdateListener(this); super.dispose(); } @Override public boolean isDirty() { return false; } @Override public boolean isSaveAsAllowed() { return false; } @Override public void createPartControl(Composite parent) { ManagedForm managedForm = new ManagedForm(parent); form = managedForm.getForm(); FormToolkit toolkit = managedForm.getToolkit(); toolkit.decorateFormHeading(form.getForm()); form.setText(" " + appName + " (" + connectionName + ")"); form.setImage(CodewindUIPlugin.getImage(CodewindUIPlugin.CODEWIND_BANNER)); form.getBody().setLayout(new GridLayout()); messageComp = toolkit.createComposite(form.getBody()); GridLayout messageLayout = new GridLayout(); messageLayout.verticalSpacing = 0; messageLayout.horizontalSpacing = 10; messageComp.setLayout(messageLayout); messageComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL)); messageLabel = toolkit.createLabel(messageComp, ""); messageLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL)); boldFont = IDEUtil.newFont(parent.getShell(), parent.getFont(), SWT.BOLD); sectionComp = toolkit.createComposite(form.getBody()); GridLayout layout = new GridLayout(); layout.numColumns = 2; layout.verticalSpacing = 0; layout.horizontalSpacing = 10; sectionComp.setLayout(layout); sectionComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL)); projectInfoSection = new ProjectInfoSection(sectionComp, toolkit, 2, 1); addSpacer(sectionComp, toolkit, 2, 1); projectStatusSection = new ProjectStatusSection(sectionComp, toolkit, 2, 1); addSpacer(sectionComp, toolkit, 2, 1); projectLinkSection = new ProjectLinkSection(sectionComp, toolkit, 2, 1); addSpacer(sectionComp, toolkit, 2, 1); appInfoSection = new AppInfoSection(sectionComp, toolkit, 2, 1); addSpacer(sectionComp, toolkit, 2, 1); toolkit.createLabel(sectionComp, "", SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false, 2, 1)); addSpacer(sectionComp, toolkit, 2, 1); Hyperlink preferencesLink = toolkit.createHyperlink(sectionComp, "Control opening of overview page on project create and add", SWT.WRAP); preferencesLink.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, false, 1, 1)); preferencesLink.addHyperlinkListener(new IHyperlinkListener() { @Override public void linkActivated(HyperlinkEvent arg0) { PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(parent.getShell(), CodewindPrefsParentPage.ID, null, null); if (dialog != null) { dialog.open(); } else { Logger.logError("Could not create the preference dialog for: " + CodewindPrefsParentPage.ID); } } @Override public void linkEntered(HyperlinkEvent arg0) { // Empty } @Override public void linkExited(HyperlinkEvent arg0) { // Empty } }); Button refreshButton = new Button(sectionComp, SWT.PUSH); refreshButton.setText(Messages.AppOverviewEditorRefreshButton); refreshButton.setLayoutData(new GridData(GridData.END, GridData.CENTER, false, false, 1, 1)); refreshButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { final CodewindConnection conn = getConn(); final CodewindApplication app = getApp(conn); Job job = new Job(NLS.bind(Messages.RefreshProjectJobLabel, app.name)) { @Override protected IStatus run(IProgressMonitor monitor) { app.connection.refreshApps(app.projectID); Display.getDefault().asyncExec(() -> ApplicationOverviewEditorPart.this.update(conn, app)); return Status.OK_STATUS; } }; job.schedule(); } }); CodewindConnection conn = getConn(); update(conn, getApp(conn), true); } @Override public void update(UpdateType type, Object element) { if (element instanceof CodewindApplication && ((CodewindApplication)element).projectID.equals(projectId)) { Display.getDefault().asyncExec(() -> { switch(type) { case MODIFY: CodewindApplication app = (CodewindApplication)element; ApplicationOverviewEditorPart.this.update(app.connection, app); break; case REMOVE: ApplicationOverviewEditorPart.this.getEditorSite().getPage().closeEditor(ApplicationOverviewEditorPart.this, false); break; } }); } else if (element instanceof CodewindConnection && ((CodewindConnection)element).getConid().equals(connectionId)) { Display.getDefault().asyncExec(() -> { switch(type) { case MODIFY: CodewindConnection conn = (CodewindConnection)element; CodewindApplication app = conn.getAppByID(projectId); ApplicationOverviewEditorPart.this.update(conn, app); break; case REMOVE: ApplicationOverviewEditorPart.this.getEditorSite().getPage().closeEditor(ApplicationOverviewEditorPart.this, false); break; } }); } else if (element == null) { // A null element means update everything CodewindApplication app = getApp(getConn()); if (app != null) { Display.getDefault().asyncExec(() -> { switch(type) { case MODIFY: ApplicationOverviewEditorPart.this.update(app.connection, app); break; case REMOVE: // Do nothing. There should never be a top level remove event. break; } }); } } } public void update(CodewindConnection conn, CodewindApplication app) { update(conn, app, false); } public void update(CodewindConnection conn, CodewindApplication app, boolean init) { if (form.isDisposed()) { return; } boolean changed = false; if (conn == null || !conn.isConnected() || app == null) { changed = !messageComp.getVisible(); messageComp.setVisible(true); ((GridData)messageComp.getLayoutData()).exclude = false; messageLabel.setText(conn == null || !conn.isConnected() ? Messages.AppOverviewEditorNoConnection : Messages.AppOverviewEditorNoApplication); sectionComp.setVisible(false); ((GridData)sectionComp.getLayoutData()).exclude = true; } else { changed = messageComp.getVisible(); messageComp.setVisible(false); ((GridData)messageComp.getLayoutData()).exclude = true; sectionComp.setVisible(true); ((GridData)sectionComp.getLayoutData()).exclude = false; projectInfoSection.update(app); projectStatusSection.update(app); projectLinkSection.update(app); appInfoSection.update(app); } Point currentSize = form.getBody().getSize(); form.layout(true, true); Point newSize = form.getBody().computeSize(SWT.DEFAULT, SWT.DEFAULT); if (!newSize.equals(currentSize)) { changed = true; } if (init || changed) { form.reflow(true); } } private CodewindConnection getConn() { return CodewindConnectionManager.getConnectionById(connectionId); } private CodewindApplication getApp(CodewindConnection connection) { if (connection == null) { return null; } return connection.getAppByID(projectId); } private class ProjectInfoSection { private final StringEntry typeEntry; private final StringEntry languageEntry; private final StringEntry projectIdEntry; private final StringEntry locationEntry; public ProjectInfoSection(Composite parent, FormToolkit toolkit, int hSpan, int vSpan) { Section section = toolkit.createSection(parent, ExpandableComposite.TWISTIE | ExpandableComposite.TITLE_BAR); section.setText(Messages.AppOverviewEditorProjectInfoSection); section.setLayoutData(new GridData(SWT.FILL,SWT.FILL, true, false, hSpan, vSpan)); section.setExpanded(true); Composite composite = toolkit.createComposite(section); GridLayout layout = new GridLayout(); layout.numColumns = 2; layout.marginHeight = 5; layout.marginWidth = 10; layout.verticalSpacing = 5; layout.horizontalSpacing = 10; composite.setLayout(layout); composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL)); toolkit.paintBordersFor(composite); section.setClient(composite); typeEntry = new StringEntry(composite, Messages.AppOverviewEditorTypeEntry); languageEntry = new StringEntry(composite, Messages.AppOverviewEditorLanguageEntry); projectIdEntry = new StringEntry(composite, Messages.AppOverviewEditorProjectIdEntry); locationEntry = new StringEntry(composite, Messages.AppOverviewEditorLocationEntry); } public void update(CodewindApplication app) { typeEntry.setValue(app.projectType.getDisplayName(), true); languageEntry.setValue(app.projectLanguage.getDisplayName(), true); projectIdEntry.setValue(app.projectID, true); locationEntry.setValue(app.fullLocalPath.toOSString(), true); } } private class ProjectStatusSection { private final StringEntry autoBuildEntry; private final StringEntry injectMetricsEntry; private final StringEntry appStatusEntry; private final StringEntry buildStatusEntry; private final StringEntry lastBuildEntry; private final StringEntry lastImageBuildEntry; private final Link projectLogs; private final Text noProjectLogs; public ProjectStatusSection(Composite parent, FormToolkit toolkit, int hSpan, int vSpan) { Section section = toolkit.createSection(parent, ExpandableComposite.TWISTIE | ExpandableComposite.TITLE_BAR); section.setText(Messages.AppOverviewEditorProjectStatusSection); section.setLayoutData(new GridData(SWT.FILL,SWT.FILL, true, false, hSpan, vSpan)); section.setExpanded(true); Composite composite = toolkit.createComposite(section); GridLayout layout = new GridLayout(); layout.numColumns = 2; layout.marginHeight = 5; layout.marginWidth = 10; layout.verticalSpacing = 5; layout.horizontalSpacing = 10; composite.setLayout(layout); composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL)); toolkit.paintBordersFor(composite); section.setClient(composite); autoBuildEntry = new StringEntry(composite, Messages.AppOverviewEditorAutoBuildEntry); injectMetricsEntry = new StringEntry(composite, Messages.AppOverviewEditorInjectMetricsEntry); appStatusEntry = new StringEntry(composite, Messages.AppOverviewEditorAppStatusEntry); buildStatusEntry = new StringEntry(composite, Messages.AppOverviewEditorBuildStatusEntry); lastImageBuildEntry = new StringEntry(composite, Messages.AppOverviewEditorLastImageBuildEntry); lastBuildEntry = new StringEntry(composite, Messages.AppOverviewEditorLastBuildEntry); Label label = new Label(composite, SWT.NONE); label.setFont(boldFont); label.setText(Messages.AppOverviewEditorProjectLogs); label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING, false, false)); projectLogs = new Link(composite, SWT.NONE); projectLogs.setText(""); projectLogs.setVisible(false); GridData data = new GridData(GridData.BEGINNING, GridData.BEGINNING, true, false); data.horizontalIndent = 2; data.exclude = true; projectLogs.setLayoutData(data); IDEUtil.paintBackgroundToMatch(projectLogs, composite); projectLogs.addListener(SWT.Selection, event -> { CodewindEclipseApplication app = (CodewindEclipseApplication) getApp(getConn()); if (app == null) { Logger.logError("A log link was selected but could not find the application for the " + connectionId //$NON-NLS-1$ + " connection with name: " + projectId); //$NON-NLS-1$ return; } Optional<ProjectLogInfo> logInfo = app.getLogInfos().stream().filter(info -> info.logName.equals(event.text)).findFirst(); if (logInfo.isPresent()) { try { SocketConsole console = app.getConsole(logInfo.get()); if (console == null) { console = CodewindConsoleFactory.createLogFileConsole(app, logInfo.get()); app.addConsole(console); } ConsolePlugin.getDefault().getConsoleManager().showConsoleView(console); } catch (Exception e) { Logger.logError("An error occurred trying to open the " + logInfo.get().logName //$NON-NLS-1$ + "log file for application: " + projectId, e); //$NON-NLS-1$ MessageDialog.openError(parent.getShell(), Messages.AppOverviewEditorOpenLogErrorTitle, NLS.bind(Messages.AppOverviewEditorOpenLogErrorMsg, new String[] {logInfo.get().logName, app.name, e.getMessage()})); } } else { Logger.logError("The " + event.text + " was selected but the associated log info could not be found for the " + projectId + " project."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } }); noProjectLogs = new Text(composite, SWT.READ_ONLY); noProjectLogs.setText(Messages.AppOverviewEditorNoProjectLogs); noProjectLogs.setData(FormToolkit.KEY_DRAW_BORDER, Boolean.FALSE); noProjectLogs.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING, true, false)); IDEUtil.paintBackgroundToMatch(noProjectLogs, composite); } public void update(CodewindApplication app) { autoBuildEntry.setValue(app.isAutoBuild() ? Messages.AppOverviewEditorAutoBuildOn : Messages.AppOverviewEditorAutoBuildOff, true); injectMetricsEntry.setValue(metricsInjectionState(app.canInjectMetrics(), app.isMetricsInjected()), true); appStatusEntry.setValue(getAppStatusString(app), true); buildStatusEntry.setValue(getBuildStatusString(app), true); long lastImageBuild = app.getLastImageBuild(); String lastImageBuildStr = Messages.AppOverviewEditorImageNeverBuilt; if (lastImageBuild > 0) { lastImageBuildStr = formatTimestamp(lastImageBuild); } lastImageBuildEntry.setValue(lastImageBuildStr, true); long lastBuild = app.getLastBuild(); String lastBuildStr = Messages.AppOverviewEditorProjectNeverBuilt; if (lastBuild > 0) { lastBuildStr = formatTimestamp(lastBuild); } lastBuildEntry.setValue(lastBuildStr, true); if (app.isAvailable() && !app.getLogInfos().isEmpty()) { StringBuilder builder = new StringBuilder(); app.getLogInfos().stream().forEach(info -> { if (builder.length() > 0) { builder.append(", "); } builder.append("<a>" + info.logName + "</a>"); //$NON-NLS-1$ //$NON-NLS-2$ }); boolean changed = !projectLogs.getVisible() || !builder.toString().equals(projectLogs.getText()); projectLogs.setText(builder.toString()); projectLogs.setToolTipText(Messages.AppOverviewEditorProjectLogsTooltip); IDEUtil.setControlVisibility(projectLogs, true); IDEUtil.setControlVisibility(noProjectLogs, false); if (changed) { projectLogs.requestLayout(); } } else { boolean changed = !noProjectLogs.getVisible(); projectLogs.setText(""); IDEUtil.setControlVisibility(projectLogs, false); IDEUtil.setControlVisibility(noProjectLogs, true); if (changed) { noProjectLogs.requestLayout(); } } } private String getAppStatusString(CodewindApplication app) { if (app.isAvailable()) { StringBuilder builder = new StringBuilder(); builder.append(app.getAppStatus().getDisplayString(app.getStartMode())); DetailedAppStatus details = app.getAppStatusDetails(); if (details != null && details.getMessage() != null) { builder.append(": "); if (details.getSeverity() != null) { builder.append("(" + details.getSeverity().displayString + ") "); } builder.append(details.getMessage()); } return builder.toString(); } return Messages.AppOverviewEditorStatusDisabled; } private String getBuildStatusString(CodewindApplication app) { if (app.isAvailable()) { String buildStatusStr = app.getBuildStatus().getDisplayString(); if (app.getBuildDetails() != null) { buildStatusStr += " (" + app.getBuildDetails() + ")"; } return buildStatusStr; } return null; } } private class ProjectLinkSection { private final Text toLinkDescriptionText, fromLinkDescriptionText; private final LinkTable toLinkTable, fromLinkTable; public ProjectLinkSection(Composite parent, FormToolkit toolkit, int hSpan, int vSpan) { Section section = toolkit.createSection(parent, ExpandableComposite.TWISTIE | ExpandableComposite.TITLE_BAR); section.setText(Messages.AppOverviewEditorProjectLinksSection); section.setLayoutData(new GridData(SWT.FILL,SWT.FILL, true, false, hSpan, vSpan)); section.setExpanded(true); Composite composite = toolkit.createComposite(section); GridLayout layout = new GridLayout(); layout.numColumns = 2; layout.marginHeight = 5; layout.marginWidth = 10; layout.verticalSpacing = 10; layout.horizontalSpacing = 10; composite.setLayout(layout); composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL)); toolkit.paintBordersFor(composite); section.setClient(composite); // Link description and manage link toLinkDescriptionText = new Text(composite, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY); toLinkDescriptionText.setText(Messages.AppOverviewEditorProjectLinksNoLinks); toLinkDescriptionText.setData(FormToolkit.KEY_DRAW_BORDER, Boolean.FALSE); toLinkDescriptionText.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING, true, false)); IDEUtil.paintBackgroundToMatch(toLinkDescriptionText, composite); Hyperlink manageLink = toolkit.createHyperlink(composite, Messages.AppOverviewEditorProjectLinksManageLinks, SWT.WRAP); GridData data = new GridData(GridData.END, GridData.BEGINNING, false, false); manageLink.setLayoutData(data); manageLink.addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(org.eclipse.ui.forms.events.HyperlinkEvent e) { final CodewindConnection conn = getConn(); final CodewindApplication app = getApp(conn); ManageLinksAction.openManageLinksDialog(app); } }); // Link table toLinkTable = new LinkTable(composite, toolkit, Messages.LinkMgmtProjectColumn); // From link description fromLinkDescriptionText = new Text(composite, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY); fromLinkDescriptionText.setText(Messages.AppOverviewEditorProjectLinksNoFromLinks); fromLinkDescriptionText.setData(FormToolkit.KEY_DRAW_BORDER, Boolean.FALSE); fromLinkDescriptionText.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING, true, false, 2, 1)); IDEUtil.paintBackgroundToMatch(fromLinkDescriptionText, composite); // From link table fromLinkTable = new LinkTable(composite, toolkit, Messages.AppOverviewEditorProjectLinksSourceProject); // Initialize toLinkTable.tableComp.setVisible(false); ((GridData) toLinkTable.tableComp.getLayoutData()).exclude = true; fromLinkTable.tableComp.setVisible(false); ((GridData) fromLinkTable.tableComp.getLayoutData()).exclude = true; } public void update(CodewindApplication app) { // Update links int itemCount = toLinkTable.linkTable.getItemCount(); toLinkTable.linkTable.removeAll(); ProjectLinks links = app.getProjectLinks(); if (links != null) { links.getLinks().forEach(linkInfo -> { CodewindApplication targetApp = app.connection.getAppByID(linkInfo.getProjectId()); TableItem item = new TableItem(toLinkTable.linkTable, SWT.NONE); item.setData(linkInfo); if (targetApp != null) { item.setText(0, targetApp.name); } else { String text = Messages.LinkMgmtErrorTargetMissing; String projectName = linkInfo.getProjectName(); if (projectName != null && !projectName.isEmpty()) { text += " (" + projectName + ")"; } item.setText(0, text); item.setImage(0, PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_ERROR_TSK)); } item.setText(1, linkInfo.getEnvVar()); }); } toLinkTable.updateTable(itemCount, toLinkDescriptionText, Messages.AppOverviewEditorProjectLinksNoLinks, Messages.AppOverviewEditorProjectLinksDescription); // Update from links itemCount = fromLinkTable.linkTable.getItemCount(); fromLinkTable.linkTable.removeAll(); Map<CodewindApplication, List<LinkInfo>> fromLinkMap = app.getLinksToThisProject(); if (fromLinkMap != null) { fromLinkMap.entrySet().stream().forEach(entry -> { entry.getValue().stream().forEach(linkInfo -> { TableItem item = new TableItem(fromLinkTable.linkTable, SWT.NONE); item.setData(linkInfo); item.setText(0, entry.getKey().name); item.setText(1, linkInfo.getEnvVar()); }); }); } fromLinkTable.updateTable(itemCount, fromLinkDescriptionText, Messages.AppOverviewEditorProjectLinksNoFromLinks, Messages.AppOverviewEditorProjectLinksFromDescription); } private class LinkTable { public final Composite tableComp; public final Table linkTable; public final TableColumn projectColumn, envVarColumn; public LinkTable(Composite composite, FormToolkit toolkit, String projectColumnLabel) { // Create a composite for the table so can use TableColumnLayout tableComp = toolkit.createComposite(composite, SWT.NONE); TableColumnLayout tableColumnLayout = new TableColumnLayout(); tableComp.setLayout(tableColumnLayout); tableComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); linkTable = toolkit.createTable(tableComp, SWT.BORDER | SWT.H_SCROLL | SWT.MULTI | SWT.FULL_SELECTION); GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); data.heightHint = 100; linkTable.setLayoutData(data); // Columns projectColumn = new TableColumn(linkTable, SWT.NONE); projectColumn.setText(projectColumnLabel); projectColumn.setResizable(true); envVarColumn = new TableColumn(linkTable, SWT.NONE); envVarColumn.setText(Messages.LinkMgmtEnvVarColumn); envVarColumn.setResizable(true); linkTable.setHeaderVisible(true); linkTable.setLinesVisible(true); Arrays.stream(linkTable.getColumns()).forEach(TableColumn::pack); tableColumnLayout.setColumnData(projectColumn, new ColumnWeightData(10, Math.max(50, projectColumn.getWidth()), true)); tableColumnLayout.setColumnData(envVarColumn, new ColumnWeightData(10, Math.max(50, envVarColumn.getWidth()), true)); } public void updateTable(int itemCount, Text descriptionText, String emptyMsg, String descMsg) { boolean changed = false; if (linkTable.getItemCount() > 0) { if (!tableComp.getVisible()) { changed = true; descriptionText.setText(descMsg); tableComp.setVisible(true); ((GridData)tableComp.getLayoutData()).exclude = false; } // Resize the table if necessary if (itemCount != linkTable.getItemCount()) { changed = true; int clientWidth = projectColumn.getWidth() + envVarColumn.getWidth(); int clientHeight = linkTable.getItemHeight() * linkTable.getItemCount(); if (linkTable.getLinesVisible() && linkTable.getItemCount() > 0) { clientHeight += linkTable.getGridLineWidth() * (linkTable.getItemCount() - 1); } Rectangle bounds = linkTable.computeTrim(0, 0, clientWidth, clientHeight); ((GridData)linkTable.getLayoutData()).heightHint = bounds.y; } } if (linkTable.getItemCount() == 0 && tableComp.getVisible()) { changed = true; descriptionText.setText(emptyMsg); tableComp.setVisible(false); ((GridData)tableComp.getLayoutData()).exclude = true; } if (changed) { descriptionText.requestLayout(); tableComp.requestLayout(); } } } } private class AppInfoSection { private final StringEntry containerIdEntry; private final StringEntry podNameEntry; private final StringEntry namespaceEntry; private final LinkEntry appURLEntry; private final StringEntry hostAppPortEntry; private final StringEntry appPortEntry; private final StringEntry hostDebugPortEntry; private final StringEntry debugPortEntry; private final Button editButton; private final Button infoButton; public AppInfoSection(Composite parent, FormToolkit toolkit, int hSpan, int vSpan) { Section section = toolkit.createSection(parent, ExpandableComposite.TWISTIE | ExpandableComposite.TITLE_BAR); section.setText(Messages.AppOverviewEditorAppInfoSection); section.setLayoutData(new GridData(SWT.FILL,SWT.FILL, true, false, hSpan, vSpan)); section.setExpanded(true); Composite composite = toolkit.createComposite(section); GridLayout layout = new GridLayout(); layout.numColumns = 2; layout.marginHeight = 5; layout.marginWidth = 10; layout.verticalSpacing = 5; layout.horizontalSpacing = 10; composite.setLayout(layout); composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL)); toolkit.paintBordersFor(composite); section.setClient(composite); containerIdEntry = new StringEntry(composite, Messages.AppOverviewEditorContainerIdEntry); podNameEntry = new StringEntry(composite, Messages.AppOverviewEditorPodNameEntry); namespaceEntry = new StringEntry(composite, Messages.AppOverviewEditorNamespaceEntry); appURLEntry = new LinkEntry(composite, toolkit, Messages.AppOverviewEditorAppUrlEntry, (url) -> { CodewindApplication app = getApp(getConn()); if (app == null) { Logger.logError("Could not get the application for opening in a browser: " + appName); //$NON-NLS-1$ return; } OpenAppAction.openAppInBrowser(app); }); hostAppPortEntry = new StringEntry(composite, Messages.AppOverviewEditorHostAppPortEntry); appPortEntry = new StringEntry(composite, Messages.AppOverviewEditorAppPortEntry); CodewindConnection conn = getConn(); if (conn != null && !conn.isLocal()) { hostDebugPortEntry = new StringEntry(composite, Messages.AppOverviewEditorLocalDebugPortEntry); } else { hostDebugPortEntry = new StringEntry(composite, Messages.AppOverviewEditorHostDebugPortEntry); } debugPortEntry = new StringEntry(composite, Messages.AppOverviewEditorDebugPortEntry); Composite buttonComp = toolkit.createComposite(composite); layout = new GridLayout(); layout.numColumns = 2; layout.marginTop = 20; layout.marginHeight = 0; layout.marginWidth = 0; layout.verticalSpacing = 5; layout.horizontalSpacing = 10; buttonComp.setLayout(layout); buttonComp.setLayoutData(new GridData(GridData.END, GridData.END, false, false, 2, 1)); editButton = new Button(buttonComp, SWT.PUSH); editButton.setText(Messages.AppOverviewEditorEditProjectSettings); editButton.setLayoutData(new GridData(GridData.END, GridData.CENTER, false, false)); editButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { CodewindApplication app = getApp(getConn()); if (app == null) { // Should not happen Logger.logError("Trying to open the settings file from the overview page but the app is not found with name: " + appName + ", and project id: " + projectId); //$NON-NLS-1$ //$NON-NLS-2$ return; } IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(app.name); if (project != null && project.isAccessible()) { IFile file = project.getFile(SETTINGS_FILE); if (file != null && file.exists()) { try { IDE.openEditor(page, file, JSON_EDITOR_ID); } catch (PartInitException e) { Logger.logError("Error trying to open project settings file: " + file, e); //$NON-NLS-1$ MessageDialog.openError(parent.getShell(), Messages.AppOverviewEditorOpenSettingsErrorTitle, NLS.bind(Messages.AppOverviewEditorOpenSettingsErrorMsg, e)); } return; } } // Try using an external file IPath path = app.fullLocalPath.append(SETTINGS_FILE); if (path.toFile().exists()) { IFileStore fileStore = EFS.getLocalFileSystem().getStore(path); FileStoreEditorInput input = new FileStoreEditorInput(fileStore); try { IDE.openEditor(page, input, JSON_EDITOR_ID); } catch (PartInitException e) { Logger.logError("Error trying to open project settings file: " + path.toOSString(), e); //$NON-NLS-1$ MessageDialog.openError(parent.getShell(), Messages.AppOverviewEditorOpenSettingsErrorTitle, NLS.bind(Messages.AppOverviewEditorOpenSettingsErrorMsg, e)); } return; } Logger.logError("Failed to open project settings file for project: " + appName + ", with id: " + projectId); //$NON-NLS-1$ //$NON-NLS-2$ MessageDialog.openError(parent.getShell(), Messages.AppOverviewEditorOpenSettingsErrorTitle, Messages.AppOverviewEditorOpenSettingsNotFound); } }); infoButton = new Button(buttonComp, SWT.PUSH); infoButton.setText(Messages.AppOverviewEditorProjectSettingsInfo); infoButton.setLayoutData(new GridData(GridData.END, GridData.CENTER, false, false)); infoButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { try { IWorkbenchBrowserSupport browserSupport = PlatformUI.getWorkbench().getBrowserSupport(); IWebBrowser browser = browserSupport .createBrowser(IWorkbenchBrowserSupport.NAVIGATION_BAR | IWorkbenchBrowserSupport.LOCATION_BAR, CWSETTINGS_INFO_ID, null, null); URL url = new URL(UIConstants.CWSETTINGS_INFO_URL); browser.openURL(url); } catch (Exception e) { Logger.logError("Failed to open the browser for the project settings documentation: " + UIConstants.CWSETTINGS_INFO_URL, e); //$NON-NLS-1$ } } }); } public void update(CodewindApplication app) { if (app.connection.isLocal()) { containerIdEntry.setValue(app.isAvailable() ? app.getContainerId() : null, true); podNameEntry.setValue(null, false); namespaceEntry.setValue(null, false); } else { containerIdEntry.setValue(null, false); podNameEntry.setValue(app.isAvailable() ? app.getPodName() : null, true); namespaceEntry.setValue(app.isAvailable() ? app.getNamespace() : null, true); } appURLEntry.setValue(app.isAvailable() && app.getRootUrl() != null ? app.getRootUrl().toString() : null, true); hostAppPortEntry.setValue(app.isAvailable() && app.getHttpPort() > 0 ? Integer.toString(app.getHttpPort()) : null, true); appPortEntry.setValue(app.isAvailable() && app.getContainerAppPort() > 0 ? Integer.toString(app.getContainerAppPort()) : null, true); String hostDebugPort = null; String debugPort = null; if (app.supportsDebug()) { if (app.getStartMode().isDebugMode()) { hostDebugPort = app.isAvailable() && app.getDebugConnectPort() > 0 ? Integer.toString(app.getDebugConnectPort()) : null; debugPort = app.isAvailable() && app.getContainerDebugPort() > 0 ? Integer.toString(app.getContainerDebugPort()) : null; } else { hostDebugPort = debugPort = Messages.AppOverviewEditorNotDebugging; } } else { hostDebugPort = debugPort = app.getCapabilitiesReady() ? Messages.AppOverviewEditorDebugNotSupported : null; } hostDebugPortEntry.setValue(hostDebugPort, true); debugPortEntry.setValue(debugPort, true); boolean hasSettingsFile = hasSettingsFile(app); IDEUtil.setControlVisibility(editButton, hasSettingsFile); IDEUtil.setControlVisibility(infoButton, hasSettingsFile); } private boolean hasSettingsFile(CodewindApplication app) { IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(app.name); if (project != null && project.isAccessible()) { IFile file = project.getFile(SETTINGS_FILE); if (file != null && file.exists()) { return true; } } IPath path = app.fullLocalPath.append(SETTINGS_FILE); if (path.toFile().exists()) { return true; } return false; } } @SuppressWarnings("deprecation") private String formatTimestamp(long timestamp) { // Temporary - improve by showing how long ago the build happened Date date = new Date(timestamp); return date.toLocaleString(); } private void addSpacer(Composite composite, FormToolkit toolkit, int horizontalSpan, int verticalSpan) { toolkit.createLabel(composite, "").setLayoutData(new GridData(GridData.FILL, GridData.FILL, false, false, horizontalSpan, verticalSpan)); } private class StringEntry { private final Label label; private final Text text; public StringEntry(Composite composite, String name) { label = new Label(composite, SWT.NONE); label.setFont(boldFont); label.setText(name); label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING, false, false)); text = new Text(composite, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY); text.setData(FormToolkit.KEY_DRAW_BORDER, Boolean.FALSE); text.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING, true, false)); IDEUtil.paintBackgroundToMatch(text, composite); } public void setValue(String value, boolean visible) { boolean changed = visible != label.getVisible(); if (changed) { IDEUtil.setControlVisibility(label, visible); IDEUtil.setControlVisibility(text, visible); } if (visible) { String valueText = value != null && !value.isEmpty() ? value : Messages.AppOverviewEditorNotAvailable; if (!valueText.equals(text.getText())) { text.setText(valueText); changed = true; } } if (changed) { text.requestLayout(); } } } public interface BooleanAction { public void execute(boolean value); } private class LinkEntry { private final Text text; private final Hyperlink link; private String linkUrl; public LinkEntry(Composite composite, FormToolkit toolkit, String name, LinkAction action) { Label label = new Label(composite, SWT.NONE); label.setFont(boldFont); label.setText(name); // If not available then use a text field text = new Text(composite, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY); text.setData(FormToolkit.KEY_DRAW_BORDER, Boolean.FALSE); text.setText(Messages.AppOverviewEditorNotAvailable); text.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING, false, false)); IDEUtil.paintBackgroundToMatch(text, composite); link = toolkit.createHyperlink(composite, "", SWT.WRAP); link.setVisible(false); GridData data = new GridData(GridData.BEGINNING, GridData.BEGINNING, true, false); data.horizontalIndent = 2; data.exclude = true; link.setLayoutData(data); link.addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(org.eclipse.ui.forms.events.HyperlinkEvent e) { action.execute(linkUrl); } }); } public void setValue(String linkUrl, boolean visible) { this.linkUrl = linkUrl; if (linkUrl != null && !linkUrl.isEmpty()) { boolean changed = !link.getVisible() || !linkUrl.equals(link.getText()); link.setText(linkUrl); IDEUtil.setControlVisibility(link, true); IDEUtil.setControlVisibility(text, false); if (changed) { link.requestLayout(); } } else { boolean changed = !text.getVisible(); IDEUtil.setControlVisibility(link, false); IDEUtil.setControlVisibility(text, true); if (changed) { text.requestLayout(); } } } } public interface LinkAction { public void execute(String url); } @Override public void setFocus() { if (contents != null) { contents.setFocus(); } } public String metricsInjectionState(boolean injectMetricsAvailable, boolean injectMetricsEnabled) { if (injectMetricsAvailable) { return (injectMetricsEnabled) ? Messages.AppOverviewEditorInjectMetricsOn : Messages.AppOverviewEditorInjectMetricsOff; } return Messages.AppOverviewEditorInjectMetricsUnavailable; } }