delegates;
+
+ public MultiValidator(IValidator... delegates) {
+ this.delegates = Arrays.asList(delegates);
+ }
+
+ @Override
+ public void validate(Document document, Page currentValues, IValue origValue, IValue updatedValue) throws ValidationException {
+ for (IValidator delegate : delegates) {
+ delegate.validate(document, currentValues, origValue, updatedValue);
+ }
+ }
+}
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/NoopValidator.java b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/NoopValidator.java
new file mode 100644
index 00000000..3672f865
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/NoopValidator.java
@@ -0,0 +1,18 @@
+package org.cloudcoder.app.wizard.model.validators;
+
+import org.cloudcoder.app.wizard.model.Document;
+import org.cloudcoder.app.wizard.model.IValue;
+import org.cloudcoder.app.wizard.model.Page;
+
+public class NoopValidator implements IValidator {
+ public static final NoopValidator INSTANCE = new NoopValidator();
+
+ private NoopValidator() {
+
+ }
+
+ @Override
+ public void validate(Document document, Page currentValues, IValue origValue, IValue updatedValue) throws ValidationException {
+ // Do nothing
+ }
+}
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/StringValueEndsInSuffixValidator.java b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/StringValueEndsInSuffixValidator.java
new file mode 100644
index 00000000..c653012d
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/StringValueEndsInSuffixValidator.java
@@ -0,0 +1,39 @@
+package org.cloudcoder.app.wizard.model.validators;
+
+import org.cloudcoder.app.wizard.model.Document;
+import org.cloudcoder.app.wizard.model.IValue;
+import org.cloudcoder.app.wizard.model.Page;
+
+public class StringValueEndsInSuffixValidator implements IValidator {
+ private String suffix;
+ private boolean ignoreCase;
+
+ /**
+ * Constructor.
+ *
+ * @param suffix the suffix to test
+ * @param ignoreCase true if case should be ignored, false otherwise
+ */
+ public StringValueEndsInSuffixValidator(String suffix, boolean ignoreCase) {
+ this.suffix = suffix;
+ this.ignoreCase = ignoreCase;
+ }
+
+ @Override
+ public void validate(Document document, Page currentValues, IValue origValue, IValue updatedValue) throws ValidationException {
+ String value = updatedValue.getString();
+ String testSuffix = suffix;
+
+ if (ignoreCase) {
+ value = value.toLowerCase();
+ testSuffix = testSuffix.toLowerCase();
+ }
+
+ if (!value.endsWith(testSuffix)) {
+ throw new ValidationException(
+ origValue,
+ updatedValue,
+ "Value " + updatedValue.getString() + " does not end with " + suffix);
+ }
+ }
+}
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/StringValueEqualValidator.java b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/StringValueEqualValidator.java
new file mode 100644
index 00000000..4e58180c
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/StringValueEqualValidator.java
@@ -0,0 +1,28 @@
+package org.cloudcoder.app.wizard.model.validators;
+
+import org.cloudcoder.app.wizard.model.Document;
+import org.cloudcoder.app.wizard.model.IValue;
+import org.cloudcoder.app.wizard.model.Page;
+import org.cloudcoder.app.wizard.model.StringValue;
+
+/**
+ * Validate that a {@link StringValue} is the same
+ * as another {@link StringValue} on the same {@link Page}.
+ * Useful for password confirmation fields.
+ */
+public class StringValueEqualValidator implements IValidator {
+ private String otherStringValueName;
+
+ public StringValueEqualValidator(String otherStringValueName) {
+ this.otherStringValueName = otherStringValueName;
+ }
+
+ @Override
+ public void validate(Document document, Page currentValues, IValue origValue, IValue updatedValue) throws ValidationException {
+ IValue other = currentValues.getValue(otherStringValueName);
+ if (!other.getString().equals(updatedValue.getString())) {
+ String msg = "Value does not match value of " + other.getLabel() + " field";
+ throw new ValidationException(origValue, updatedValue, msg);
+ }
+ }
+}
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/StringValueNonemptyValidator.java b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/StringValueNonemptyValidator.java
new file mode 100644
index 00000000..5b4c1bb7
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/StringValueNonemptyValidator.java
@@ -0,0 +1,20 @@
+package org.cloudcoder.app.wizard.model.validators;
+
+import org.cloudcoder.app.wizard.model.Document;
+import org.cloudcoder.app.wizard.model.IValue;
+import org.cloudcoder.app.wizard.model.Page;
+
+public class StringValueNonemptyValidator implements IValidator {
+ public static final StringValueNonemptyValidator INSTANCE = new StringValueNonemptyValidator();
+
+ private StringValueNonemptyValidator() {
+
+ }
+
+ @Override
+ public void validate(Document document, Page currentValues, IValue origValue, IValue updatedValue) throws ValidationException {
+ if (updatedValue.getString().trim().equals("")) {
+ throw new ValidationException(origValue, updatedValue, "Value must be nonempty");
+ }
+ }
+}
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/ValidationException.java b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/ValidationException.java
new file mode 100644
index 00000000..4e426655
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/model/validators/ValidationException.java
@@ -0,0 +1,24 @@
+package org.cloudcoder.app.wizard.model.validators;
+
+import org.cloudcoder.app.wizard.model.IValue;
+
+public class ValidationException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ private final IValue origValue;
+ private final IValue invalidValue;
+
+ public ValidationException(IValue origValue, IValue invalidValue, String msg) {
+ super(msg);
+ this.origValue = origValue;
+ this.invalidValue = invalidValue;
+ }
+
+ public IValue getOrigValue() {
+ return origValue;
+ }
+
+ public IValue getInvalidValue() {
+ return invalidValue;
+ }
+}
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/aws.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/aws.msg.html
new file mode 100644
index 00000000..2512f37b
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/aws.msg.html
@@ -0,0 +1,8 @@
+Please enter your AWS credentials.
+Your AWS credentials will be used to provision the
+server on which the CloudCoder webapp will run,
+and will also be used to dynamically provision
+servers to build and test submissions.
+The credentials you provide can (and should) be
+an IAM user. The user should have permission
+to create EC2 instances and VPCs.
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/awsInstanceType.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/awsInstanceType.msg.html
new file mode 100644
index 00000000..c8892d8e
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/awsInstanceType.msg.html
@@ -0,0 +1,32 @@
+
+Choose an EC2 instance type
+for the CloudCoder webapp/database server.
+
+
+
+A t2.micro instance, which is eligible for the EC2 free
+tier, is known to work well for CloudCoder if you have 60 or fewer
+concurrent users. It is likely that a t2.nano
+instance will work reasonably well if you have a relatively
+small number of concurrent users (perhaps 30 or so). If you are
+expecting a large number of concurrent users (100 or more)
+you should consider using one of the larger instance types.
+
+
+
+Note that if you will have a significant number of concurrent
+users, you will want to run some number of builders
+separately (not on the webapp server.) See [TODO: link to
+documentation] for details on setting up builders.
+
+
+
\ No newline at end of file
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/awsKeypair.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/awsKeypair.msg.html
new file mode 100644
index 00000000..284c2d3b
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/awsKeypair.msg.html
@@ -0,0 +1,21 @@
+
+You will need to choose a keypair to use to control access to
+the server(s) to be provisioned for CloudCoder.
+You can use an existing keypair associated with your AWS
+account, or the installation wizard can create a new
+keypair for you.
+
+
+If you want to use an existing keypair, choose the .pem
+file containing the keypair. Important:
+the filename must match the name of the keypair in your
+AWS account.
+
+
+If you do not use an existing keypair, the installation wizard
+will create a new one for you, which will be called
+cloudcoder-keypair. Once the keypair is generated,
+you will have the opportunity to save it to a file. You
+will need the keypair in order to connect to your servers
+via ssh, so be sure to save it.
+
\ No newline at end of file
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/awsRegion.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/awsRegion.msg.html
new file mode 100644
index 00000000..45b3fc4f
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/awsRegion.msg.html
@@ -0,0 +1,9 @@
+Please choose an
+AWS EC2 Region.
+
+
+
+Choosing a region close to you geographically isn't a bad idea.
+Also note that regions vary in cost, with US regions
+generally being the least expensive.
+
\ No newline at end of file
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/bootstrap.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/bootstrap.msg.html
new file mode 100644
index 00000000..10332f79
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/bootstrap.msg.html
@@ -0,0 +1,7 @@
+
+Bootstrapping CloudCoder by downloading, configuring, and installing
+the CloudCoder software on the webapp instance.
+
+
+This can take several minutes.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/ccAcct.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/ccAcct.msg.html
new file mode 100644
index 00000000..6eebf70f
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/ccAcct.msg.html
@@ -0,0 +1,13 @@
+
+Please enter information for your CloudCoder user account.
+This account will be the administrator account for the CloudCoder
+installation, meaning that you will be able to create courses,
+register instructors and students, and perform other administrative
+tasks using this account.
+
+
+
+Note that the email address will be
+used as the contact information for the Let's Encrypt
+SSL certificate, so make sure you use a valid email address.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/dns.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/dns.msg.html
new file mode 100644
index 00000000..b88b8dee
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/dns.msg.html
@@ -0,0 +1,18 @@
+
+Enter the hostname that you will use for your CloudCoder instance.
+
+
+
+If you will be using a dynamic DNS provider such as
+Duck DNS, make sure that
+the hostname matches the subdomain(s) offered by your
+dynamic DNS provider. For example, a Duck DNS hostname
+should be something like myserver.duckdns.org.
+
+
+
+If you will not be using a dynamic DNS provider, then you will need to
+configure an A record for the hostname you choose.
+The wizard will let you know what IP address to use once
+the webapp server has been provisioned.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/duckDns.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/duckDns.msg.html
new file mode 100644
index 00000000..d925fb25
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/duckDns.msg.html
@@ -0,0 +1,6 @@
+
+This page allows you to specify your Duck DNS authorization
+token. This will allow the installation wizard to
+use Duck DNS to associate the CloudCoder webapp's public
+IP address with a hostname once the webapp is provisioned.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/dynDns.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/dynDns.msg.html
new file mode 100644
index 00000000..07b4ff71
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/dynDns.msg.html
@@ -0,0 +1,20 @@
+
+This page allows you to choose a dynamic DNS provider.
+This is the easiest way to configure a DNS hostname for your
+CloudCoder instance. We highly recommend using dynamic
+DNS if you are just trying out CloudCoder. It's easy and
+free!
+
+
+
+If you choose to use a dynamic DNS provider,
+you should create an account with that provider (if you haven't
+already) and create a hostname for your CloudCoder instance.
+You'll also need your account credentials.
+
+
+
+If you don't use a dynamic DNS provider, you'll need to
+create a DNS entry for the CloudCoder webapp once it is
+provisioned.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/dynDnsAcct.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/dynDnsAcct.msg.html
new file mode 100644
index 00000000..d1ff8262
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/dynDnsAcct.msg.html
@@ -0,0 +1,7 @@
+
+Enter your username and password for your Dynamic DNS provider.
+This step is required if you are using one of the following providers:
+
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/error.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/error.msg.html
new file mode 100644
index 00000000..894ffe39
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/error.msg.html
@@ -0,0 +1,16 @@
+
+An error occurred running the CloudCoder installation wizard.
+
+
+
+Please copy all of the text from the "Output log" tab into
+a text editor and save it to a file. Then, email
+dhovemey@ycp.edu
+to report the error.
+
+
+
+Please accept our sincere apologies: we would like to make
+installing and running CloudCoder as easy as possible,
+and we will do what we can to help you get up and running.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/errorSsl.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/errorSsl.msg.html
new file mode 100644
index 00000000..4d351b28
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/errorSsl.msg.html
@@ -0,0 +1,5 @@
+
+An error occurred installing the SSL certificate.
+See error log for details.
+Sorry that this is a very lame message.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/finished.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/finished.msg.html
new file mode 100644
index 00000000..9a158ce8
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/finished.msg.html
@@ -0,0 +1,3 @@
+
+Dummy report — you are not supposed to see this.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/finished.reporttemplate.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/finished.reporttemplate.html
new file mode 100644
index 00000000..34d74def
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/finished.reporttemplate.html
@@ -0,0 +1,39 @@
+
+Congratulations, CloudCoder has been installed successfully!
+Please read this information carefully.
+
+
+
+Details about your installation:
+
+
+ | Detail | Value |
+ | Hostname | ${dns.hostname} |
+ | URL | https://${dns.hostname}/cloudcoder |
+ | Public IP address | ${cloudInfo.webappPublicIp} |
+ | DNS hostname configured | ${db.dnsHostnameConfigured} |
+ | SSL certificate installed | ${db.sslCertInstalled} |
+ | Used existing keypair | ${awsKeypair.useExisting} |
+
+
+The ${cloudInfo.dataDir} directory contains files
+with information about the installation.
+Within this directory:
+
+
+- The
report.html file is this installation report.
+ - The
ccinstall.properties
+file has all of the configuration information you entered.
+- The
cloudcoder-keypair.pem file is the keypair
+controlling ssh access to the webapp instance. If this keypair was
+generated (i.e., you didn't use an existing keypair), make sure
+you save the keypair.
+- The
installwizard-yyyy-MM-dd-hh-mm.log
+file is the complete output log of the installation. Note that it will
+likely contain sensitive information.
+
+
+
+Please email dhovemey@ycp.edu
+if you have any questions about CloudCoder.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/finishedSsl.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/finishedSsl.msg.html
new file mode 100644
index 00000000..cc9725c2
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/finishedSsl.msg.html
@@ -0,0 +1,3 @@
+
+Dummy SSL installation report — you are not supposed to see this.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/finishedSsl.reporttemplate.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/finishedSsl.reporttemplate.html
new file mode 100644
index 00000000..a7839f26
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/finishedSsl.reporttemplate.html
@@ -0,0 +1,11 @@
+
+Let's Encrypt SSL certificate for ${dns.hostname} issued and installed successfully!
+
+
+
+You should now be able to connect to CloudCoder using the following link:
+
+
+
+https://${dns.hostname}/cloudcoder
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/instDetails.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/instDetails.msg.html
new file mode 100644
index 00000000..77b7a54f
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/instDetails.msg.html
@@ -0,0 +1,3 @@
+
+Please enter details about your CloudCoder instance.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/installSsl.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/installSsl.msg.html
new file mode 100644
index 00000000..c5633a08
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/installSsl.msg.html
@@ -0,0 +1,4 @@
+
+Issuing and installing Let's Encrypt SSL certificate.
+This can take several minutes.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/intBuild.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/intBuild.msg.html
new file mode 100644
index 00000000..a2d89b31
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/intBuild.msg.html
@@ -0,0 +1,19 @@
+
+Check the Enable integrated builder? checkbox below if you want to enable an
+integrated builder.
+
+
+
+The builder is the component of CloudCoder that compiles and
+tests submissions. Enabling the integrated builder means that
+a builder will run on the webapp server. This is very
+useful if you are just trying out CloudCoder, or if this
+CloudCoder instance will have only very light use.
+
+
+
+For production use, it is better to run builders on
+dedicated computers. Builders are CPU and memory intensive,
+and running a builder on the webapp server can degrade
+the performance of the webapp.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/mysqlAcct.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/mysqlAcct.msg.html
new file mode 100644
index 00000000..36eac828
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/mysqlAcct.msg.html
@@ -0,0 +1,13 @@
+
+Choose passwords for the MySQL root and
+cloudcoder accounts. The root account
+is the MySQL superuser, and the cloudcoder
+user is the account the CloudCoder webapp uses to access
+the database.
+
+
+The MySQL database is not exposed to external clients,
+so it's not terribly important to use strong passwords here.
+If you would like to keep the default values (which is
+abc123 for both passwords), that should be fine.
+
\ No newline at end of file
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/prevCcinstall.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/prevCcinstall.msg.html
new file mode 100644
index 00000000..6e6b6844
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/prevCcinstall.msg.html
@@ -0,0 +1,22 @@
+
+It looks like you have run the CloudCoder installation wizard
+previously. Check the box labeled Load previous values
+if you would like to load the previous values.
+
+
+
+This is a very good idea if
+
+
+- You did a dry run previously, and now you are ready to
+install for real, or
+
+- You are re-running the installation
+wizard to issue and install a Let's Encrypt SSL certificate
+
+
+
+Loading the previous configuration values means you won't
+have to re-enter them. You will still be able to make
+changes if you desire.
+
\ No newline at end of file
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/provisioningStep.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/provisioningStep.msg.html
new file mode 100644
index 00000000..06608b5f
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/provisioningStep.msg.html
@@ -0,0 +1,10 @@
+
+Provisioning the server and network resources for the
+CloudCoder webapp. On AWS, this includes creating a
+VPC (virtual private cloud), an EC2 virtual machine
+to run the webapp, and an elastic IP address as the webapp's
+public address.
+
+
+This can take several minutes.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/ready.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/ready.msg.html
new file mode 100644
index 00000000..90d7796b
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/ready.msg.html
@@ -0,0 +1,7 @@
+
+At this point we are ready to install CloudCoder!
+
+
+
+Click Install to proceed with the installation.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/selectTask.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/selectTask.msg.html
new file mode 100644
index 00000000..2c9fc3dd
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/selectTask.msg.html
@@ -0,0 +1,20 @@
+
+Welcome to the CloudCoder installation wizard!
+Please choose a task.
+
+
+Choose Install Cloudcoder if you want to install
+Cloudcoder.
+
+
+Choose Issue and install SSL certificate if you have already
+installed CloudCoder, but the installer did not issue and install
+an SSL certificate because either
+
+
+- You chose to manually configure a DNS hostname rather than
+using dynamic DNS to configure one automatically, or
+- Configuring a dynamic DNS hostname failed during the original
+installation, and since then you have manually configured a DNS
+hostname
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/welcome.msg.html b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/welcome.msg.html
new file mode 100644
index 00000000..2f0e84ca
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/res/welcome.msg.html
@@ -0,0 +1,23 @@
+
+Let's install CloudCoder!
+You will need the following resources:
+
+
+
+- An Amazon Web Services account
+- Either the ability to create a DNS A record for the
+server hosting the CloudCoder webapp, or an
+account with one of the supported dynamic DNS providers (which are
+Duck DNS
+and No-IP.)
+
+
+
+
+You can check the Do a dry run checkbox if you just want
+to see how the installation wizard works, without actually
+installing CloudCoder. We highly recommend doing this
+your first time through. Your settings will be saved
+automatically so that you can reload them when you are ready
+to install for real.
+
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/ui/BooleanValueField.java b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/ui/BooleanValueField.java
new file mode 100644
index 00000000..1efa6307
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/ui/BooleanValueField.java
@@ -0,0 +1,60 @@
+package org.cloudcoder.app.wizard.ui;
+
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JCheckBox;
+
+import org.cloudcoder.app.wizard.model.BooleanValue;
+import org.cloudcoder.app.wizard.model.IValue;
+
+public class BooleanValueField extends LabeledField implements IPageField, UIConstants {
+ private static final long serialVersionUID = 1L;
+ private JCheckBox checkBox;
+
+ public BooleanValueField() {
+ this.checkBox = new JCheckBox();
+ checkBox.setPreferredSize(new Dimension(FIELD_COMPONENT_WIDTH, FIELD_COMPONENT_HEIGHT));
+ add(checkBox);
+ checkBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ onChange();
+ }
+ });
+ }
+
+ @Override
+ public void setValue(BooleanValue value) {
+ super.setValue(value);
+ checkBox.setSelected(value.getBoolean());
+ }
+
+ @Override
+ public void markValid() {
+ // Nothing to do
+ }
+
+ @Override
+ public void markInvalid() {
+ // Nothing to do, this field can't be invalid
+ }
+
+ @Override
+ public IValue getCurrentValue() {
+ BooleanValue current = getValue().clone();
+ current.setBoolean(checkBox.isSelected());
+ return current;
+ }
+
+ @Override
+ public void setSelectiveEnablement(boolean enabled) {
+ checkBox.setEnabled(enabled);
+ }
+
+ @Override
+ public void updateValue(IValue value) {
+ setValue((BooleanValue)value);
+ }
+}
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/ui/ConfigPanel.java b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/ui/ConfigPanel.java
new file mode 100644
index 00000000..47689847
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/ui/ConfigPanel.java
@@ -0,0 +1,122 @@
+package org.cloudcoder.app.wizard.ui;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.BoxLayout;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import org.cloudcoder.app.wizard.model.IValue;
+import org.cloudcoder.app.wizard.model.Page;
+
+public class ConfigPanel extends JPanel implements IWizardPagePanel, UIConstants {
+ private static final long serialVersionUID = 1L;
+
+ private JPanel content;
+ private Page page;
+ private List fields;
+ private Runnable changeCallback;
+
+ public ConfigPanel() {
+ setLayout(new BorderLayout());
+
+ this.content = new JPanel();
+ BoxLayout boxLayout = new BoxLayout(content, BoxLayout.Y_AXIS);
+ content.setLayout(boxLayout);
+
+ fields = new ArrayList();
+
+ // Callback to execute when UI values change -
+ // used to update selective enablement.
+ this.changeCallback = new Runnable() {
+ @Override
+ public void run() {
+ updateSelectiveEnablement();
+ }
+ };
+
+ JScrollPane scrollPane = new JScrollPane(content);
+ add(scrollPane, BorderLayout.CENTER);
+ }
+
+ @Override
+ public Type getType() {
+ return Type.CONFIG;
+ }
+
+ @Override
+ public ConfigPanel asConfigPanel() {
+ return this;
+ }
+
+ @Override
+ public InstallPanel asInstallPanel() {
+ throw new IllegalStateException("Not an InstallPanel");
+ }
+
+ @Override
+ public Component asComponent() {
+ return this;
+ }
+
+ /**
+ * Get a version of the {@link Page} with updated values
+ * from the UI fields.
+ *
+ * @return {@link Page} containing current UI field values
+ */
+ public Page getCurrentValues() {
+ Page current = page.clone();
+ for (int i = 0; i < page.getNumValues(); i++) {
+ current.set(i, fields.get(i).getCurrentValue());
+ }
+ return current;
+ }
+
+ @Override
+ public void setPage(Page page) {
+ this.page = page;
+ for (IValue v : page) {
+ IPageField field = PageFieldFactory.createForValue(v);
+ fields.add(field);
+ Component component = field.asComponent();
+ component.setPreferredSize(new Dimension(SINGLE_COMPONENT_FIELD_WIDTH, field.getFieldHeight()));
+ component.setMaximumSize(new Dimension(SINGLE_COMPONENT_FIELD_WIDTH, field.getFieldHeight()));
+ content.add(component);
+ field.setChangeCallback(this.changeCallback);
+ }
+ updateSelectiveEnablement();
+ }
+
+ @Override
+ public void resyncFields(Page page) {
+ for (int i = 0; i < page.getNumValues(); i++) {
+ fields.get(i).updateValue(page.get(i));
+ }
+ }
+
+ public IPageField getField(int index) {
+ return fields.get(index);
+ }
+
+ public void markAllValid() {
+ for (IPageField field : fields) {
+ field.markValid();
+ }
+ }
+
+ private void updateSelectiveEnablement() {
+ if (page == null) {
+ return;
+ }
+ Page current = getCurrentValues();
+ // Do selective enablement based on current values
+ for (int i = 0; i < current.getNumValues(); i++) {
+ fields.get(i).setSelectiveEnablement(current.isEnabled(current.get(i).getName()));
+ }
+ }
+}
diff --git a/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/ui/EnumValueField.java b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/ui/EnumValueField.java
new file mode 100644
index 00000000..555ce1b5
--- /dev/null
+++ b/CloudCoderInstallationWizard/src/org/cloudcoder/app/wizard/ui/EnumValueField.java
@@ -0,0 +1,82 @@
+package org.cloudcoder.app.wizard.ui;
+
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JComboBox;
+
+import org.cloudcoder.app.wizard.model.EnumValue;
+import org.cloudcoder.app.wizard.model.IValue;
+
+public class EnumValueField> extends LabeledField> implements IPageField, UIConstants {
+ private static final long serialVersionUID = 1L;
+
+ private JComboBox