summaryrefslogtreecommitdiffstats
path: root/dozentenmodul/src
diff options
context:
space:
mode:
authorManuel Bentele2021-05-19 11:46:36 +0200
committerManuel Bentele2021-05-19 11:46:36 +0200
commitaad75f084c9635878a11e701163ad66e034023ba (patch)
treee18aef591ccd4616687ed53e0b407d58014d9785 /dozentenmodul/src
parent[client,server] Rename 'vm.disk' package to 'virtualization.disk' (diff)
parent[client] Allow user to set a container type for container images (diff)
downloadtutor-module-aad75f084c9635878a11e701163ad66e034023ba.tar.gz
tutor-module-aad75f084c9635878a11e701163ad66e034023ba.tar.xz
tutor-module-aad75f084c9635878a11e701163ad66e034023ba.zip
Merge branch 'feature/docker'
Diffstat (limited to 'dozentenmodul/src')
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/panel/ContainerPanel.java57
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ImageDetailsWindow.java8
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/layout/ContainerUploadPageLayout.java27
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ContainerUploadPage.java9
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/model/ContainerBuildContextMethod.java2
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/model/ContainerMeta.java66
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/util/ContainerUtils.java3
-rw-r--r--dozentenmodul/src/main/properties/i18n/page.properties1
-rw-r--r--dozentenmodul/src/main/properties/i18n/page_de_DE.properties2
-rw-r--r--dozentenmodul/src/main/properties/i18n/page_layout.properties2
-rw-r--r--dozentenmodul/src/main/properties/i18n/page_layout_de_DE.properties2
11 files changed, 161 insertions, 18 deletions
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/panel/ContainerPanel.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/panel/ContainerPanel.java
index 37008e78..8b79d064 100644
--- a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/panel/ContainerPanel.java
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/panel/ContainerPanel.java
@@ -6,11 +6,12 @@ import org.openslx.bwlp.thrift.iface.ImageDetailsRead;
import org.openslx.dozmod.gui.Gui;
import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor;
import org.openslx.dozmod.gui.configurator.ContainerBindMountConfigurator;
+import org.openslx.dozmod.gui.control.ComboBox;
import org.openslx.dozmod.gui.control.QLabel;
import org.openslx.dozmod.gui.helper.GridManager;
import org.openslx.dozmod.gui.helper.I18n;
-import org.openslx.dozmod.model.ContainerBuildContextMethod;
import org.openslx.dozmod.model.ContainerDefinition;
+import org.openslx.dozmod.model.ContainerMeta;
import org.openslx.thrifthelper.ThriftManager;
import org.openslx.util.ThriftUtil;
@@ -32,7 +33,9 @@ public class ContainerPanel extends JPanel {
private final Logger LOGGER = Logger.getLogger(ContainerBindMountConfigurator.class);
private final QLabel lblContainerRunOpt;
- private final QLabel lblContainerImageName;
+
+ private final QLabel lblContainerImageType;
+ private final ComboBox<ContainerMeta.ContainerImageType> cboContainerImageType;
private final JTextArea txtContainerRecipe;
private final JTextField txtContainerRun;
@@ -63,7 +66,7 @@ public class ContainerPanel extends JPanel {
GridManager grdContainer = new GridManager(this, 2, false, new Insets(8, 2, 8, 2));
- lblContainerImageName = new QLabel(
+ QLabel lblContainerImageName = new QLabel(
I18n.PANEL.getString("ContainerPanel.Label.ImageName.text"));
grdContainer.add(lblContainerImageName);
txtContainerImageName = new JTextField();
@@ -75,6 +78,23 @@ public class ContainerPanel extends JPanel {
scrollableTextArea.setMinimumSize(Gui.getScaledDimension(0, 200));
scrollableTextArea.setPreferredSize(Gui.getScaledDimension(0, 200));
grdContainer.add(scrollableTextArea, 2).fill(true, true).expand(true, true);
+
+
+ lblContainerImageType = new QLabel("Container Image Type");
+ cboContainerImageType = new ComboBox<>(new ComboBox.ComboBoxRenderer<ContainerMeta.ContainerImageType>() {
+ @Override public String renderItem(ContainerMeta.ContainerImageType item) {
+ if (item == null)
+ return "null";
+ return item.name();
+ }
+ }, ContainerMeta.ContainerImageType.class);
+ for (ContainerMeta.ContainerImageType type:ContainerMeta.ContainerImageType.values()) {
+ cboContainerImageType.addItem(type);
+ }
+ cboContainerImageType.setSelectedItem(ContainerMeta.ContainerImageType.LECTURE);
+ grdContainer.add(lblContainerImageType);
+ grdContainer.add(cboContainerImageType).fill(true,false).expand(true,false);
+
grdContainer.add(pnlContainerMeta, 2).fill(true, true).expand(true, true);
grdContainer.finish(true);
@@ -103,14 +123,25 @@ public class ContainerPanel extends JPanel {
txtContainerImageName.setText(image.imageName);
txtContainerImageName.setEnabled(false);
- // TODO simplify this mess. ContainerBuildContextMethod is to complex
- if (containerDefinition.getBuildContextMethod() == ContainerBuildContextMethod.FILE) {
+ // TODO simplify this mess. ContainerBuildContextMethod is to complex or useless
+ switch (containerDefinition.getBuildContextMethod())
+ {
+ case FILE:
txtContainerRecipe.setText(containerDefinition.getContainerRecipe());
- } else if (containerDefinition.getBuildContextMethod()
- == ContainerBuildContextMethod.GIT_REPOSITORY) {
+ break;
+ case IMAGE_REPO:
+ txtContainerRecipe.setText(containerDefinition.getContainerMeta().getImageRepo());
+ break;
+ case GIT_REPOSITORY:
txtContainerRecipe.setText(containerDefinition.getContainerMeta().getBuildContextUrl());
+ break;
+ default:
+ LOGGER.error("Unknown Build Context");
+ break;
}
+ cboContainerImageType.setSelectedItem(containerDefinition.getContainerMeta().getImageType());
+
if (context.equals(IMAGE_CONTEXT)) {
initImageDetails();
} else if (context.equals(CONTAINER_CONTEXT)) {
@@ -118,13 +149,14 @@ public class ContainerPanel extends JPanel {
} else {
LOGGER.error("Container Panel init failed: Please use proper context");
}
-
}
private void initImageDetails() {
- // currently do not allow user to change the Dockerfile in the suite.
+ // currently do not allow user to change the Image Repository or Dockerfile in the suite.
txtContainerRecipe.setEnabled(false);
+
+ // do not show container specific input options
lblContainerRunOpt.setVisible(false);
txtContainerRun.setVisible(false);
bindMountConfigurator.setVisible(false);
@@ -133,6 +165,9 @@ public class ContainerPanel extends JPanel {
private void initContainerDetails() {
txtContainerRecipe.setEnabled(false);
+ lblContainerImageType.setVisible(false);
+ cboContainerImageType.setEditable(false);
+ cboContainerImageType.setVisible(false);
txtContainerRun.setText(containerDefinition.getContainerMeta().getRunOptions());
bindMountConfigurator.setData(containerDefinition.getContainerMeta().getBindMountConfig());
}
@@ -144,6 +179,7 @@ public class ContainerPanel extends JPanel {
I18n.PANEL.getString("ContainerPanel.Constraint.NoEmptyDockerfile.text")));
changeMonitor.add(txtContainerRun);
changeMonitor.add(bindMountConfigurator);
+ changeMonitor.addFixedCombo(cboContainerImageType,null);
isFirstTime = false;
}
}
@@ -159,11 +195,14 @@ public class ContainerPanel extends JPanel {
ContainerDefinition newConDev = new ContainerDefinition(containerDefinition);
+ newConDev.getContainerMeta().setImageType(
+ (ContainerMeta.ContainerImageType) cboContainerImageType.getSelectedItem());
newConDev.getContainerMeta().setRunOptions(txtContainerRun.getText());
newConDev.getContainerMeta().setBindMountConfig(bindMountConfigurator.getData());
// TODO do I really need this check? Maybe just get every setting and upload it.
if (!newConDev.equals(containerDefinition)) {
+ LOGGER.info("Update Container Definition");
try {
ThriftManager.getSatClient()
.setImageVersionVirtConfig(satelliteToken, image.getLatestVersionId(),
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ImageDetailsWindow.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ImageDetailsWindow.java
index a847082f..3fc632b8 100644
--- a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ImageDetailsWindow.java
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/window/ImageDetailsWindow.java
@@ -576,6 +576,14 @@ public class ImageDetailsWindow extends ImageDetailsWindowLayout
}
}
+ // 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;
+ } else {
+ LOGGER.error("No Image: Could not save Container Settings!");
+ }
+
changeMonitor.reset();
return true;
}
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/layout/ContainerUploadPageLayout.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/layout/ContainerUploadPageLayout.java
index 5fcc6dcd..0496261f 100644
--- a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/layout/ContainerUploadPageLayout.java
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/layout/ContainerUploadPageLayout.java
@@ -24,6 +24,8 @@ public class ContainerUploadPageLayout extends WizardPage {
protected final QLabel lblImageName;
protected final JTextArea txtInfoText;
+ protected final JTextField txtImageRepo;
+
protected final JTabbedPane tpInput;
protected final JTextField txtGitRepo;
@@ -38,8 +40,24 @@ public class ContainerUploadPageLayout extends WizardPage {
setDescription(I18n.PAGE_LAYOUT.getString("ContainerUploadPage.description"));
GridManager grid = new GridManager(this, 3, false);
+ JPanel imageRepoPanel = new JPanel();
+ imageRepoPanel.setVisible(true);
+ GridManager tmpGrid = new GridManager(imageRepoPanel, 2, true, new Insets(5, 0, 5, 0));
+ QLabel lblImageRepo = new QLabel(
+ I18n.PAGE_LAYOUT.getString("ContainerUploadPage.DockerFile.label"));
+ lblImageRepo.setToolTipText(
+ I18n.PAGE_LAYOUT.getString("ContainerUploadPage.ImageRepository.ToolTipText"));
+ txtImageRepo = new JTextField();
+ txtImageRepo.setEditable(true);
+ txtImageRepo.setToolTipText(
+ I18n.PAGE_LAYOUT.getString("ContainerUploadPage.ImageRepository.ToolTipText"));
+ tmpGrid.add(lblImageRepo);
+ tmpGrid.add(txtImageRepo).fill(true, false).expand(true, false);
+ tmpGrid.finish(false);
+
+
JPanel p1 = new JPanel();
- p1.setVisible(true);
+ p1.setVisible(false);
GridManager g1 = new GridManager(p1, 3, true, new Insets(5, 0, 5, 0));
QLabel imageFileCaption = new QLabel(
I18n.PAGE_LAYOUT.getString("ContainerUploadPage.DockerFile.label"));
@@ -68,13 +86,12 @@ public class ContainerUploadPageLayout extends WizardPage {
tpInput = new JTabbedPane();
tpInput.addTab("Dockerfile", p1);
tpInput.addTab("Git Repository", p2);
- tpInput.setSelectedIndex(ContainerBuildContextMethod.FILE.ordinal());
+ tpInput.addTab("Image Repository", imageRepoPanel);
+ // set "Image Repository" as selected
+ tpInput.setSelectedIndex(2);
grid.add(tpInput, 3).fill(true, false);
- // Start as with Dockerfile as input!
- tpInput.setSelectedIndex(0);
-
lblImageName = new QLabel(I18n.PANEL.getString("ContainerPanel.Label.ImageName.text"));
txtImageName = new JTextField();
grid.add(lblImageName);
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ContainerUploadPage.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ContainerUploadPage.java
index c2db8554..466b26a0 100644
--- a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ContainerUploadPage.java
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/wizard/page/ContainerUploadPage.java
@@ -233,6 +233,12 @@ public class ContainerUploadPage extends ContainerUploadPageLayout {
ContainerBuildContextMethod method = getBuildContextMethod();
switch (method) {
+ case IMAGE_REPO:
+ if (txtImageRepo.getText() == null || txtImageRepo.getText().isEmpty()) {
+ setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoImageRepo"));
+ return false;
+ }
+ break;
case FILE:
if (txtImageFile.getText() == null || txtImageFile.getText().isEmpty()) {
setWarningMessage(I18n.PAGE.getString("ContainerUploadPage.Warning.NoReceipt"));
@@ -271,6 +277,9 @@ public class ContainerUploadPage extends ContainerUploadPageLayout {
case FILE:
containerDefinition.setContainerRecipe(state.descriptionFile);
break;
+ case IMAGE_REPO:
+ containerMeta.setImageRepo(txtImageRepo.getText());
+ state.descriptionFile = getDummyFile();
case GIT_REPOSITORY:
containerMeta.setBuildContextUrl(txtGitRepo.getText());
state.descriptionFile = getDummyFile();
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/model/ContainerBuildContextMethod.java b/dozentenmodul/src/main/java/org/openslx/dozmod/model/ContainerBuildContextMethod.java
index 6ed42ba8..54b7bd39 100644
--- a/dozentenmodul/src/main/java/org/openslx/dozmod/model/ContainerBuildContextMethod.java
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/model/ContainerBuildContextMethod.java
@@ -2,7 +2,7 @@ package org.openslx.dozmod.model;
public enum ContainerBuildContextMethod {
- FILE, GIT_REPOSITORY;
+ FILE, GIT_REPOSITORY,IMAGE_REPO;
public static ContainerBuildContextMethod fromInt(int index) {
return values()[index];
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/model/ContainerMeta.java b/dozentenmodul/src/main/java/org/openslx/dozmod/model/ContainerMeta.java
index 32818acf..0306d0ee 100644
--- a/dozentenmodul/src/main/java/org/openslx/dozmod/model/ContainerMeta.java
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/model/ContainerMeta.java
@@ -7,21 +7,64 @@ import java.util.Objects;
/**
* ContainerMeta is used to store container specific information.
* An object of this class will be serialized with gson to a json file.
+ *
+ * TODO remove build_context_method
+ * no need to distinguish beettween methods
+ * TODO rename build_context_url to build_context
*/
public class ContainerMeta {
+ public enum ContainerImageType implements org.apache.thrift.TEnum {
+ LECTURE("lecture"),
+ BATCH("batch"),
+ DATA("data");
+
+ private final String name;
+
+ ContainerImageType(String name){
+ this.name = name;
+ }
+
+ public boolean equalNames(String other){
+ return name.equals(other);
+ }
+ public static ContainerImageType getByName(String name){
+ for (ContainerImageType item : ContainerImageType.values()){
+ if (item.name.equals(name))
+ return item;
+ }
+ // name is not an enum, return lecture as default
+ return LECTURE;
+ }
+
+
+
+ @Override public String toString() {
+ return this.name;
+ }
+
+ @Override public int getValue() {
+ return this.ordinal();
+ }
+ }
+
+
private int build_context_method;
+ private String image_repo;
private String build_context_url;
private String image_name;
private String run_options;
+ private String image_type;
private List<ContainerBindMount> bind_mount_config = new ArrayList<>();
public ContainerMeta() {
+ image_repo = "";
build_context_method = ContainerBuildContextMethod.FILE.ordinal();
build_context_url = "";
image_name = "";
run_options = "";
+ image_type = ContainerImageType.LECTURE.toString();
bind_mount_config = new ArrayList<>();
}
@@ -30,6 +73,8 @@ public class ContainerMeta {
build_context_url = containerMeta.build_context_url;
image_name = containerMeta.image_name;
run_options = containerMeta.run_options;
+ image_repo = containerMeta.image_repo;
+
for (ContainerBindMount bm : containerMeta.bind_mount_config)
bind_mount_config.add(new ContainerBindMount(bm.getSource(), bm.getTarget(), bm.getOptions()));
@@ -75,6 +120,21 @@ public class ContainerMeta {
this.bind_mount_config = bindMountConfig;
}
+ public String getImageRepo() { return image_repo; }
+
+ public void setImageRepo(String from_image) { this.image_repo = from_image; }
+
+ public ContainerImageType getImageType() {
+ if (image_type == null || image_type.length() == 0)
+ return ContainerImageType.LECTURE;
+
+ return ContainerImageType.getByName(image_type);
+ }
+
+ public void setImageType(ContainerImageType image_type) {
+ this.image_type = image_type.toString();
+ }
+
@Override public boolean equals(Object o) {
if (this == o)
return true;
@@ -83,10 +143,12 @@ public class ContainerMeta {
ContainerMeta that = (ContainerMeta) o;
return Objects.equals(build_context_url, that.build_context_url) && Objects.equals(image_name,
that.image_name) && Objects.equals(run_options, that.run_options) && Objects.equals(
- bind_mount_config, that.bind_mount_config);
+ bind_mount_config, that.bind_mount_config) && Objects.equals(image_repo,that.image_repo)
+ && Objects.equals(image_type,that.image_type);
}
@Override public int hashCode() {
- return Objects.hash(build_context_url, image_name, run_options);
+ return Objects.hash(build_context_url, image_name, run_options,
+ bind_mount_config, image_repo, image_type);
}
}
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/util/ContainerUtils.java b/dozentenmodul/src/main/java/org/openslx/dozmod/util/ContainerUtils.java
index eeaae891..0e5a1d15 100644
--- a/dozentenmodul/src/main/java/org/openslx/dozmod/util/ContainerUtils.java
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/util/ContainerUtils.java
@@ -27,7 +27,8 @@ public class ContainerUtils {
if (image != null && image.getVirtId().equals(TConst.VIRT_DOCKER)) {
List<LectureSummary> lectureSummaries = LectureCache.get(true);
for (LectureSummary lecture : lectureSummaries) {
- if (lecture.imageBaseId.equals(image.imageBaseId)) {
+ // lecture.imageBaseId is null when no image linked to it -> NullPointer on equals
+ if (image.imageBaseId.equals(lecture.imageBaseId)) {
return true;
}
}
diff --git a/dozentenmodul/src/main/properties/i18n/page.properties b/dozentenmodul/src/main/properties/i18n/page.properties
index f9c094a2..fb951700 100644
--- a/dozentenmodul/src/main/properties/i18n/page.properties
+++ b/dozentenmodul/src/main/properties/i18n/page.properties
@@ -69,6 +69,7 @@ ImageOvfConversion.Dialog.RemoveTmpDirectory.title=Delete temporary directory?
# ContainerUploadPage
ContainerUploadPage.Description.ContainerDefFinished=Container definition finished
+ContainerUploadPage.Warning.NoImageRepo=No Image Repository provided
ContainerUploadPage.Warning.NoReceipt=No Container Recipe provided!
ContainerUploadPage.Warning.NoGitRepository=No Git Repository provided!
ContainerUploadPage.Warning.NoProperName=Set proper Image Name
diff --git a/dozentenmodul/src/main/properties/i18n/page_de_DE.properties b/dozentenmodul/src/main/properties/i18n/page_de_DE.properties
index 43ab8f41..599bb26d 100644
--- a/dozentenmodul/src/main/properties/i18n/page_de_DE.properties
+++ b/dozentenmodul/src/main/properties/i18n/page_de_DE.properties
@@ -70,6 +70,7 @@ ContainerUploadPage.Warning.NoReceipt=Keine Containeranweisungen angegeben!
ContainerUploadPage.Warning.NoGitRepository=Kein Git Repository angegeben!
ContainerUploadPage.Warning.NoProperName=Bitte Imagenamen setzen
ContainerUploadPage.Warning.NoRunOptions=Bitte Container Start Optionen setzen
+ContainerUploadPage.Warning.NoImageRepo=Kein Image Repository angegeben
# ImageUploadSummaryPage
ImageUploadSummary.UploadInitState.requesting=Der Upload-Vorgang wird mit dem Server ausgehandelt...
@@ -112,3 +113,4 @@ LectureLocationSelection.WizardPage.errorMessage.tooManyLocations=Zu viele Räum
# LectureOptionsPage
LectureOptions.WizardPage.description=Klicken Sie auf ''Weiter'', um Berechtigungen festzulegen \
oder ''Fertigstellen''.
+
diff --git a/dozentenmodul/src/main/properties/i18n/page_layout.properties b/dozentenmodul/src/main/properties/i18n/page_layout.properties
index bc282897..b14eae34 100644
--- a/dozentenmodul/src/main/properties/i18n/page_layout.properties
+++ b/dozentenmodul/src/main/properties/i18n/page_layout.properties
@@ -60,8 +60,10 @@ ImageOvfConversion.Button.StartConversion.text=Start conversion
ContainerUploadPage.title=Create new Container Image
ContainerUploadPage.description=Please provide an Input for a Docker Image
ContainerUploadPage.DockerFile.label=Docker File
+ContainerUploadPage.ImageRepo.label=Image Repository
ContainerUploadPage.GitRepository.label=Git Repository
ContainerUploadPage.GitRepository.toolTipText=Set clone address of Git Repository [git@ | http://] [.git]. Currently no Validation Checks!
+ContainerUploadPage.ImageRepository.ToolTipText=Specify the Container Image by his Repository Name (e.g python:3.7 or tensorflow/tensorflow)
ContainerUploadPage.CheckBox.ContainsLicenseRestricted.text=Contains license restricted software
ContainerUploadPage.ContainerImageFile.label=Pre-build Container Image
ContainerUploadPage.ContainerImageFile.ToolTipText=<html>Optionally attach a tar archive of a container image to the upload.<br>\
diff --git a/dozentenmodul/src/main/properties/i18n/page_layout_de_DE.properties b/dozentenmodul/src/main/properties/i18n/page_layout_de_DE.properties
index cd5b7f9e..de385d68 100644
--- a/dozentenmodul/src/main/properties/i18n/page_layout_de_DE.properties
+++ b/dozentenmodul/src/main/properties/i18n/page_layout_de_DE.properties
@@ -59,8 +59,10 @@ ImageOvfConversion.Button.StartConversion.text=Konvertierung starten
ContainerUploadPage.title=Neues Container-Image anlegen
ContainerUploadPage.description=Bitte geben Sie Daten für das Docker-Image an
ContainerUploadPage.DockerFile.label=Docker File
+ContainerUploadPage.ImageRepo.label=Image Repository
ContainerUploadPage.GitRepository.label=Git Repository
ContainerUploadPage.GitRepository.toolTipText=Clone Addresse des Git Repositories setzen. [git@ | http://] [.git]. Derzeit keine Validierungsprüfungen!
+ContainerUploadPage.ImageRepository.ToolTipText=Geben Sie das Container-Image durch seinen Repository-Namen an (z.B. python:3.7 oder tensorflow/tensorflow)
ContainerUploadPage.CheckBox.ContainsLicenseRestricted.text=Enthält lizenzpflichtige Software
ContainerUploadPage.ContainerImageFile.label=Vorgebautes Container-Image\