package org.openslx.dozmod.gui.window; import java.awt.Color; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.nio.ByteBuffer; import java.util.*; import java.util.List; import javax.swing.AbstractAction; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.thrift.TException; import org.openslx.bwlp.thrift.iface.*; import org.openslx.dozmod.Branding; import org.openslx.dozmod.gui.Gui; import org.openslx.dozmod.gui.MainWindow; import org.openslx.dozmod.gui.changemonitor.AbstractControlWrapper; import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor; import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.TextNotEmptyConstraint; import org.openslx.dozmod.gui.helper.PopupMenu; import org.openslx.dozmod.gui.helper.*; import org.openslx.dozmod.gui.panel.ContainerPanel; import org.openslx.dozmod.gui.window.UserListWindow.UserAddedCallback; import org.openslx.dozmod.gui.window.layout.ImageDetailsWindowLayout; import org.openslx.dozmod.gui.wizard.ImageUpdateWizard; import org.openslx.dozmod.gui.wizard.LectureWizard; import org.openslx.dozmod.permissions.ImagePerms; import org.openslx.dozmod.thrift.ImageDetailsActions; import org.openslx.dozmod.thrift.Session; import org.openslx.dozmod.thrift.ThriftActions; import org.openslx.dozmod.thrift.ThriftActions.DeleteCallback; import org.openslx.dozmod.thrift.ThriftActions.ImageMetaCallback; import org.openslx.dozmod.thrift.ThriftError; import org.openslx.dozmod.thrift.cache.LectureCache; import org.openslx.dozmod.thrift.cache.MetaDataCache; import org.openslx.dozmod.thrift.cache.UserCache; import org.openslx.dozmod.util.DesktopEnvironment; import org.openslx.dozmod.util.FormatHelper; import org.openslx.sat.thrift.version.Feature; import org.openslx.thrifthelper.Comparators; import org.openslx.thrifthelper.TConst; import org.openslx.thrifthelper.ThriftManager; import org.openslx.util.QuickTimer; import org.openslx.util.QuickTimer.Task; import org.openslx.util.Util; /** * Window for displaying and editing the details of an image. */ public class ImageDetailsWindow extends ImageDetailsWindowLayout implements UiFeedback { /** * Version for serialization. */ private static final long serialVersionUID = -1901954219828374015L; private static final Logger LOGGER = LogManager.getLogger(ImageDetailsWindow.class); /** * Self-reference */ private final ImageDetailsWindow me = this; /** * Action handler proxying thrift calls to differentiate between local and * published image mode */ private final ImageDetailsActions actionHandler; /** * Callback interface to refresh image list after changing image details */ public interface ImageUpdatedCallback { public void updated(); } /** * Callback instance */ private ImageUpdatedCallback callback = null; /** * Image that this window shows the details of */ private ImageDetailsRead image = null; /** * The current state of custom permissions of the image */ private Map customPermissions; /** * Did the user have admin rights due to default permissions? */ private boolean adminRightsFromDefaultPermissions; /** * Popup menu items */ private final JMenuItem mnuNewLecture = new JMenuItem(I18n.WINDOW.getString("ImageDetails.MenuItem.newLecture.text")); private final JMenuItem mnuDownload = new JMenuItem(I18n.WINDOW.getString("ImageDetails.MenuItem.download.text")); private final JMenuItem mnuVmConfig = new JMenuItem(I18n.WINDOW.getString("ImageDetails.MenuItem.vmConfig.text")); private final JMenuItem mnuDelete = new JMenuItem(I18n.WINDOW.getString("ImageDetails.MenuItem.delete.text")); private final JMenuItem mnuExtendExpiryDate = new JMenuItem(I18n.WINDOW.getString("ImageDetails.MenuItem.extendExpirationDate.text")); private DialogChangeMonitor changeMonitor; private AbstractControlWrapper changeListenerPermissions; /** * Constructor * * @param modalParent parent of this popup window * @param callback callback to be called when the image details have changed */ public ImageDetailsWindow(Frame modalParent, ImageUpdatedCallback callback, ImageDetailsActions actionHandler) { super(modalParent); this.callback = callback; this.actionHandler = actionHandler; // Set up change monitor changeMonitor = new DialogChangeMonitor(new DialogChangeMonitor.Callback() { @Override public void validityChanged(String errorMessage) { lblError.setText(errorMessage); btnSaveChanges.setEnabled(changeMonitor.isValid() && changeMonitor.wasEverModified()); LOGGER.debug("Valid: " + changeMonitor.isValid()); } @Override public void modificationChanged() { btnSaveChanges.setEnabled(changeMonitor.isValid() && changeMonitor.wasEverModified()); LOGGER.debug("Changed: " + changeMonitor.isCurrentlyModified()); } }); // Hook when user presses X (top right) setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { safeClose(); } }); /* * Button listeners */ btnClose.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { safeClose(); } }); btnSaveChanges.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { saveChanges(); } }); btnUpdateImage.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { // FIXME: This will always discard all changes even if you cancel right away new ImageUpdateWizard(me, image).setVisible(true); refresh(); } }); btnUploadToMaster.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { uploadToMaster(); } }); btnShowLinkingLectures.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (safeClose()) { LectureListWindow page = MainWindow.showPage(LectureListWindow.class); page.filterByImageBaseId(image.imageBaseId); } } }); btnChangeOwner.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { UserListWindow.open(me, new UserAddedCallback() { @Override public void userAdded(UserInfo user, UserListWindow window) { window.dispose(); if (Gui.showMessageBox(me, I18n.WINDOW.getString("ImageDetails.Message.yesNo.changeOwner"), MessageType.QUESTION_YESNO, null, null)) setImageOwner(user); } }, I18n.WINDOW.getString("ImageDetails.Button.changeOwner.caption"), image.ownerId); } }); tblVersions.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); /* * Popup menu for the version table on the right side */ final PopupMenu pop = new PopupMenu(new ActionListener() { public void actionPerformed(ActionEvent e) { ImageVersionDetails selected = tblVersions.getSelectedItem(); if (selected == null) return; if (e.getSource().equals(mnuNewLecture)) { ImageSummaryRead summary = new ImageSummaryRead(); summary.setImageName( image.getImageName()); // Maybe create a helper class/function some day that transforms all fields new LectureWizard(me, summary, selected.getVersionId()).setVisible(true); } if (e.getSource().equals(mnuDownload)) { performImageDownload(selected); } if (e.getSource().equals(mnuDelete)) { deleteVersions(tblVersions.getSelectedItems()); } if (e.getSource().equals(mnuVmConfig)) { editVmConfig(selected); } if (e.getSource().equals(mnuExtendExpiryDate)) { extendVersionExpiry(tblVersions.getSelectedItems()); } } }); pop.addMenuItem(mnuNewLecture); pop.addMenuItem(mnuDownload); if (Session.isLectureRestrictionsSupported()) pop.addMenuItem(mnuVmConfig); pop.addSeparator(); pop.addMenuItem(mnuDelete); if (Session.canExtendImageExpiry()) { pop.addSeparator(); pop.addMenuItem(mnuExtendExpiryDate); } // keyboard shortcut tblVersions.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) .put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete"); tblVersions.getActionMap().put("delete", new AbstractAction() { /** * Version for serialization. */ private static final long serialVersionUID = -8997886497303940355L; @Override public void actionPerformed(ActionEvent ae) { if (ImagePerms.canEdit(image)) { deleteVersions(tblVersions.getSelectedItems()); } } }); /* * Mouse adapter for the version table */ final MouseAdapter ma = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { processClick(e); } @Override public void mouseReleased(MouseEvent e) { processClick(e); } private void processClick(MouseEvent e) { // rowIndex at mouse cursor int mouseRowIndex = tblVersions.rowAtPoint(e.getPoint()); // is the click event on an already selected row? boolean alreadySelectedRow = false; for (int i : tblVersions.getSelectedRows()) { if (i == mouseRowIndex) { alreadySelectedRow = true; break; } } if (mouseRowIndex >= 0 && mouseRowIndex < tblVersions.getRowCount() && SwingUtilities.isRightMouseButton(e)) { // select row if it wasn't in selection before if (!alreadySelectedRow) { tblVersions.setRowSelectionInterval(mouseRowIndex, mouseRowIndex); } if (e.isPopupTrigger()) { boolean multiSelection = tblVersions.getSelectedRowCount() != 1; ImageVersionDetails selectedVersion = tblVersions.getSelectedItem(); mnuNewLecture.setEnabled( !multiSelection && selectedVersion.isValid && ImagePerms.canLink(image)); mnuDownload.setEnabled(!multiSelection && ImagePerms.canDownload(image)); // Allow configuration edition for downloaded VMs for now // Docker images can not be edited since configuration menu for containers does not exist mnuVmConfig.setEnabled( !multiSelection && selectedVersion.isValid && ImagePerms.canEdit( image ) && !TConst.VIRT_DOCKER.equals(image.getVirtId())); mnuDelete.setEnabled(ImagePerms.canEdit(image)); mnuExtendExpiryDate.setEnabled( !multiSelection && (selectedVersion.isValid || Session.hasFeature( Feature.EXTEND_EXPIRED_VM))); pop.show(e.getComponent(), e.getX(), e.getY()); } } } }; scpVersions.addMouseListener(ma); tblVersions.addMouseListener(ma); // add controls to change monitor to handle user actions changeMonitor.add(txtTitle).addConstraint(new TextNotEmptyConstraint( I18n.WINDOW.getString("ImageDetails.TextField.title.error"))); changeMonitor.add(txtDescription).addConstraint(new TextNotEmptyConstraint( I18n.WINDOW.getString("ImageDetails.TextField.description.error"))); changeMonitor.add(txtTags); changeMonitor.addFixedCombo(cboOperatingSystem, Comparators.operatingSystem) .addConstraint(new DialogChangeMonitor.ValidationConstraint() { public String checkStateValid(OperatingSystem userInput) { if (userInput != null && MetaDataCache.getOsById(userInput.osId) != null) // validating OS coming from the cache makes no sense? return null; return I18n.WINDOW.getString("ImageDetails.ComboBox.OS.error"); } }); changeMonitor.addFixedCombo(cboShareMode, null) .addConstraint(new DialogChangeMonitor.ValidationConstraint() { public String checkStateValid(ShareMode userInput) { if (userInput != null) return null; return I18n.WINDOW.getString("ImageDetails.ComboBox.shareMode.error"); } }); changeMonitor.add(chkIsTemplate); changeMonitor.add(chkDefaultPermLink); changeMonitor.add(chkDefaultPermDownload); changeMonitor.add(chkDefaultPermEdit); changeMonitor.add(chkDefaultPermAdmin); changeListenerPermissions = changeMonitor.add(ctlImagePermissionConfigurator); // update default permissions hook for the permission configurator to apply to newly added users final ItemListener updateDefaultPermissionsListener = new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if (e.getSource() == chkDefaultPermLink) image.defaultPermissions.link = chkDefaultPermLink.isSelected(); if (e.getSource() == chkDefaultPermDownload) image.defaultPermissions.download = chkDefaultPermDownload.isSelected(); if (e.getSource() == chkDefaultPermEdit) image.defaultPermissions.edit = chkDefaultPermEdit.isSelected(); if (e.getSource() == chkDefaultPermAdmin) image.defaultPermissions.admin = chkDefaultPermAdmin.isSelected(); } }; chkDefaultPermLink.addItemListener(updateDefaultPermissionsListener); chkDefaultPermDownload.addItemListener(updateDefaultPermissionsListener); chkDefaultPermEdit.addItemListener(updateDefaultPermissionsListener); chkDefaultPermAdmin.addItemListener(updateDefaultPermissionsListener); /* * Initial state of GUI elements */ setFocusable(true); btnSaveChanges.setEnabled(false); txtVersion.setEditable(false); txtId.setEditable(false); chkIsTemplate.setEnabled(Session.isSuperUser()); btnUploadToMaster.setVisible(actionHandler.isImagePublishSupported()); makeEditable(false); // TODO finish ShareMode in server cboShareMode.setEnabled(false); } private void editVmConfig(final ImageVersionDetails selected) { QuickTimer.scheduleOnce(new Task() { ByteBuffer machineDescription = null; @Override public void fire() { try { machineDescription = ThriftManager.getSatClient() .getImageVersionVirtConfig(Session.getSatelliteToken(), selected.versionId); } catch (TException e) { LOGGER.error("Failed to retrieve virtualizer config for image version " + "'" + image.latestVersionId + ", see trace: ", e); return; } Gui.asyncExec(new Runnable() { @Override public void run() { if (machineDescription == null) { Gui.showMessageBox( I18n.WINDOW.getString("ImageDetails.Message.error.couldNotRetrieveVMConfig"), MessageType.ERROR, LOGGER, null); return; } VirtDropDownConfigEditorWindow.open(me, actionHandler, selected.versionId, machineDescription); } }); } }); } /******************************************************************************** * * Helper triggering the actual thrift calls * ********************************************************************************/ /** * Sets the image to the given imageBaseId. This will also trigger fill() * which will set the image details fields to the values represented by this * image. * * @param imageBaseId the id of the image to be displayed */ public void setImage(final String imageBaseId) { // Just prime the cache... MetaDataCache.getOperatingSystems(); MetaDataCache.getVirtualizers(); final ImageMetaCallback callback = new ImageMetaCallback() { @Override public void fetchedImageDetails(ImageDetailsRead imageDetails, Map permissions) { if (imageDetails == null) { return; } if (permissions == null) { permissions = new HashMap<>(); } synchronized (me) { image = imageDetails; customPermissions = permissions; } fillDetails(); } }; actionHandler.getImageDetails(imageBaseId, callback); } /** * Sets the owner of the selected image to the given user. * * @param user UserInfo to set the owner to */ private void setImageOwner(final UserInfo user) { if (!actionHandler.setImageOwner(image.getImageBaseId(), user)) { return; } Gui.showMessageBox(me, I18n.WINDOW.getString("ImageDetails.Message.info.setImageOwner", FormatHelper.userName(user)), MessageType.INFO, null, null); makeEditable(false); refresh(); } /** * Called by the "Publish" button. Tries to upload that image to the * masterserver. */ private void uploadToMaster() { // 04.2018: bail if we the user tries to publish a non-vmware image. if (Session.hasFeature(Feature.MULTIPLE_HYPERVISORS) && !TConst.VIRT_VMWARE.equals(image.virtId)) { Gui.showMessageBox( I18n.WINDOW.getString("ImageDetails.Message.error.publishNonVMWareImage", MetaDataCache.getVirtualizerById(image.virtId).virtName), MessageType.ERROR, null, null); return; } if (changeMonitor.isCurrentlyModified()) { if (Gui.showMessageBox(I18n.WINDOW.getString("ImageDetails.Message.yesNo.currentlyModified"), MessageType.QUESTION_YESNO, null, null)) { if (!saveChangesInternal()) { return; } } } // let the user confirm or abort if (!Gui.showMessageBox(I18n.WINDOW.getString("ImageDetails.Message.yesNo.confirmOrAbort"), MessageType.QUESTION_YESNO, null, null)) return; // start upload to masterserver QuickTimer.scheduleOnce(new Task() { @Override public void fire() { final String transferId; try { transferId = ThriftManager.getSatClient() .publishImageVersion(Session.getSatelliteToken(), image.latestVersionId); } catch (TException e1) { ThriftError.showMessage(me, LOGGER, e1, I18n.WINDOW.getString("ImageDetails.Message.error.uploadFailed", DesktopEnvironment.Link.REGISTER_BWIDM.uri.toString())); return; } Gui.asyncExec(new Runnable() { @Override public void run() { MainWindow.addPassiveTransfer(transferId, image.imageName, true); // Inform user Gui.showMessageBox(ImageDetailsWindow.this, I18n.WINDOW.getString("ImageDetails.Message.info.passiveTransfer", Branding.getServiceName(), Branding.getApplicationName()), MessageType.INFO, null, null); } }); } }); } /** * Called by the "Save" button, tries to save the changes internally and then * react based depending on the outcome of the save */ private void saveChanges() { boolean saved = saveChangesInternal(); // if there was nothing to save, saved would be true from the above call // however we wouldn't even get to saving if nothing was changed, so its fine if (saved) { if (callback != null) callback.updated(); dispose(); } else { btnSaveChanges.setEnabled(true); } } /** * Helper to only save the changes, nothing else. Updating GUI elements is done * by saveChanges() * * @return false if any try to save changes failed, true otherwise */ private boolean saveChangesInternal() { // Special case: User has admin rights through default permissions // -> user removes default admin permissions // -> first save custom permissions, then the rest (including default // permissions) if (adminRightsFromDefaultPermissions && changeListenerPermissions.isCurrentlyChanged()) { if (!saveCustomPermissions()) { return false; } changeListenerPermissions.reset(); } // first build the ImageBaseWrite from the GUI fields final ImageBaseWrite ibw = new ImageBaseWrite(txtTitle.getText(), txtDescription.getText(), cboOperatingSystem.getItemAt(cboOperatingSystem.getSelectedIndex()).osId, image.virtId, chkIsTemplate.isSelected(), new ImagePermissions(image.defaultPermissions.link, image.defaultPermissions.download, image.defaultPermissions.edit, image.defaultPermissions.admin), cboShareMode.getItemAt(cboShareMode.getSelectedIndex())); try { actionHandler.updateImageBase(image.getImageBaseId(), ibw); LOGGER.info("Successfully saved new metadata"); } catch (TException e) { ThriftError.showMessage(me, LOGGER, e, I18n.WINDOW.getString("ImageDetails.Message.error.saveChangesInternal")); return false; } if (changeListenerPermissions.isCurrentlyChanged()) { if (!saveCustomPermissions()) { return false; } } // TODO maybe there could be a nicer way to distinguish between image and containerImage if (image != null && image.getVirtId().equals(TConst.VIRT_DOCKER) ) { if(!pnlTabContainer.saveChanges(Session.getSatelliteToken(),image)) return false; } changeMonitor.reset(); return true; } /** * Helper to save the custom user permissions (those not included in * ImageBaseWrite). * * @return true if successfully saved, false otherwise. */ private boolean saveCustomPermissions() { try { actionHandler.writeImagePermissions(image.getImageBaseId(), ctlImagePermissionConfigurator.getPermissions()); LOGGER.info("Successfully saved new custom permissions"); } catch (TException e) { ThriftError.showMessage(me, LOGGER, e, I18n.WINDOW.getString("ImageDetails.Message.error.saveCustomPermissions")); return false; } return true; } /** * Triggers the download of the given image version. * * @param selected image to download. */ private void performImageDownload(ImageVersionDetails selected) { if (selected.getVersionId() == null) { Gui.showMessageBox(this, I18n.WINDOW.getString("ImageDetails.Message.error.performImageDownload"), MessageType.ERROR, null, null); return; } // using actionHandler here is not needed, as this ThriftAction works for // downloads // from either the master server or the satellite server ThriftActions.initDownload(JOptionPane.getFrameForComponent(this), selected.versionId, image.imageName, image.virtId, image.osId, selected.fileSize, null); } /** * Triggers the deletion of the given image version. * * @param version image version to delete. */ private void deleteVersion(final ImageVersionDetails version) { if (version == null) return; actionHandler.deleteImageVersion(version, new DeleteCallback() { @Override public void isDeleted(boolean success) { refresh(); } }); } /** * Triggers the deletion of a list of versions. * * @param versions to delete */ private void deleteVersions(List versions) { if (versions == null || versions.isEmpty()) return; for (ImageVersionDetails version : versions) { deleteVersion(version); } } /** * Extends the expiration date for given image versions to current date plus the * user-supplied duration. * * @param versions to extend the validity of */ private void extendVersionExpiry(List versions) { int daysToExtend = -1; if (versions.size() > 1) { // more than one version given, ask the user once and use the value for all // versions. daysToExtend = ExpiryDateChooser.askFutureExpiryDuration(this, null); if (daysToExtend == -1) return; } int count = 0; for (ImageVersionDetails img : versions) { long currentExpiryTime = Math.max(img.expireTime, Util.unixTime()); Date currentExpiryTimeAsDate = DateTimeHelper.endOfDay(new Date(currentExpiryTime * 1000L)); if (daysToExtend == -1) { daysToExtend = ExpiryDateChooser.askFutureExpiryDuration(this, currentExpiryTimeAsDate); if (daysToExtend == -1) return; } currentExpiryTimeAsDate = DateTimeHelper.addDaysTo(currentExpiryTimeAsDate, daysToExtend); try { ThriftManager.getSatClient() .setImageVersionExpiry(Session.getSatelliteToken(), img.versionId, currentExpiryTimeAsDate.getTime() / 1000L); count++; } catch (TException e) { ThriftError.showMessage(this, LOGGER, e, I18n.WINDOW.getString("ImageDetails.Message.error.extendVersionExpiry", img.versionId)); } } if (count > 0) { Gui.showMessageBox(this, I18n.WINDOW.getString("ImageDetails.Message.info.extendVersionExpiry", count), MessageType.INFO, null, null); refresh(); } } /******************************************************************************** * * General UI helpers * ********************************************************************************/ /** * */ private void refresh() { String baseId = image.getImageBaseId(); synchronized (me) { image = null; } setImage(baseId); } /** * callback function when we received the image's details from the server */ private void fillDetails() { if (image == null) return; txtTitle.setText(image.getImageName()); txtTitle.setCaretPosition(0); txtDescription.setText(image.getDescription()); lblOwner.setUser(UserCache.find(image.getOwnerId())); lblUpdater.setUser(UserCache.find(image.getUpdaterId())); lblCreateTime.setText(FormatHelper.longDate(image.getCreateTime())); lblUpdateTime.setText(FormatHelper.longDate(image.getUpdateTime())); txtVersion.setText(image.getLatestVersionId()); txtId.setText(image.getImageBaseId()); chkIsTemplate.setSelected(image.isTemplate); setTitle(image.getImageName()); // fill os combo, but only once :) if (cboOperatingSystem.getItemCount() == 0) { List osList = MetaDataCache.getOperatingSystems(); // all fine, lets sort it Collections.sort(osList, new Comparator() { public int compare(OperatingSystem o1, OperatingSystem o2) { return o1.getOsName().compareTo(o2.getOsName()); } }); for (OperatingSystem os : osList) { cboOperatingSystem.addItem(os); } cboOperatingSystem.setSelectedItem(new OperatingSystem(image.getOsId(), null, null, null, 0, 0)); } Virtualizer virt = MetaDataCache.getVirtualizerById(image.getVirtId(), true); if (virt != null) lblVirtualizer.setText(virt.getVirtName()); // fill share mode combo, if not already done if (cboShareMode.getItemCount() == 0) { for (ShareMode mode : ShareMode.values()) { cboShareMode.addItem(mode); } } cboShareMode.setSelectedItem(image.getShareMode()); String tagsString = ""; if (image.getTags() != null) { for (String tag : image.getTags()) { tagsString = tagsString + ", " + tag; } } txtTags.setText(tagsString); // init permissions, remember if the user had admin rights through default // permissions adminRightsFromDefaultPermissions = image.defaultPermissions.admin; ctlImagePermissionConfigurator.initPanel(customPermissions, image.defaultPermissions, image.ownerId); chkDefaultPermAdmin.setSelected(image.defaultPermissions.admin); chkDefaultPermEdit.setSelected(image.defaultPermissions.edit); chkDefaultPermDownload.setSelected(image.defaultPermissions.download); chkDefaultPermLink.setSelected(image.defaultPermissions.link); // Count the number of linked lectures to the image int lectureCount = 0; for (LectureSummary lecture : LectureCache.get(false)) { if (lecture == null || lecture.imageBaseId == null) continue; if (lecture.imageBaseId.equals(image.imageBaseId)) lectureCount++; } lblLinkedLectureCount.setText(Integer.toString(lectureCount)); lblLinkedLectureCount.setForeground(lectureCount > 0 ? null : Color.RED); btnShowLinkingLectures.setEnabled(lectureCount > 0); // set the versions of the image to the table tblVersions.setData(image.getVersions(), true); // make fields editable is allowed makeEditable(true); changeMonitor.reset(); // finally do show it all setVisible(true); } /** * Enables/disables the editable fields based on 'editable' * * @param editable true to make fields editable, false otherwise. */ private void makeEditable(boolean editable) { // always disable all buttons unless image's share mode is LOCAL or PUBLISH if (image != null) { editable &= image.shareMode == ShareMode.LOCAL || image.shareMode == ShareMode.PUBLISH; } // now do the regular permission check editable = editable && (ImagePerms.canEdit(image) || ImagePerms.canAdmin(image)); txtTitle.setEditable(editable); txtDescription.setEditable(editable); txtTags.setEditable(editable); cboOperatingSystem.setEnabled(editable); // cboShareMode.setEnabled(editable); btnChangeOwner.setEnabled(editable && ImagePerms.canAdmin(image)); btnUpdateImage.setEnabled(editable); if (actionHandler.isImagePublishSupported()) btnUploadToMaster.setEnabled(editable); // If this Image is from type Docker (Container) show the new Container Tab if (image != null && image.getVirtId().equals(TConst.VIRT_DOCKER)) { lblVirtualizer.setText(TConst.VIRT_DOCKER); pnlTabContainer.init(Session.getSatelliteToken(), image, ContainerPanel.IMAGE_CONTEXT); pnlTabContainer.addToChangeMonitor(changeMonitor); showContainerTab(); // TODO Currently, do not allow user to upload a new version or share an Container Image. btnUploadToMaster.setEnabled(false); } } /** * Opens a new ImageDetailsWindow showing the details of the image with ID = * imageBaseId * * @param modalParent parent of this window * @param imageBaseId id of the image to set the details of */ public static void open(Frame modalParent, String imageBaseId, ImageUpdatedCallback callback, ImageDetailsActions actionHandler) { ImageDetailsWindow win = new ImageDetailsWindow(modalParent, callback, actionHandler); win.setImage(imageBaseId); win.setVisible(true); } /* ******************************************************************************* * * Dialog class overrides * * ******************************************************************************* */ @Override public void setVisible(boolean visible) { if (!isVisible()) { pack(); MainWindow.centerShell(this); } super.setVisible(visible); } /* ******************************************************************************* * * UIFeedback implementation * * ******************************************************************************* */ @Override public boolean wantConfirmQuit() { return changeMonitor.isCurrentlyModified(); } @Override public void escapePressed() { safeClose(); } /* * Safe close helper: checks if we have unsaved work and prompt the user for * confirmation if so */ private boolean safeClose() { if (changeMonitor.isCurrentlyModified() && !Gui.showMessageBox(me, I18n.WINDOW.getString("ImageDetails.Message.yesNo.safeClose"), MessageType.QUESTION_YESNO, null, null)) return false; dispose(); return true; } }