adds version management

Sun, 17 May 2020 16:23:39 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 17 May 2020 16:23:39 +0200
changeset 59
c759c60507a2
parent 58
8d3047f78190
child 60
ed51c5b1f3e5

adds version management

src/main/java/de/uapcore/lightpit/dao/DataAccessObjects.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/VersionDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGDataAccessObjects.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGVersionDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/Version.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/modules/UsersModule.java file | annotate | diff | comparison | revisions
src/main/resources/localization/projects.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/projects_de.properties file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/project-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/projects.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/user-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/users.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/version-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/dynamic_fragments/versions.jsp file | annotate | diff | comparison | revisions
src/main/webapp/lightpit.css file | annotate | diff | comparison | revisions
--- a/src/main/java/de/uapcore/lightpit/dao/DataAccessObjects.java	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/DataAccessObjects.java	Sun May 17 16:23:39 2020 +0200
@@ -30,6 +30,6 @@
 
 public interface DataAccessObjects {
     UserDao getUserDao();
-
     ProjectDao getProjectDao();
+    VersionDao getVersionDao();
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/uapcore/lightpit/dao/VersionDao.java	Sun May 17 16:23:39 2020 +0200
@@ -0,0 +1,53 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Mike Becker. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+package de.uapcore.lightpit.dao;
+
+import de.uapcore.lightpit.entities.Project;
+import de.uapcore.lightpit.entities.Version;
+
+import java.sql.SQLException;
+import java.util.List;
+
+public interface VersionDao {
+
+    Version find(int id) throws SQLException;
+    void save(Version instance) throws SQLException;
+    boolean update(Version instance) throws SQLException;
+    default void saveOrUpdate(Version instance) throws SQLException {
+        if (!update(instance)) save(instance);
+    }
+
+    /**
+     * Lists all versions for the specified project.
+     * @param project the project
+     * @return a list of versions
+     * @throws SQLException on any kind of SQL error
+     */
+    List<Version> list(Project project) throws SQLException;
+}
--- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGDataAccessObjects.java	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGDataAccessObjects.java	Sun May 17 16:23:39 2020 +0200
@@ -31,6 +31,7 @@
 import de.uapcore.lightpit.dao.DataAccessObjects;
 import de.uapcore.lightpit.dao.ProjectDao;
 import de.uapcore.lightpit.dao.UserDao;
+import de.uapcore.lightpit.dao.VersionDao;
 
 import java.sql.Connection;
 import java.sql.SQLException;
@@ -39,10 +40,12 @@
 
     private final UserDao userDao;
     private final ProjectDao projectDao;
+    private final VersionDao versionDao;
 
     public PGDataAccessObjects(Connection connection) throws SQLException {
         userDao = new PGUserDao(connection);
         projectDao = new PGProjectDao(connection);
+        versionDao = new PGVersionDao(connection);
     }
 
     @Override
@@ -54,4 +57,9 @@
     public ProjectDao getProjectDao() {
         return projectDao;
     }
+
+    @Override
+    public VersionDao getVersionDao() {
+        return versionDao;
+    }
 }
--- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java	Sun May 17 16:23:39 2020 +0200
@@ -28,7 +28,6 @@
  */
 package de.uapcore.lightpit.dao.postgres;
 
-import de.uapcore.lightpit.dao.GenericDao;
 import de.uapcore.lightpit.dao.ProjectDao;
 import de.uapcore.lightpit.entities.Project;
 import de.uapcore.lightpit.entities.User;
@@ -44,7 +43,7 @@
 import static de.uapcore.lightpit.dao.Functions.setForeignKeyOrNull;
 import static de.uapcore.lightpit.dao.Functions.setStringOrNull;
 
-public final class PGProjectDao implements ProjectDao, GenericDao<Project> {
+public final class PGProjectDao implements ProjectDao {
 
     private final PreparedStatement insert, update, list, find;
 
--- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java	Sun May 17 16:23:39 2020 +0200
@@ -28,7 +28,6 @@
  */
 package de.uapcore.lightpit.dao.postgres;
 
-import de.uapcore.lightpit.dao.GenericDao;
 import de.uapcore.lightpit.dao.UserDao;
 import de.uapcore.lightpit.entities.User;
 
@@ -42,7 +41,7 @@
 
 import static de.uapcore.lightpit.dao.Functions.setStringOrNull;
 
-public final class PGUserDao implements UserDao, GenericDao<User> {
+public final class PGUserDao implements UserDao {
 
     private final PreparedStatement insert, update, list, find;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGVersionDao.java	Sun May 17 16:23:39 2020 +0200
@@ -0,0 +1,120 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Mike Becker. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+package de.uapcore.lightpit.dao.postgres;
+
+import de.uapcore.lightpit.dao.VersionDao;
+import de.uapcore.lightpit.entities.Project;
+import de.uapcore.lightpit.entities.Version;
+import de.uapcore.lightpit.entities.VersionStatus;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public final class PGVersionDao implements VersionDao {
+
+    private final PreparedStatement insert, update, list, find;
+
+    public PGVersionDao(Connection connection) throws SQLException {
+        list = connection.prepareStatement(
+                "select id, project, name, ordinal, status " +
+                        "from lpit_version " +
+                        "where project = ? " +
+                        "order by ordinal, name");
+
+        find = connection.prepareStatement(
+                "select id, project, name, ordinal, status " +
+                        "from lpit_version " +
+                        "where id = ?");
+
+        insert = connection.prepareStatement(
+                "insert into lpit_version (project, name, ordinal, status) values (?, ?, ?, ?::version_status)"
+        );
+        update = connection.prepareStatement(
+                "update lpit_version set name = ?, ordinal = ?, status = ?::version_status where id = ?"
+        );
+    }
+
+    public Version mapColumns(ResultSet result) throws SQLException {
+        final var version = new Version(result.getInt("id"), new Project(result.getInt("project")));
+        version.setName(result.getString("name"));
+        version.setOrdinal(result.getInt("ordinal"));
+        version.setStatus(VersionStatus.valueOf(result.getString("status")));
+        return version;
+    }
+
+    @Override
+    public void save(Version instance) throws SQLException {
+        Objects.requireNonNull(instance.getName());
+        Objects.requireNonNull(instance.getProject());
+        insert.setInt(1, instance.getProject().getId());
+        insert.setString(2, instance.getName());
+        insert.setInt(3, instance.getOrdinal());
+        insert.setString(4, instance.getStatus().name());
+        insert.executeUpdate();
+    }
+
+    @Override
+    public boolean update(Version instance) throws SQLException {
+        Objects.requireNonNull(instance.getName());
+        update.setString(1, instance.getName());
+        update.setInt(2, instance.getOrdinal());
+        update.setString(3, instance.getStatus().name());
+        update.setInt(4, instance.getId());
+        return update.executeUpdate() > 0;
+    }
+
+    @Override
+    public List<Version> list(Project project) throws SQLException {
+        list.setInt(1, project.getId());
+        List<Version> versions = new ArrayList<>();
+        try (var result = list.executeQuery()) {
+            while (result.next()) {
+                versions.add(mapColumns(result));
+            }
+        }
+        return versions;
+    }
+
+    @Override
+    public Version find(int id) throws SQLException {
+        find.setInt(1, id);
+        try (var result = find.executeQuery()) {
+            if (result.next()) {
+                return mapColumns(result);
+            } else {
+                return null;
+            }
+        }
+    }
+}
--- a/src/main/java/de/uapcore/lightpit/entities/Version.java	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/entities/Version.java	Sun May 17 16:23:39 2020 +0200
@@ -33,21 +33,27 @@
 public class Version implements Comparable<Version> {
 
     private final int id;
+    private final Project project;
     private String name;
     /**
      * If we do not want versions to be ordered lexicographically we may specify an order.
      */
-    private int ordinal;
-    private VersionStatus status;
+    private int ordinal = 0;
+    private VersionStatus status = VersionStatus.Future;
 
-    public Version(int id) {
+    public Version(int id, Project project) {
         this.id = id;
+        this.project = project;
     }
 
     public int getId() {
         return id;
     }
 
+    public Project getProject() {
+        return project;
+    }
+
     public String getName() {
         return name;
     }
--- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Sun May 17 16:23:39 2020 +0200
@@ -33,10 +33,17 @@
 import de.uapcore.lightpit.dao.DataAccessObjects;
 import de.uapcore.lightpit.entities.Project;
 import de.uapcore.lightpit.entities.User;
+import de.uapcore.lightpit.entities.Version;
+import de.uapcore.lightpit.entities.VersionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
 import java.sql.SQLException;
+import java.util.NoSuchElementException;
 import java.util.Optional;
 
 import static de.uapcore.lightpit.Functions.fqn;
@@ -52,9 +59,11 @@
 )
 public final class ProjectsModule extends AbstractLightPITServlet {
 
+    private static final Logger LOG = LoggerFactory.getLogger(ProjectsModule.class);
+
     public static final String SESSION_ATTR_SELECTED_PROJECT = fqn(ProjectsModule.class, "selected-project");
 
-    @RequestMapping(method = HttpMethod.GET)
+    @RequestMapping(method = HttpMethod.GET, menuKey = "menu.index")
     public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
         final var projectList = dao.getProjectDao().list();
         req.setAttribute("projects", projectList);
@@ -115,18 +124,80 @@
 
             setRedirectLocation(req, "./projects/");
             setDynamicFragment(req, Constants.DYN_FRAGMENT_COMMIT_SUCCESSFUL);
-        } catch (NullPointerException | NumberFormatException | SQLException ex) {
+            LOG.debug("Successfully updated project {}", project.getName());
+        } catch (NoSuchElementException | NumberFormatException | SQLException ex) {
             // TODO: set request attribute with error text
             req.setAttribute("project", project);
             setDynamicFragment(req, "project-form");
+            LOG.warn("Form validation failure: {}", ex.getMessage());
+            LOG.debug("Details:", ex);
         }
 
         return ResponseType.HTML;
     }
 
+    @RequestMapping(requestPath = "versions", method = HttpMethod.GET, menuKey = "menu.versions")
+    public ResponseType versions(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException {
+        final var selectedProject = (Project)req.getSession().getAttribute(SESSION_ATTR_SELECTED_PROJECT);
+        if (selectedProject == null) {
+            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return ResponseType.NONE;
+        }
 
-    @RequestMapping(requestPath = "versions", method = HttpMethod.GET, menuKey = "menu.versions")
-    public ResponseType versions(HttpServletRequest req, DataAccessObjects dao) {
+        req.setAttribute("versions", dao.getVersionDao().list(selectedProject));
+        setDynamicFragment(req, "versions");
+
+        return ResponseType.HTML;
+    }
+
+    @RequestMapping(requestPath = "versions/edit", method = HttpMethod.GET)
+    public ResponseType editVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException {
+        final var selectedProject = (Project)req.getSession().getAttribute(SESSION_ATTR_SELECTED_PROJECT);
+        if (selectedProject == null) {
+            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return ResponseType.NONE;
+        }
+
+        Optional<Integer> id = getParameter(req, Integer.class, "id");
+        if (id.isPresent()) {
+            req.setAttribute("version", Optional.ofNullable(dao.getVersionDao().find(id.get())).orElse(new Version(-1, selectedProject)));
+        } else {
+            req.setAttribute("version", new Version(-1, selectedProject));
+        }
+        req.setAttribute("versionStatusEnum", VersionStatus.values());
+
+        setDynamicFragment(req, "version-form");
+
+        return ResponseType.HTML;
+    }
+
+    @RequestMapping(requestPath = "versions/commit", method = HttpMethod.POST)
+    public ResponseType commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException {
+        final var selectedProject = (Project)req.getSession().getAttribute(SESSION_ATTR_SELECTED_PROJECT);
+        if (selectedProject == null) {
+            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return ResponseType.NONE;
+        }
+
+        Version version = new Version(-1, selectedProject);
+        try {
+            version = new Version(getParameter(req, Integer.class, "id").orElseThrow(), selectedProject);
+            version.setName(getParameter(req, String.class, "name").orElseThrow());
+            getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal);
+            version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow()));
+            dao.getVersionDao().saveOrUpdate(version);
+
+            setRedirectLocation(req, "./projects/versions/");
+            setDynamicFragment(req, Constants.DYN_FRAGMENT_COMMIT_SUCCESSFUL);
+            LOG.debug("Successfully updated version {} for project {}", version.getName(), selectedProject.getName());
+        } catch (NoSuchElementException | NumberFormatException | SQLException ex) {
+            // TODO: set request attribute with error text
+            req.setAttribute("version", version);
+            req.setAttribute("versionStatusEnum", VersionStatus.values());
+            setDynamicFragment(req, "version-form");
+            LOG.warn("Form validation failure: {}", ex.getMessage());
+            LOG.debug("Details:", ex);
+        }
 
         return ResponseType.HTML;
     }
--- a/src/main/java/de/uapcore/lightpit/modules/UsersModule.java	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/modules/UsersModule.java	Sun May 17 16:23:39 2020 +0200
@@ -32,10 +32,13 @@
 import de.uapcore.lightpit.*;
 import de.uapcore.lightpit.dao.DataAccessObjects;
 import de.uapcore.lightpit.entities.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServletRequest;
 import java.sql.SQLException;
+import java.util.NoSuchElementException;
 import java.util.Optional;
 
 @LightPITModule(
@@ -49,6 +52,8 @@
 )
 public final class UsersModule extends AbstractLightPITServlet {
 
+    private static final Logger LOG = LoggerFactory.getLogger(UsersModule.class);
+
     @RequestMapping(method = HttpMethod.GET)
     public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
         final var userDao = dao.getUserDao();
@@ -90,10 +95,14 @@
 
             setRedirectLocation(req, "./teams/");
             setDynamicFragment(req, Constants.DYN_FRAGMENT_COMMIT_SUCCESSFUL);
-        } catch (NullPointerException | NumberFormatException | SQLException ex) {
+
+            LOG.debug("Successfully updated user {}", user.getUsername());
+        } catch (NoSuchElementException | NumberFormatException | SQLException ex) {
             // TODO: set request attribute with error text
             req.setAttribute("user", user);
             setDynamicFragment(req, "user-form");
+            LOG.warn("Form validation failure: {}", ex.getMessage());
+            LOG.debug("Details:", ex);
         }
 
         return ResponseType.HTML;
--- a/src/main/resources/localization/projects.properties	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/resources/localization/projects.properties	Sun May 17 16:23:39 2020 +0200
@@ -23,9 +23,11 @@
 
 menuLabel=Projects
 
+menu.index=Index
 menu.versions=Versions
 
 button.create=New Project
+button.version.create=New Version
 
 no-projects=Welcome to LightPIT. Start off by creating a new project!
 
@@ -34,4 +36,15 @@
 thead.repoUrl=Repository
 thead.owner=Project Lead
 
+thead.version.name=Version
+thead.version.status=Status
+thead.version.ordinal=Custom Ordering
+tooltip.ordinal=Use to override lexicographic ordering.
+
 placeholder.null-owner=Unassigned
+
+version.status.Future=Future
+version.status.Unreleased=Unreleased
+version.status.Released=Released
+version.status.LTS=LTS
+version.status.Deprecated=Deprecated
\ No newline at end of file
--- a/src/main/resources/localization/projects_de.properties	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/resources/localization/projects_de.properties	Sun May 17 16:23:39 2020 +0200
@@ -23,9 +23,11 @@
 
 menuLabel=Projekte
 
+menu.index=Index
 menu.versions=Versionen
 
 button.create=Neues Projekt
+button.version.create=Neue Version
 
 no-projects=Wilkommen bei LightPIT. Beginnen Sie mit der Erstellung eines Projektes!
 
@@ -34,4 +36,15 @@
 thead.repoUrl=Repository
 thead.owner=Projektleitung
 
+thead.version.name=Version
+thead.version.status=Status
+thead.version.ordinal=Sequenznummer
+tooltip.ordinal=\u00dcbersteuert die lexikographische Sortierung.
+
 placeholder.null-owner=Nicht Zugewiesen
+
+version.status.Future=Geplant
+version.status.Unreleased=Unver\u00f6ffentlicht
+version.status.Released=Ver\u00f6ffentlicht
+version.status.LTS=Langzeitsupport
+version.status.Deprecated=Veraltet
--- a/src/main/webapp/WEB-INF/dynamic_fragments/project-form.jsp	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/webapp/WEB-INF/dynamic_fragments/project-form.jsp	Sun May 17 16:23:39 2020 +0200
@@ -69,7 +69,7 @@
         <tr>
             <td colspan="2">
                 <input type="hidden" name="id" value="${project.id}" />
-                <a href="./${moduleInfo.modulePath}" class="button"><fmt:message bundle="${lightpit_bundle}" key="button.cancel"/></a>
+                <a href="./${moduleInfo.modulePath}/" class="button"><fmt:message bundle="${lightpit_bundle}" key="button.cancel"/></a>
                 <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay" /></button>
             </td>
         </tr>
--- a/src/main/webapp/WEB-INF/dynamic_fragments/projects.jsp	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/webapp/WEB-INF/dynamic_fragments/projects.jsp	Sun May 17 16:23:39 2020 +0200
@@ -46,7 +46,7 @@
 </div>
 
 <c:if test="${not empty projects}">
-<table id="project-list" class="datatable medskip">
+<table id="project-list" class="datatable medskip fullwidth">
     <colgroup>
         <col>
         <col style="width: 10%">
@@ -67,7 +67,7 @@
     <c:forEach var="project" items="${projects}">
         <tr class="nowrap" <c:if test="${project eq selectedProject}">data-selected</c:if> >
             <td style="width: 2em;"><a href="./${moduleInfo.modulePath}/edit?id=${project.id}">&#x270e;</a></td>
-            <td><a href="./${moduleInfo.modulePath}?select=${project.id}"><c:out value="${project.name}"/></a></td>
+            <td><a href="./${moduleInfo.modulePath}/?select=${project.id}"><c:out value="${project.name}"/></a></td>
             <td><c:out value="${project.description}"/></td>
             <td>
                 <c:if test="${not empty project.repoUrl}">
--- a/src/main/webapp/WEB-INF/dynamic_fragments/user-form.jsp	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/webapp/WEB-INF/dynamic_fragments/user-form.jsp	Sun May 17 16:23:39 2020 +0200
@@ -61,7 +61,7 @@
         <tr>
             <td colspan="2">
                 <input type="hidden" name="userid" value="${user.id}" />
-                <a href="./${moduleInfo.modulePath}" class="button"><fmt:message bundle="${lightpit_bundle}" key="button.cancel"/></a>
+                <a href="./${moduleInfo.modulePath}/" class="button"><fmt:message bundle="${lightpit_bundle}" key="button.cancel"/></a>
                 <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay" /></button>
             </td>
         </tr>
--- a/src/main/webapp/WEB-INF/dynamic_fragments/users.jsp	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/webapp/WEB-INF/dynamic_fragments/users.jsp	Sun May 17 16:23:39 2020 +0200
@@ -44,7 +44,7 @@
 </div>
 
 <c:if test="${not empty users}">
-    <table class="datatable medskip" style="width: auto">
+    <table class="datatable medskip">
         <thead>
         <tr>
             <th></th>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/WEB-INF/dynamic_fragments/version-form.jsp	Sun May 17 16:23:39 2020 +0200
@@ -0,0 +1,76 @@
+<%--
+DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+
+Copyright 2018 Mike Becker. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+--%>
+<%@page pageEncoding="UTF-8" %>
+<%@page import="de.uapcore.lightpit.Constants" %>
+<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+
+<c:set scope="page" var="moduleInfo" value="${requestScope[Constants.REQ_ATTR_MODULE_INFO]}"/>
+<c:set scope="page" var="selectedProject" value="${sessionScope[ProjectsModule.SESSION_ATTR_SELECTED_PROJECT]}"/>
+
+<jsp:useBean id="version" type="de.uapcore.lightpit.entities.Version" scope="request"/>
+<jsp:useBean id="versionStatusEnum" type="de.uapcore.lightpit.entities.VersionStatus[]" scope="request" />
+
+<form action="./${moduleInfo.modulePath}/versions/commit" method="post">
+    <table class="formtable" style="width: 35ch">
+        <colgroup>
+            <col>
+            <col style="width: 100%">
+        </colgroup>
+        <tbody>
+        <tr>
+            <th><fmt:message key="thead.version.name"/></th>
+            <td><input name="name" type="text" maxlength="20" required value="${version.name}" /> </td>
+        </tr>
+        <tr>
+            <th><fmt:message key="thead.version.status"/></th>
+            <td>
+                <select name="status" required>
+                    <c:forEach var="elem" items="${versionStatusEnum}">
+                        <option value="${elem}"><fmt:message key="version.status.${elem}" /> </option>
+                    </c:forEach>
+                </select>
+            </td>
+        </tr>
+        <tr title="<fmt:message key="tooltip.ordinal" />">
+            <th><fmt:message key="thead.version.ordinal"/></th>
+            <td>
+                <input name="ordinal" type="number" min="0" value="${version.ordinal}" />
+            </td>
+        </tr>
+        </tbody>
+        <tfoot>
+        <tr>
+            <td colspan="2">
+                <input type="hidden" name="id" value="${version.id}" />
+                <a href="./${moduleInfo.modulePath}/versions/" class="button"><fmt:message bundle="${lightpit_bundle}" key="button.cancel"/></a>
+                <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay" /></button>
+            </td>
+        </tr>
+        </tfoot>
+    </table>
+</form>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/WEB-INF/dynamic_fragments/versions.jsp	Sun May 17 16:23:39 2020 +0200
@@ -0,0 +1,68 @@
+<%--
+DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+
+Copyright 2018 Mike Becker. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+--%>
+<%@page pageEncoding="UTF-8" %>
+<%@page import="de.uapcore.lightpit.Constants" %>
+<%@page import="de.uapcore.lightpit.modules.ProjectsModule" %>
+<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+
+<c:set scope="page" var="moduleInfo" value="${requestScope[Constants.REQ_ATTR_MODULE_INFO]}"/>
+<c:set scope="page" var="selectedProject" value="${sessionScope[ProjectsModule.SESSION_ATTR_SELECTED_PROJECT]}"/>
+
+<jsp:useBean id="versions" type="java.util.List<de.uapcore.lightpit.entities.Version>" scope="request"/>
+
+<c:if test="${empty selectedProject}">
+    <div class="info-box">
+    <fmt:message key="no-projects" />
+    </div>
+</c:if>
+<c:if test="${not empty selectedProject}">
+<div id="tool-area">
+    <a href="./${moduleInfo.modulePath}/versions/edit" class="button"><fmt:message key="button.version.create" /></a>
+</div>
+
+<c:if test="${not empty versions}">
+<table id="project-list" class="datatable medskip">
+    <thead>
+    <tr>
+        <th></th>
+        <th><fmt:message key="thead.version.name"/></th>
+        <th><fmt:message key="thead.version.status"/></th>
+    </tr>
+    </thead>
+    <tbody>
+    <c:forEach var="version" items="${versions}">
+        <tr class="nowrap" >
+            <td style="width: 2em;"><a href="./${moduleInfo.modulePath}/versions/edit?id=${version.id}">&#x270e;</a></td>
+            <td><c:out value="${version.name}"/></td>
+            <td><fmt:message key="version.status.${version.status}" /></td>
+        </tr>
+    </c:forEach>
+    </tbody>
+</table>
+</c:if>
+</c:if>
--- a/src/main/webapp/lightpit.css	Sun May 17 16:00:13 2020 +0200
+++ b/src/main/webapp/lightpit.css	Sun May 17 16:23:39 2020 +0200
@@ -117,7 +117,7 @@
 }
 
 table.datatable {
-    width: 100%;
+    width: auto;
     border-style: solid;
     border-width: 1pt;
     border-color: black;

mercurial