diff --git a/gitop.web/pom.xml b/gitop.web/pom.xml index 72d8296811..bef13cad31 100644 --- a/gitop.web/pom.xml +++ b/gitop.web/pom.xml @@ -66,6 +66,12 @@ commons-fileupload 1.3 + + commons-validator + commons-validator + 1.4.0 + + org.imgscalr diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/GitopWebApp.java b/gitop.web/src/main/java/com/pmease/gitop/web/GitopWebApp.java index 009be12150..41bba88a55 100644 --- a/gitop.web/src/main/java/com/pmease/gitop/web/GitopWebApp.java +++ b/gitop.web/src/main/java/com/pmease/gitop/web/GitopWebApp.java @@ -37,8 +37,8 @@ import com.pmease.gitop.core.manager.UserManager; import com.pmease.gitop.web.assets.AssetLocator; import com.pmease.gitop.web.component.avatar.AvatarImageResource; import com.pmease.gitop.web.component.avatar.AvatarImageResourceReference; -import com.pmease.gitop.web.page.account.AccountHomePage; import com.pmease.gitop.web.page.account.RegisterPage; +import com.pmease.gitop.web.page.account.home.AccountHomePage; import com.pmease.gitop.web.page.account.setting.password.AccountPasswordPage; import com.pmease.gitop.web.page.account.setting.permission.AccountPermissionPage; import com.pmease.gitop.web.page.account.setting.permission.AddTeamPage; @@ -47,6 +47,7 @@ import com.pmease.gitop.web.page.account.setting.profile.AccountProfilePage; import com.pmease.gitop.web.page.account.setting.repos.AccountReposPage; import com.pmease.gitop.web.page.home.HomePage; import com.pmease.gitop.web.page.init.ServerInitPage; +import com.pmease.gitop.web.page.project.CreateProjectPage; import com.pmease.gitop.web.page.project.ProjectHomePage; import com.pmease.gitop.web.page.test.TestPage; import com.pmease.gitop.web.page.test.TestPage2; @@ -181,6 +182,9 @@ public class GitopWebApp extends AbstractWicketConfig { mountPage("settings/repos", AccountReposPage.class); mountPage("teams/add", AddTeamPage.class); mountPage("teams/edit/${teamId}", EditTeamPage.class); + + // project related + mountPage("new", CreateProjectPage.class); mountPage("/test", TestPage.class); mountPage("test2", TestPage2.class); diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/assets/css/base.css b/gitop.web/src/main/java/com/pmease/gitop/web/assets/css/base.css index 8079cabad0..d44cbdcc4f 100644 --- a/gitop.web/src/main/java/com/pmease/gitop/web/assets/css/base.css +++ b/gitop.web/src/main/java/com/pmease/gitop/web/assets/css/base.css @@ -194,6 +194,47 @@ a > .icon-null, .icon-null { display: inline-block; width: 10px; } background: #2ba6cb; } +.btn-gray { + color: #333333; + background-color: #eaeaea; + border-color: #cccccc; +} + +.btn-gray:hover, +.btn-gray:focus, +.btn-gray:active, +.btn-gray.active, +.open .dropdown-toggle.btn-gray { + color: #333333; + background-color: #d5d5d5; + border-color: #adadad; +} + +.btn-gray:active, +.btn-gray.active, +.open .dropdown-toggle.btn-gray { + background-image: none; +} + +.btn-gray.disabled, +.btn-gray[disabled], +fieldset[disabled] .btn-gray, +.btn-gray.disabled:hover, +.btn-gray[disabled]:hover, +fieldset[disabled] .btn-gray:hover, +.btn-gray.disabled:focus, +.btn-gray[disabled]:focus, +fieldset[disabled] .btn-gray:focus, +.btn-gray.disabled:active, +.btn-gray[disabled]:active, +fieldset[disabled] .btn-gray:active, +.btn-gray.disabled.active, +.btn-gray[disabled].active, +fieldset[disabled] .btn-gray.active { + background-color: #eaeaea; + border-color: #cccccc; +} + .btn-radio-group .btn .icon-ok { display: none; } .btn-radio-group .btn.active { background: #999; color: white; } .btn-radio-group .btn.active .icon-ok, .btn-radio-group .btn:hover .icon-ok { display: inline-block; } diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/assets/css/page.css b/gitop.web/src/main/java/com/pmease/gitop/web/assets/css/page.css index c2066f853b..be8c652d5c 100644 --- a/gitop.web/src/main/java/com/pmease/gitop/web/assets/css/page.css +++ b/gitop.web/src/main/java/com/pmease/gitop/web/assets/css/page.css @@ -51,6 +51,8 @@ */ #main { background: white; } +.padder { padding: 20px; } + .sidebar .head, .content .head { padding: 15px 20px; white-space: nowrap; overflow: hidden; border-bottom: 1px solid #ddd; margin-bottom: 20px; } .sidebar .head a { color: #666; font-weight:bold; line-height: 24px; } .sidebar .head a:hover { text-decoration: none; color: #09F; } @@ -106,8 +108,8 @@ th.wicket_orderUp a, th.wicket_orderDown a { padding-left: 20px; } } /* LOGIN PAGE */ -#register-form, #login-form { padding: 50px 0; width: 500px; margin: 0 auto; } -#login-form h1, #register-form h1 { font-size: 32px; margin: 0 0 30px; font-weight: 300; } +.small-section, #register-form, #login-form { padding: 50px 0; width: 500px; margin: 0 auto; } +.small-section h1, #login-form h1, #register-form h1 { font-size: 32px; margin: 0 0 30px; font-weight: 300; } #login-form .input-group-addon { font-size: 18px; color: #666; } #login-form .user-group .input-group-addon { border-bottom-left-radius: 0; } #login-form .user-group input.form-control { border-bottom-right-radius: 0; } @@ -125,7 +127,7 @@ th.wicket_orderUp a, th.wicket_orderDown a { padding-left: 20px; } .members .list-group-item:hover a { color: #333; } .members .item-actions {top: 12px;} .members .item-actions .img-link { color: #ddd; font-size: 20px; } -.members .list-group-item:hover .img-link, .members .item-actions .img-link:hover { color: #990000; } +.members .list-group-item:hover .remove-link, .members .item-actions .remove-link:hover { color: #990000; } .members .avatar { width: 32px; height: 32px; margin-right: 8px; } .permission-link { color: #666; } @@ -134,5 +136,63 @@ a.permission-link:hover { color: #333; text-decoration: none; } .user-choice-row .img-thumbnail { float: left; } .user-choice-row p { margin-left: 44px; margin-bottom: 0; } .user-choice-row p.text-muted { font-size: 0.85em; } +.select2-container.user-choice .select2-choice { height: 34px; line-height: 34px;} .btn-group-permissions > a { width: 80px; } + +.link-item a.confirm-link:hover { color: #990000; } + +/* ACCOUNT HOME */ +.vcard-column { padding: 20px; } +.vcard-column .avatar { width: 200px; height: 200px; margin-bottom: 18px; } +.vcard-column .avatar > img { border-radius: 8px; } +.vcard-name { margin: 0 0 18px; } +.vcard-name b, .vcard-name em { display: block; white-space: nowrap; overflow: hidden; } +.vcard-name b { font-size: 26px; } +.vcard-name em { font-size: 24px; color: #888; font-style: normal; font-weight: 300; } +.vcard-stats { padding: 20px 0; border-top: 1px solid #ddd; overflow: hidden; } +.vcard-stats a { display: inline-block; color: #999; font-size: 12px; line-height: 1.123; width: 66px; overflow: hidden; text-align: center; } +.vcard-stats a > b { display: block; color: #333; font-size: 28px; } +.vcard-stats a:hover { text-decoration: none; color: #09f; } +.vcard-stats a:hover > b { color: #09f; } + +.nav-piped > li { float: left; } + +.page-nav { border-bottom: 1px solid #ddd; padding: 10px 20px 0; } +.page-nav ul { margin: 0; padding: 0; list-style: none; } + +.page-nav .nav-piped > li > a { color: #999; padding: 12px 15px;} +.page-nav .nav-piped > li > a:hover { background: transparent; color: #09f; } +.page-nav .nav-piped > li.active > a { color: #333; font-weight: bold; } +.page-nav .nav-piped > li.active > a:after{ + position: absolute; + bottom: -1px; + right: 50%; + margin-right: -6px; + display: inline-block; + width: 0; + height: 0; + border-bottom: 6px solid #333; + border-right: 6px solid transparent; + border-top: 0 dotted; + border-left: 6px solid transparent; + content: ""; +} + +ul.btn-list { list-style: none; margin: 0; } +.btn-list > li { float: left; list-style: none; padding: 0; margin: 0 6px;} +.page-nav .btn-list > li { padding: 7px 0; } +.page-nav .btn-list .btn { font-weight: bold; } + +.project-list .list-group-item { position: relative; padding-left: 44px; } +.project-list .list-group-item i { position: absolute; left: 0; top: 10px; font-size: 32px; width: 32px; color: #bbb; } +.project-list h4 { margin: 0 0 9px; } +.project-list p { margin: 0 0 6px; font-size: 12px;} +.project-list .last-updated { color: #999; } + +ul.members-list { list-style: none; margin: 0; padding: 0; } +.members-list > li.member { width: 220px; display: inline-block; list-style: none; padding-left: 60px; margin-right: 18px; margin-bottom: 18px; position: relative; font-size: 18px; white-space: nowrap; overflow: hidden;} +.member > .avatar { width: 48px; height: 48px; position: absolute; left: 0; top: 0; } +.member > .avatar > img { border-radius: 50%; } +.member > h4 { margin-top: 0; } +.member > em { font-style: normal; display: block; color: #999; } \ No newline at end of file diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/component/link/LinkPanel.html b/gitop.web/src/main/java/com/pmease/gitop/web/common/component/link/LinkPanel.html new file mode 100644 index 0000000000..efdc0d8738 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/component/link/LinkPanel.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/component/link/LinkPanel.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/component/link/LinkPanel.java new file mode 100644 index 0000000000..3d7c7d1773 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/component/link/LinkPanel.java @@ -0,0 +1,25 @@ +package com.pmease.gitop.web.common.component.link; + +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.AbstractLink; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.IModel; + +@SuppressWarnings("serial") +public abstract class LinkPanel extends Panel { + + public LinkPanel(String id, IModel label) { + super(id, label); + } + + @Override + protected void onInitialize() { + super.onInitialize(); + + AbstractLink link = createLink("link"); + add(link); + link.add(new Label("text", getDefaultModel())); + } + + protected abstract AbstractLink createLink(String id); +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/AbstractDataType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/AbstractDataType.java new file mode 100644 index 0000000000..fb7da5b218 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/AbstractDataType.java @@ -0,0 +1,31 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +public abstract class AbstractDataType implements DataType { + + @Override + public String asString(Object from, String pattern) { + return asString(from, pattern, Locale.getDefault()); + } + + @Override + public String asString(Object from) { + return asString(from, null); + } + + @Override + public Object fromString(String value, String pattern) { + return fromString(value, pattern, Locale.getDefault()); + } + + @Override + public Object fromString(String value) { + return fromString(value, null); + } + + @Override + public boolean isNumericType() { + return false; + } +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/AbstractNumericType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/AbstractNumericType.java new file mode 100644 index 0000000000..685db08406 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/AbstractNumericType.java @@ -0,0 +1,38 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +import org.apache.commons.validator.routines.BigDecimalValidator; + +public abstract class AbstractNumericType extends AbstractDataType { + + @Override + public boolean isNumericType() { + return true; + } + + protected Number toNumber(Object from) { + if (from == null) { + return 0; + } + + if (from instanceof Number) { + return (Number) from; + } + + if (from instanceof String) { + String s = (String) from; + if ("NaN".equalsIgnoreCase(s)) { + return Double.NaN; + } + + return BigDecimalValidator.getInstance().validate(s, null, Locale.getDefault()); + } + + if (from instanceof Boolean) { + return ((Boolean) from) ? 1 : 0; + } + + throw new TypeCastException(this, from); + } +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/BooleanType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/BooleanType.java new file mode 100644 index 0000000000..02938353c5 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/BooleanType.java @@ -0,0 +1,80 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +import com.google.common.base.Objects; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; + +public class BooleanType extends AbstractDataType { + + @Override + public String asString(Object from, String pattern, Locale locale) { + Boolean b = (Boolean) typeCast(from); + if (Strings.isNullOrEmpty(pattern)) { + return b.toString(); + } + + String[] tokens = Iterables.toArray(Splitter.on(":").limit(2).omitEmptyStrings().split(pattern), String.class); + return b ? tokens[0] : tokens[1]; + } + + @Override + public Object fromString(String value, String pattern, Locale locale) { + if (Strings.isNullOrEmpty(value)) { + return null; + } + + if (Strings.isNullOrEmpty(pattern)) { + if ("TRUE".equalsIgnoreCase(value)) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } + + String[] tokens = Iterables.toArray(Splitter.on(":").limit(2).omitEmptyStrings().split(pattern), String.class); + if (Objects.equal(value, tokens[0])) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } + + @Override + public Class getReturnClass() { + return Boolean.class; + } + + @Override + public Object typeCast(Object from) { + if (from == null){ + return Boolean.FALSE; + } + + if (from instanceof Boolean) { + return from; + } + + if (from instanceof Number) { + return ((Number) from).intValue() > 0; + } + + if (from instanceof String) { + if ("TRUE".equalsIgnoreCase((String) from)) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } + + throw new TypeCastException(this, from); + } + + @Override + public String getTypeName() { + return "BOOLEAN"; + } + +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/ByteType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/ByteType.java new file mode 100644 index 0000000000..09708b3242 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/ByteType.java @@ -0,0 +1,36 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +import org.apache.commons.validator.routines.ByteValidator; + +public class ByteType extends AbstractNumericType { + + @Override + public String asString(Object from, String pattern, Locale locale) { + Byte b = (Byte) typeCast(from); + return ByteValidator.getInstance().format(b, pattern, locale); + } + + @Override + public Object fromString(String value, String pattern, Locale locale) { + return ByteValidator.getInstance().validate(value, pattern, locale); + } + + @Override + public Object typeCast(Object from) { + Number number = toNumber(from); + return number.byteValue(); + } + + @Override + public Class getReturnClass() { + return Byte.class; + } + + @Override + public String getTypeName() { + return "BYTE"; + } + +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DataType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DataType.java new file mode 100644 index 0000000000..7ceb1a6d64 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DataType.java @@ -0,0 +1,26 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +public interface DataType { + + String asString(Object from, String pattern, Locale locale); + + String asString(Object from, String pattern); + + String asString(Object from); + + Object fromString(String value, String pattern, Locale locale); + + Object fromString(String value, String pattern); + + Object fromString(String value); + + Class getReturnClass(); + + boolean isNumericType(); + + Object typeCast(Object from); + + String getTypeName(); +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DataTypes.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DataTypes.java new file mode 100644 index 0000000000..230e180a11 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DataTypes.java @@ -0,0 +1,33 @@ +package com.pmease.gitop.web.common.datatype; + +public class DataTypes { + + private static enum NULLE { + } + + public static final DataType BOOLEAN = new BooleanType(); + public static final DataType BYTE = new ByteType(); + public static final DataType SHORT = new ShortType(); + public static final DataType INTEGER = new IntegerType(); + public static final DataType LONG = new LongType(); + public static final DataType FLOAT = new FloatType(); + public static final DataType DOUBLE = new DoubleType(); + public static final DataType PERCENT = new PercentType(); + public static final DataType DATE = new DateType(); + public static final DataType ENUM = new EnumType(NULLE.class); + public static final DataType STRING = new StringType(); + + static final DataType[] PRIMITIVES = new DataType[] { + BOOLEAN, BYTE, SHORT, INTEGER, LONG, FLOAT, DOUBLE, PERCENT, ENUM, STRING + }; + + public static DataType getType(Class clazz) { + for (DataType each : PRIMITIVES) { + if (each.getReturnClass().isAssignableFrom(clazz)) { + return each; + } + } + + throw new UnsupportedOperationException("Unable to find the data type for class [" + clazz + "]"); + } +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DateType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DateType.java new file mode 100644 index 0000000000..e48168ba0f --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DateType.java @@ -0,0 +1,82 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +import org.apache.commons.validator.routines.DateValidator; +import org.joda.time.DateTime; +import org.joda.time.ReadableDateTime; +import org.joda.time.format.ISODateTimeFormat; + +import com.google.common.base.Strings; + +public class DateType extends AbstractDataType { + + @Override + public String asString(Object from, String pattern, Locale locale) { + Date date = (Date) typeCast(from); + + if (date == null) { + return null; + } + + if (Strings.isNullOrEmpty(pattern)) { + return ISODateTimeFormat.dateTime().withZoneUTC().print(new DateTime(date)); + } + + return DateValidator.getInstance().format(date, pattern, locale); + } + + @Override + public Object fromString(String value, String pattern, Locale locale) { + if (Strings.isNullOrEmpty(pattern)) { + return ISODateTimeFormat.dateTime().withZoneUTC().parseDateTime(value).toDate(); + } + + return DateValidator.getInstance().validate(value, pattern, locale); + } + + @Override + public Class getReturnClass() { + return Date.class; + } + + @Override + public Object typeCast(Object from) { + if (from == null || (from instanceof Date)) { + return from; + } + + if (from instanceof Long) { + return new Date((Long) from); + } + + if (from instanceof Calendar) { + return new Date(((Calendar) from).getTimeInMillis()); + } + + if (from instanceof java.sql.Timestamp) { + return new Date(((java.sql.Timestamp) from).getTime()); + } + + if (from instanceof ReadableDateTime) { + return ((ReadableDateTime) from).toDateTime().toDate(); + } + + if (from instanceof String) { + DateTime dt = ISODateTimeFormat.dateTime().parseDateTime((String) from); + if (dt != null) { + return dt.toDate(); + } + } + + throw new TypeCastException(this, from); + } + + @Override + public String getTypeName() { + return "DATE"; + } + +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DoubleType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DoubleType.java new file mode 100644 index 0000000000..d0902b5e28 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DoubleType.java @@ -0,0 +1,35 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +import org.apache.commons.validator.routines.DoubleValidator; + +public class DoubleType extends AbstractNumericType { + + @Override + public String asString(Object from, String pattern, Locale locale) { + Double d = (Double) typeCast(from); + return DoubleValidator.getInstance().format(d, pattern, locale); + } + + @Override + public Object fromString(String value, String pattern, Locale locale) { + return DoubleValidator.getInstance().validate(value, pattern, locale); + } + + @Override + public Class getReturnClass() { + return Double.class; + } + + @Override + public Object typeCast(Object from) { + return toNumber(from).doubleValue(); + } + + @Override + public String getTypeName() { + return "DOUBLE"; + } + +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DurationFormatUtils.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DurationFormatUtils.java new file mode 100644 index 0000000000..062e4f59ee --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DurationFormatUtils.java @@ -0,0 +1,158 @@ +package com.pmease.gitop.web.common.datatype; + +import org.joda.time.format.ISOPeriodFormat; +import org.joda.time.format.PeriodFormatter; +import org.joda.time.format.PeriodFormatterBuilder; + +public class DurationFormatUtils { + // public static final String ISO8601 = "yyyy-MM-dd'T'HH:mm:ssZ"; + + public static PeriodFormatter createPeriodFormatter(String pattern) { + PeriodFormatterBuilder builder = new PeriodFormatterBuilder(); + parsePatternTo(builder, pattern); + return builder.toFormatter(); + } + + private static void parsePatternTo(PeriodFormatterBuilder builder, String pattern) { + int length = pattern.length(); + int[] indexRef = new int[1]; + + builder.printZeroAlways(); + + for (int i = 0; i < length; i++) { + indexRef[0] = i; + String token = parseToken(pattern, indexRef); + i = indexRef[0]; + + int tokenLen = token.length(); + if (tokenLen == 0) { + break; + } + char c = token.charAt(0); + + switch (c) { + case 'y': // year (number) + builder.appendYears(); + break; + case 'd': // days + builder.minimumPrintedDigits(tokenLen); + builder.appendDays(); + break; + case 'H': // hours + builder.minimumPrintedDigits(tokenLen); + builder.appendHours(); + break; + case 'm': // minutes + builder.minimumPrintedDigits(tokenLen); + builder.appendMinutes(); + break; + case 's': // seconds + builder.minimumPrintedDigits(tokenLen); + builder.appendSeconds(); + break; + case 'S': // milliseconds + builder.minimumPrintedDigits(tokenLen); + builder.appendMillis(); + break; + case '\'': // literal text + String sub = token.substring(1); + builder.appendLiteral(new String(sub)); + break; + default: + throw new IllegalArgumentException("Illegal pattern component: " + token); + } + } + } + + /** + * Parses an individual token. + * + * @param pattern the pattern string + * @param indexRef a single element array, where the input is the start location and the output is + * the location after parsing the token + * @return the parsed token + */ + private static String parseToken(String pattern, int[] indexRef) { + StringBuffer buf = new StringBuffer(); + + int i = indexRef[0]; + int length = pattern.length(); + + char c = pattern.charAt(i); + if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') { + // Scan a run of the same character, which indicates a time + // pattern. + buf.append(c); + + while (i + 1 < length) { + char peek = pattern.charAt(i + 1); + if (peek == c) { + buf.append(c); + i++; + } else { + break; + } + } + } else { + // This will identify token as text. + buf.append('\''); + + boolean inLiteral = false; + + for (; i < length; i++) { + c = pattern.charAt(i); + + if (c == '\'') { + if (i + 1 < length && pattern.charAt(i + 1) == '\'') { + // '' is treated as escaped ' + i++; + buf.append(c); + } else { + inLiteral = !inLiteral; + } + } else if (!inLiteral && (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) { + i--; + break; + } else { + buf.append(c); + } + } + } + + indexRef[0] = i; + return buf.toString(); + } + + public static PeriodFormatter isoFormatter() { + return ISOPeriodFormat.standard(); + } + + static PeriodFormatter wordFormatter = new PeriodFormatterBuilder().appendYears() + .appendSuffix(" year", " years").appendSeparator(" ").appendMonths() + .appendSuffix(" month", " months").appendSeparator(" ").appendDays() + .appendSuffix(" day", " days").appendSeparator(" ").appendHours() + .appendSuffix(" hour", " hours").appendSeparator(" ").appendMinutes() + .appendSuffix(" minute", " minutes").appendSeparator(" ").appendSeconds() + .appendSuffix(" second", " seconds").appendSeparator(" ").appendMillis3Digit().toFormatter(); + + public static PeriodFormatter wordFormatter() { + return wordFormatter; + } + + static PeriodFormatter shortWordFormatter = new PeriodFormatterBuilder().appendDays() + .appendSuffix("d").appendSeparator(", ").appendHours().appendSuffix("h").appendSeparator(":") + .appendMinutes().appendSuffix("m").appendSeparator(":").appendSeconds().appendSuffix("s") + .appendSeparator(", ").appendMillis().toFormatter(); + + public static PeriodFormatter shortWordFormatter() { + return shortWordFormatter; + } + + static PeriodFormatter timeFormatter = new PeriodFormatterBuilder().printZeroAlways() + .appendHours().appendSuffix(":").appendMinutes().appendSuffix(":").appendSeconds() + .toFormatter(); + + public static PeriodFormatter getTimeFormatter() { + return timeFormatter; + } +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DurationType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DurationType.java new file mode 100644 index 0000000000..af40e5c4e3 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/DurationType.java @@ -0,0 +1,91 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +import org.apache.commons.validator.routines.BigDecimalValidator; +import org.joda.time.DateTimeConstants; +import org.joda.time.Duration; +import org.joda.time.Period; +import org.joda.time.PeriodType; +import org.joda.time.chrono.ISOChronology; +import org.joda.time.format.PeriodFormatter; + +import com.google.common.base.Strings; + +public class DurationType extends LongType { + + public static final String FMT_WORD = "WORD"; + public static final String FMT_ISO = "ISO"; + public static final String FMT_SHORT_WORD = "SHORT"; + + @Override + public Object fromString(String str, String pattern, Locale locale) { + if (Strings.isNullOrEmpty(str)) { + return null; + } + + if ("NaN".equalsIgnoreCase(str)) { + return new Long(0); + } + + if (Strings.isNullOrEmpty(pattern)) { + try { + return Long.valueOf(str); + } catch (NumberFormatException e) { + Number number = BigDecimalValidator.getInstance().validate(str, locale); + if (number != null) { + return number.longValue(); + } else { + return 0; + } + } + } + + PeriodFormatter formatter = getFormatter(pattern, locale); + + Period period = formatter.parsePeriod(str); + return Long.valueOf(period.toStandardDuration().getMillis()); + } + + @Override + public String asString(Object value, String pattern, Locale locale) { + Long mills = (Long) typeCast(value); + if (mills == null) { + return null; + } + + if (Strings.isNullOrEmpty(pattern)) { + return mills.toString(); + } + + if (mills == 0) { + return "0 mil"; + } else if (mills < DateTimeConstants.MILLIS_PER_SECOND) { + return mills + " mils"; + } + + PeriodFormatter fmt = getFormatter(pattern, locale); + Duration duration = new Duration(mills); + return fmt.print(duration.toPeriod(PeriodType.yearMonthDayTime(), + ISOChronology.getInstanceUTC())); + } + + protected PeriodFormatter getFormatter(String pattern, Locale locale) { + PeriodFormatter fmt = null; + if (FMT_WORD.equalsIgnoreCase(pattern)) { + fmt = DurationFormatUtils.wordFormatter(); + } else if (FMT_ISO.equalsIgnoreCase(pattern)) { + fmt = DurationFormatUtils.isoFormatter(); + } else if (FMT_SHORT_WORD.equalsIgnoreCase(pattern)) { + fmt = DurationFormatUtils.shortWordFormatter(); + } else { + fmt = DurationFormatUtils.createPeriodFormatter(pattern); + } + if (locale != null) { + fmt = fmt.withLocale(locale); + } + return fmt; + } + + +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/EnumType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/EnumType.java new file mode 100644 index 0000000000..98f635ae7f --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/EnumType.java @@ -0,0 +1,62 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +@SuppressWarnings("rawtypes") +public class EnumType extends AbstractDataType { + + private final Class enumClass; + + public EnumType(Class enumClass) { + this.enumClass = enumClass; + } + + public static EnumType of(Class clazz) { + return new EnumType(clazz); + } + + @Override + public String asString(Object from, String pattern, Locale locale) { + Enum e = (Enum) typeCast(from); + if (e == null) { + return null; + } else { + return e.toString(); + } + } + + @SuppressWarnings("unchecked") + @Override + public Object fromString(String value, String pattern, Locale locale) { + return Enum.valueOf(enumClass, value); + } + + @Override + public Class getReturnClass() { + return Enum.class; + } + + @Override + public Object typeCast(Object from) { + if (from == null || from instanceof Enum) { + return from; + } + + if (from instanceof String) { + return fromString((String) from); + } + + if (from instanceof Integer) { + int i = (Integer) from; + return enumClass.getEnumConstants()[i]; + } + + throw new TypeCastException(this, from); + } + + @Override + public String getTypeName() { + return "ENUM"; + } + +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/FloatType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/FloatType.java new file mode 100644 index 0000000000..2b25f48f0c --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/FloatType.java @@ -0,0 +1,35 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +import org.apache.commons.validator.routines.FloatValidator; + +public class FloatType extends AbstractNumericType { + + @Override + public String asString(Object from, String pattern, Locale locale) { + Float f = (Float) typeCast(from); + return FloatValidator.getInstance().format(f, pattern, locale); + } + + @Override + public Object fromString(String value, String pattern, Locale locale) { + return FloatValidator.getInstance().validate(value, pattern, locale); + } + + @Override + public Class getReturnClass() { + return Float.class; + } + + @Override + public Object typeCast(Object from) { + return toNumber(from).floatValue(); + } + + @Override + public String getTypeName() { + return "FLOAT"; + } + +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/IntegerType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/IntegerType.java new file mode 100644 index 0000000000..48214ccbd3 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/IntegerType.java @@ -0,0 +1,35 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +import org.apache.commons.validator.routines.IntegerValidator; + +public class IntegerType extends AbstractNumericType { + + @Override + public String asString(Object from, String pattern, Locale locale) { + Integer i = (Integer) typeCast(from); + return IntegerValidator.getInstance().format(i, pattern, locale); + } + + @Override + public Object fromString(String value, String pattern, Locale locale) { + return IntegerValidator.getInstance().validate(value, pattern, locale); + } + + @Override + public Class getReturnClass() { + return Integer.class; + } + + @Override + public Object typeCast(Object from) { + return toNumber(from).intValue(); + } + + @Override + public String getTypeName() { + return "INTEGER"; + } + +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/LongType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/LongType.java new file mode 100644 index 0000000000..c58f7d51b0 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/LongType.java @@ -0,0 +1,35 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +import org.apache.commons.validator.routines.LongValidator; + +public class LongType extends AbstractNumericType { + + @Override + public String asString(Object from, String pattern, Locale locale) { + Long l = (Long) typeCast(from); + return LongValidator.getInstance().format(l, pattern, locale); + } + + @Override + public Object fromString(String value, String pattern, Locale locale) { + return LongValidator.getInstance().validate(value, pattern, locale); + } + + @Override + public Class getReturnClass() { + return Long.class; + } + + @Override + public Object typeCast(Object from) { + return toNumber(from).longValue(); + } + + @Override + public String getTypeName() { + return "LONG"; + } + +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/PercentType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/PercentType.java new file mode 100644 index 0000000000..7b255b41ec --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/PercentType.java @@ -0,0 +1,35 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +import org.apache.commons.validator.routines.PercentValidator; + +public class PercentType extends AbstractNumericType { + + @Override + public String asString(Object from, String pattern, Locale locale) { + Double d = (Double) typeCast(from); + return PercentValidator.getInstance().format(d, pattern, locale); + } + + @Override + public Object fromString(String value, String pattern, Locale locale) { + return PercentValidator.getInstance().validate(value, pattern, locale).doubleValue(); + } + + @Override + public Class getReturnClass() { + return Double.class; + } + + @Override + public Object typeCast(Object from) { + return toNumber(from).doubleValue(); + } + + @Override + public String getTypeName() { + return "PERCENT"; + } + +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/ShortType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/ShortType.java new file mode 100644 index 0000000000..2d1c2538ce --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/ShortType.java @@ -0,0 +1,41 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +import org.apache.commons.validator.routines.ShortValidator; + +import com.google.common.base.Strings; + +public class ShortType extends AbstractNumericType { + + @Override + public String asString(Object from, String pattern, Locale locale) { + Short s = (Short) typeCast(from); + return ShortValidator.getInstance().format(s, pattern, locale); + } + + @Override + public Object fromString(String value, String pattern, Locale locale) { + if (Strings.isNullOrEmpty(value)) { + return 0; + } + + return ShortValidator.getInstance().validate(value, pattern, locale); + } + + @Override + public Class getReturnClass() { + return Short.class; + } + + @Override + public Object typeCast(Object from) { + return toNumber(from).shortValue(); + } + + @Override + public String getTypeName() { + return "SHORT"; + } + +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/StringType.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/StringType.java new file mode 100644 index 0000000000..5a9153b390 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/StringType.java @@ -0,0 +1,40 @@ +package com.pmease.gitop.web.common.datatype; + +import java.util.Locale; + +public class StringType extends AbstractDataType { + + @Override + public String asString(Object from, String pattern, Locale locale) { + if (from == null || (from instanceof String)) { + return (String) from; + } + + return from.toString(); + } + + @Override + public Object fromString(String value, String pattern, Locale locale) { + return value; + } + + @Override + public Class getReturnClass() { + return String.class; + } + + @Override + public Object typeCast(Object from) { + if (from == null || (from instanceof String)) { + return from; + } + + return from.toString(); + } + + @Override + public String getTypeName() { + return "STRING"; + } + +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/TypeCastException.java b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/TypeCastException.java new file mode 100644 index 0000000000..14c2b9e642 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/common/datatype/TypeCastException.java @@ -0,0 +1,14 @@ +package com.pmease.gitop.web.common.datatype; + +public class TypeCastException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public TypeCastException(String msg) { + super(msg); + } + + public TypeCastException(DataType dataType, Object value) { + this("Cannot cast object [" + value + "] to data type [" + dataType + "]"); + } +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/component/link/ProjectHomeLink.java b/gitop.web/src/main/java/com/pmease/gitop/web/component/link/ProjectHomeLink.java new file mode 100644 index 0000000000..2502f7ce4f --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/component/link/ProjectHomeLink.java @@ -0,0 +1,44 @@ +package com.pmease.gitop.web.component.link; + +import org.apache.wicket.markup.html.link.AbstractLink; +import org.apache.wicket.model.AbstractReadOnlyModel; +import org.apache.wicket.model.IModel; + +import com.pmease.gitop.core.model.Project; +import com.pmease.gitop.web.common.component.link.LinkPanel; +import com.pmease.gitop.web.page.PageSpec; + +public class ProjectHomeLink extends LinkPanel { + private static final long serialVersionUID = 1L; + + private final IModel projectModel; + + @SuppressWarnings("serial") + public ProjectHomeLink(String id, final IModel projectModel) { + super(id, new AbstractReadOnlyModel() { + + @Override + public String getObject() { + Project project = projectModel.getObject(); + return project.getOwner().getName() + " / " + project.getName(); + } + + }); + + this.projectModel = projectModel; + } + + @Override + protected AbstractLink createLink(String id) { + return PageSpec.newProjectHomeLink(id, projectModel.getObject()); + } + + @Override + public void onDetach() { + if (projectModel != null) { + projectModel.detach(); + } + + super.onDetach(); + } +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/component/link/UserAvatarLink.java b/gitop.web/src/main/java/com/pmease/gitop/web/component/link/UserAvatarLink.java index c89235e30d..f6b7efb06d 100644 --- a/gitop.web/src/main/java/com/pmease/gitop/web/component/link/UserAvatarLink.java +++ b/gitop.web/src/main/java/com/pmease/gitop/web/component/link/UserAvatarLink.java @@ -11,7 +11,7 @@ import com.google.common.base.Preconditions; import com.pmease.gitop.core.model.User; import com.pmease.gitop.web.component.avatar.AvatarImage; import com.pmease.gitop.web.page.PageSpec; -import com.pmease.gitop.web.page.account.AccountHomePage; +import com.pmease.gitop.web.page.account.home.AccountHomePage; import com.pmease.gitop.web.util.WicketUtils; @SuppressWarnings("serial") diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/GlobalHeaderPanel.html b/gitop.web/src/main/java/com/pmease/gitop/web/page/GlobalHeaderPanel.html index 726bffafba..50af2e2c45 100644 --- a/gitop.web/src/main/java/com/pmease/gitop/web/page/GlobalHeaderPanel.html +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/GlobalHeaderPanel.html @@ -19,7 +19,7 @@ -
  • +
  • diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/GlobalHeaderPanel.java b/gitop.web/src/main/java/com/pmease/gitop/web/page/GlobalHeaderPanel.java index 88e3008a5d..5272794f58 100644 --- a/gitop.web/src/main/java/com/pmease/gitop/web/page/GlobalHeaderPanel.java +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/GlobalHeaderPanel.java @@ -10,6 +10,7 @@ import com.pmease.gitop.web.component.link.UserAvatarLink; import com.pmease.gitop.web.model.UserModel; import com.pmease.gitop.web.page.account.RegisterPage; import com.pmease.gitop.web.page.account.setting.profile.AccountProfilePage; +import com.pmease.gitop.web.page.project.CreateProjectPage; import com.pmease.gitop.web.shiro.LoginPage; import com.pmease.gitop.web.shiro.LogoutPage; @@ -28,6 +29,7 @@ public class GlobalHeaderPanel extends Panel { if (isSignedIn()) { add(new UserAvatarLink("userlink", new UserModel(currentUser().get()))); add(new BookmarkablePageLink("profileLink", AccountProfilePage.class)); + add(new BookmarkablePageLink("newlink", CreateProjectPage.class)); } else { add(new WebMarkupContainer("userlink").setVisibilityAllowed(false)); } diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/PageSpec.java b/gitop.web/src/main/java/com/pmease/gitop/web/page/PageSpec.java index 0087ecf934..69a69a7bb3 100644 --- a/gitop.web/src/main/java/com/pmease/gitop/web/page/PageSpec.java +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/PageSpec.java @@ -7,7 +7,7 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import com.pmease.gitop.core.model.Project; import com.pmease.gitop.core.model.User; import com.pmease.gitop.web.component.avatar.AvatarImage.AvatarImageType; -import com.pmease.gitop.web.page.account.AccountHomePage; +import com.pmease.gitop.web.page.account.home.AccountHomePage; import com.pmease.gitop.web.page.project.ProjectHomePage; import com.pmease.gitop.web.util.WicketUtils; diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/AbstractAccountPage.java b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/AbstractAccountPage.java new file mode 100644 index 0000000000..f205b5879f --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/AbstractAccountPage.java @@ -0,0 +1,46 @@ +package com.pmease.gitop.web.page.account; + +import javax.persistence.EntityNotFoundException; + +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.mapper.parameter.PageParameters; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.pmease.gitop.core.Gitop; +import com.pmease.gitop.core.manager.UserManager; +import com.pmease.gitop.core.model.User; +import com.pmease.gitop.web.model.UserModel; +import com.pmease.gitop.web.page.AbstractLayoutPage; +import com.pmease.gitop.web.page.PageSpec; + +@SuppressWarnings("serial") +public abstract class AbstractAccountPage extends AbstractLayoutPage { + + protected final IModel accountModel; + + public AbstractAccountPage(PageParameters params) { + String name = params.get(PageSpec.USER).toString(); + Preconditions.checkArgument(!Strings.isNullOrEmpty(name)); + + User user = Gitop.getInstance(UserManager.class).find(name); + if (user == null) { + throw new EntityNotFoundException("User " + name + " not found"); + } + + this.accountModel = new UserModel(user); + } + + @Override + public void onDetach() { + if (accountModel != null) { + accountModel.detach(); + } + + super.onDetach(); + } + + protected User getAccount() { + return accountModel.getObject(); + } +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/AccountHomePage.html b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/AccountHomePage.html deleted file mode 100644 index 3a84612183..0000000000 --- a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/AccountHomePage.html +++ /dev/null @@ -1,7 +0,0 @@ - - -

    Welcome, Account home

    -
    - link -
    - \ No newline at end of file diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/AccountHomePage.java b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/AccountHomePage.java deleted file mode 100644 index 04d26f6280..0000000000 --- a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/AccountHomePage.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.pmease.gitop.web.page.account; - -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.link.Link; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.LoadableDetachableModel; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import com.pmease.commons.util.GeneralException; -import com.pmease.gitop.core.Gitop; -import com.pmease.gitop.core.manager.UserManager; -import com.pmease.gitop.core.model.User; -import com.pmease.gitop.web.page.AbstractLayoutPage; - -@SuppressWarnings("serial") -public class AccountHomePage extends AbstractLayoutPage { - - private final IModel accountModel; - - public AccountHomePage(PageParameters params) { - String accountName = params.get("user").toString(); - - User account = Gitop.getInstance(UserManager.class).find(accountName); - if (account == null) - throw new GeneralException("Account %s does not exist!", accountName); - - final Long accountId = account.getId(); - - accountModel = new LoadableDetachableModel() { - - @Override - protected User load() { - return Gitop.getInstance(UserManager.class).load(accountId); - } - - }; - } - - @Override - protected void onPageInitialize() { - super.onPageInitialize(); - - add(new Label("accountName", getAccount().getName())); - - add(new Link("link") { - - @Override - public void onClick() { - - } - - }); - } - - @Override - protected String getPageTitle() { - return "Gitop"; - } - - public User getAccount() { - return accountModel.getObject(); - } - - @Override - public void detachModels() { - if (accountModel != null) - accountModel.detach(); - - super.detachModels(); - } - - public static PageParameters paramsOf(User account) { - PageParameters params = new PageParameters(); - params.set("user", account.getName()); - return params; - } - -} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/AccountHomePage.html b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/AccountHomePage.html new file mode 100644 index 0000000000..6cab6f159f --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/AccountHomePage.html @@ -0,0 +1,39 @@ + + +
    +
    + +
    +
    + + +
    + +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/AccountHomePage.java b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/AccountHomePage.java new file mode 100644 index 0000000000..8c30f4516a --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/AccountHomePage.java @@ -0,0 +1,96 @@ +package com.pmease.gitop.web.page.account.home; + +import org.apache.shiro.SecurityUtils; +import org.apache.wicket.Component; +import org.apache.wicket.behavior.AttributeAppender; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.AbstractLink; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.model.PropertyModel; +import org.apache.wicket.request.mapper.parameter.PageParameters; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.pmease.gitop.core.permission.ObjectPermission; +import com.pmease.gitop.web.component.avatar.AvatarImage; +import com.pmease.gitop.web.page.PageSpec; +import com.pmease.gitop.web.page.account.AbstractAccountPage; + +@SuppressWarnings("serial") +public class AccountHomePage extends AbstractAccountPage { + + public static enum Category { + PROJECTS("Projects"), + MEMBERS("Members"); + + final String displayName; + Category(String displayName) { + this.displayName = displayName; + } + } + + private Category category = Category.PROJECTS; + + public AccountHomePage(PageParameters params) { + super(params); + + String tab = params.get("tab").toString(); + if (!Strings.isNullOrEmpty(tab)) { + category = Category.valueOf(tab.toUpperCase()); + } + } + + @Override + protected void onPageInitialize() { + super.onPageInitialize(); + add(new AvatarImage("avatar", accountModel)); + add(new Label("fullname", new PropertyModel(accountModel, "displayName"))); + add(new Label("username", new PropertyModel(accountModel, "name"))); + + add(new ListView("category", Lists.newArrayList(Category.values())) { + + @Override + protected void populateItem(ListItem item) { + Category current = item.getModelObject(); + PageParameters params = PageSpec.forUser(accountModel.getObject()); + if (current != Category.PROJECTS) { + params.add(PageSpec.TAB, current.name().toLowerCase()); + } + + AbstractLink link = new BookmarkablePageLink("link", + AccountHomePage.class, params); + link.add(new Label("name", current.displayName)); + item.add(link); + item.add(AttributeAppender.append("class", category == current ? "active" : "")); + } + }); + + add(createContent("content")); + } + + private Component createContent(String id) { + switch (category) { + case PROJECTS: + return new ProjectListPanel(id, accountModel); + + case MEMBERS: + return new MemberListPanel(id, accountModel); + + default: + throw new IllegalArgumentException("tab " + category); + } + + } + + @Override + protected boolean isPermitted() { + return SecurityUtils.getSubject().isPermitted(ObjectPermission.ofUserRead(getAccount())); + } + + @Override + protected String getPageTitle() { + return getAccount().getName() + " (" + getAccount().getDisplayName() + ")"; + } +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/MemberListPanel.html b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/MemberListPanel.html new file mode 100644 index 0000000000..0b02f35e05 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/MemberListPanel.html @@ -0,0 +1,11 @@ + + +
      +
    • + +

      + +
    • +
    +
    + \ No newline at end of file diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/MemberListPanel.java b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/MemberListPanel.java new file mode 100644 index 0000000000..071592e5cb --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/MemberListPanel.java @@ -0,0 +1,79 @@ +package com.pmease.gitop.web.page.account.home; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.AbstractLink; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.LoadableDetachableModel; +import org.apache.wicket.model.PropertyModel; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.pmease.gitop.core.model.Membership; +import com.pmease.gitop.core.model.Team; +import com.pmease.gitop.core.model.User; +import com.pmease.gitop.web.component.avatar.AvatarImage; +import com.pmease.gitop.web.model.UserModel; +import com.pmease.gitop.web.page.PageSpec; + +@SuppressWarnings("serial") +public class MemberListPanel extends Panel { + + public MemberListPanel(String id, IModel model) { + super(id, model); + } + + @Override + protected void onInitialize() { + super.onInitialize(); + + IModel> model = new LoadableDetachableModel>() { + + @Override + protected List load() { + User account = getThisAccount(); + Collection teams = account.getTeams(); + Set users = Sets.newHashSet(); + + for (Team each : teams) { + for (Membership membership : each.getMemberships()) { + users.add(membership.getUser()); + } + } + + List result = Lists.newArrayList(users); + Collections.sort(result); + return result; + } + + }; + + ListView membersView = new ListView("member", model) { + + @Override + protected void populateItem(ListItem item) { + User user = item.getModelObject(); + IModel model = new UserModel(user); + item.add(new AvatarImage("avatar", model)); + AbstractLink link = PageSpec.newUserHomeLink("namelink", user); + link.add(new Label("name", new PropertyModel(model, "name"))); + item.add(link); + item.add(new Label("displayname", new PropertyModel(model, "displayName"))); + } + + }; + + add(membersView); + } + + private User getThisAccount() { + return (User) getDefaultModelObject(); + } +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/ProjectListPanel.html b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/ProjectListPanel.html new file mode 100644 index 0000000000..9133e5037d --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/ProjectListPanel.html @@ -0,0 +1,30 @@ + + +
    +
    + +
    + +
    + New +
    +
    +

    +
      +
    • + +

      + + forked from + +

      +

      Last updated 6 hours ago

      +
    • +
    +
    + \ No newline at end of file diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/ProjectListPanel.java b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/ProjectListPanel.java new file mode 100644 index 0000000000..0652ddc7b1 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/home/ProjectListPanel.java @@ -0,0 +1,76 @@ +package com.pmease.gitop.web.page.account.home; + +import java.util.List; + +import org.apache.shiro.SecurityUtils; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.LoadableDetachableModel; + +import com.google.common.collect.Lists; +import com.pmease.gitop.core.model.Project; +import com.pmease.gitop.core.model.User; +import com.pmease.gitop.core.permission.ObjectPermission; +import com.pmease.gitop.web.component.link.ProjectHomeLink; +import com.pmease.gitop.web.model.ProjectModel; +import com.pmease.gitop.web.page.PageSpec; + +@SuppressWarnings("serial") +public class ProjectListPanel extends Panel { + + public ProjectListPanel(String id, IModel model) { + super(id, model); + } + + @Override + protected void onInitialize() { + super.onInitialize(); + + IModel> model = new LoadableDetachableModel>() { + + @Override + protected List load() { + User account = getThisAccount(); + List projects = Lists.newArrayList(); + for (Project each : account.getRepositories()) { + if (SecurityUtils.getSubject().isPermitted(ObjectPermission.ofProjectRead(each))) { + projects.add(each); + } + } + + return projects; + } + + }; + + ListView projectsView = new ListView("project", model) { + + @Override + protected void populateItem(ListItem item) { + Project project = item.getModelObject(); +// IModel model = new ProjectModel(project); + item.add(PageSpec.newProjectHomeLink("projectlink", project) + .add(new Label("name", project.getName()))); + + if (project.getForkedFrom() != null) { + item.add(new ProjectHomeLink("forklink", new ProjectModel(project.getForkedFrom()))); + } else { + item.add(new WebMarkupContainer("forklink").setVisibilityAllowed(false)); + } + + item.add(new Label("description", project.getDescription())); + } + + }; + + add(projectsView); + } + + private User getThisAccount() { + return (User) getDefaultModelObject(); + } +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/permission/AccountPermissionPage.html b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/permission/AccountPermissionPage.html index 724257cd71..770790b5ce 100644 --- a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/permission/AccountPermissionPage.html +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/permission/AccountPermissionPage.html @@ -22,7 +22,7 @@

    Teams

    -

    Manage teams and grant the permissions so that all members in the team can have the permission to access any repositories under this account.

    +

    Manage teams and grant the permissions so that all members in the team can have the permissions to access any repositories under this account.

    diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/permission/TeamEditor.html b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/permission/TeamEditor.html index 3074a93100..da2ee2633b 100644 --- a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/permission/TeamEditor.html +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/permission/TeamEditor.html @@ -44,7 +44,7 @@ ()
    - +
    diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/repos/AccountReposPage.html b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/repos/AccountReposPage.html new file mode 100644 index 0000000000..2c2d47b9fd --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/repos/AccountReposPage.html @@ -0,0 +1,19 @@ + + +
    +
    +

    Repositories

    +
    +
    + +
    +
    +
    + \ No newline at end of file diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/repos/AccountReposPage.java b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/repos/AccountReposPage.java index 4e4e3ce217..441530a814 100644 --- a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/repos/AccountReposPage.java +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/repos/AccountReposPage.java @@ -1,6 +1,19 @@ package com.pmease.gitop.web.page.account.setting.repos; +import java.util.List; + +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.LoadableDetachableModel; + +import com.google.common.collect.Lists; +import com.pmease.gitop.core.model.Project; +import com.pmease.gitop.web.model.ProjectModel; +import com.pmease.gitop.web.page.PageSpec; import com.pmease.gitop.web.page.account.setting.AccountSettingPage; +import com.pmease.gitop.web.page.project.ProjectAdminPage; @SuppressWarnings("serial") public class AccountReposPage extends AccountSettingPage { @@ -14,4 +27,32 @@ public class AccountReposPage extends AccountSettingPage { protected Category getSettingCategory() { return Category.REPOS; } + + @Override + protected void onPageInitialize() { + super.onPageInitialize(); + + IModel> model = new LoadableDetachableModel>() { + + @Override + protected List load() { + return Lists.newArrayList(getAccount().getRepositories()); + } + + }; + + ListView view = new ListView("projects", model) { + + @Override + protected void populateItem(ListItem item) { + Project project = item.getModelObject(); + item.add(new SimpleProjectInfo("info", new ProjectModel(project))); + item.add(new BookmarkablePageLink("admin", ProjectAdminPage.class, + PageSpec.forProject(project))); + } + + }; + + add(view); + } } diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/repos/SimpleProjectInfo.html b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/repos/SimpleProjectInfo.html new file mode 100644 index 0000000000..673f6de3d4 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/repos/SimpleProjectInfo.html @@ -0,0 +1,5 @@ + + + steve/repo + + \ No newline at end of file diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/repos/SimpleProjectInfo.java b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/repos/SimpleProjectInfo.java new file mode 100644 index 0000000000..3a17d90b5a --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/account/setting/repos/SimpleProjectInfo.java @@ -0,0 +1,58 @@ +package com.pmease.gitop.web.page.account.setting.repos; + +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.IModel; + +import com.pmease.gitop.core.model.Project; + +public class SimpleProjectInfo extends Panel { + + private static final long serialVersionUID = 1L; + + public SimpleProjectInfo(String id, IModel model) { + super(id, model); + } + + @Override + protected void onInitialize() { + super.onInitialize(); + +// add(new UserProjectLink("project", (IModel) getDefaultModel())); +// add(new Label("age", new AbstractReadOnlyModel() { +// +// @Override +// public String getObject() { +// return DateUtils.formatAge(getProject().getCreatedAt()); +// } +// +// }).add(AttributeModifier.replace("title", +// new AbstractReadOnlyModel() { +// +// @Override +// public String getObject() { +// return DataTypes.DATE +// .asString(getProject().getCreatedAt(), +// Constants.DATETIME_FULL_FORMAT); +// } +// +// }))); +// +// if (getProject().isForked()) { +// add(new UserProjectLink("forkedFrom", +// new LoadableDetachableModel() { +// +// @Override +// protected Project load() { +// return getProject().getParentFork().getForkedFrom(); +// } +// })); +// } else { +// add(new WebMarkupContainer("forkedFrom") +// .setVisibilityAllowed(false)); +// } + } + + protected Project getProject() { + return (Project) getDefaultModelObject(); + } +} \ No newline at end of file diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/project/AbstractProjectPage.java b/gitop.web/src/main/java/com/pmease/gitop/web/page/project/AbstractProjectPage.java new file mode 100644 index 0000000000..1db6c62f72 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/project/AbstractProjectPage.java @@ -0,0 +1,47 @@ +package com.pmease.gitop.web.page.project; + +import javax.persistence.EntityNotFoundException; + +import org.apache.shiro.SecurityUtils; +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.mapper.parameter.PageParameters; + +import com.google.common.base.Preconditions; +import com.pmease.gitop.core.Gitop; +import com.pmease.gitop.core.manager.ProjectManager; +import com.pmease.gitop.core.model.Project; +import com.pmease.gitop.core.model.User; +import com.pmease.gitop.core.permission.ObjectPermission; +import com.pmease.gitop.web.model.ProjectModel; +import com.pmease.gitop.web.page.PageSpec; +import com.pmease.gitop.web.page.account.AbstractAccountPage; + +@SuppressWarnings("serial") +public abstract class AbstractProjectPage extends AbstractAccountPage { + + protected IModel projectModel; + + public AbstractProjectPage(PageParameters params) { + super(params); + + String projectName = params.get(PageSpec.PROJECT).toString(); + Preconditions.checkNotNull(projectName); + + User user = accountModel.getObject(); + Project project = Gitop.getInstance(ProjectManager.class).find( + user, projectName); + + if (project == null) { + throw new EntityNotFoundException("Unable to find project " + + user.getName() + " / " + projectName); + } + + projectModel = new ProjectModel(project); + } + + @Override + protected boolean isPermitted() { + return SecurityUtils.getSubject().isPermitted( + ObjectPermission.ofProjectRead(projectModel.getObject())); + } +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/project/CreateProjectPage.html b/gitop.web/src/main/java/com/pmease/gitop/web/page/project/CreateProjectPage.html new file mode 100644 index 0000000000..86c2edd5d0 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/project/CreateProjectPage.html @@ -0,0 +1,17 @@ + + +
    +

    Create Project

    + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    + \ No newline at end of file diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/project/CreateProjectPage.java b/gitop.web/src/main/java/com/pmease/gitop/web/page/project/CreateProjectPage.java new file mode 100644 index 0000000000..7b3b66deac --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/project/CreateProjectPage.java @@ -0,0 +1,88 @@ +package com.pmease.gitop.web.page.project; + +import org.apache.shiro.authz.annotation.RequiresUser; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.form.AjaxButton; +import org.apache.wicket.bean.validation.PropertyValidator; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.PropertyModel; +import org.apache.wicket.validation.IValidatable; +import org.apache.wicket.validation.IValidator; +import org.apache.wicket.validation.ValidationError; + +import com.pmease.gitop.core.Gitop; +import com.pmease.gitop.core.manager.ProjectManager; +import com.pmease.gitop.core.model.Project; +import com.pmease.gitop.core.model.User; +import com.pmease.gitop.web.common.form.FeedbackPanel; +import com.pmease.gitop.web.common.form.flatcheckbox.FlatCheckBoxElement; +import com.pmease.gitop.web.common.form.textfield.TextFieldElement; +import com.pmease.gitop.web.model.ProjectModel; +import com.pmease.gitop.web.page.AbstractLayoutPage; +import com.pmease.gitop.web.page.PageSpec; + +@SuppressWarnings("serial") +@RequiresUser +public class CreateProjectPage extends AbstractLayoutPage { + + @Override + protected String getPageTitle() { + return "Create a new project"; + } + + @Override + protected void onPageInitialize() { + super.onPageInitialize(); + + final IModel projectModel = new ProjectModel(new Project()); + Form form = new Form("form", projectModel); + add(form); + + form.add(new FeedbackPanel("feedback")); + form.add(new TextFieldElement("name", "Project Name", + new PropertyModel(projectModel, "name")) + .add(new PropertyValidator()) + .add(new IValidator() { + + @Override + public void validate(IValidatable validatable) { + String name = validatable.getValue(); + User owner = User.getCurrent(); + + for (Project each : owner.getRepositories()) { + if (each.getName().equalsIgnoreCase(name)) { + validatable.error(new ValidationError().setMessage("This project already exists")); + return; + } + } + } + + })); + + form.add(new TextFieldElement("description", "Description", + new PropertyModel(projectModel, "description")) + .add(new PropertyValidator()) + .setRequired(false)); + + form.add(new FlatCheckBoxElement("public", "Public Accessible", + new PropertyModel(projectModel, "publiclyAccessible"), + Model.of("Anyone can browse and pull this repository"))); + + form.add(new AjaxButton("submit", form) { + @Override + protected void onError(AjaxRequestTarget target, Form form) { + target.add(form); + } + + @Override + protected void onSubmit(AjaxRequestTarget target, Form form) { + Project project = projectModel.getObject(); + project.setOwner(User.getCurrent()); + Gitop.getInstance(ProjectManager.class).save(project); + setResponsePage(ProjectHomePage.class, PageSpec.forProject(project)); + } + }); + } +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/project/ProjectAdminPage.html b/gitop.web/src/main/java/com/pmease/gitop/web/page/project/ProjectAdminPage.html new file mode 100644 index 0000000000..25fa8688af --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/project/ProjectAdminPage.html @@ -0,0 +1,5 @@ + + +

    Project Administration

    +
    + \ No newline at end of file diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/project/ProjectAdminPage.java b/gitop.web/src/main/java/com/pmease/gitop/web/page/project/ProjectAdminPage.java new file mode 100644 index 0000000000..6bbf95a8a5 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/project/ProjectAdminPage.java @@ -0,0 +1,13 @@ +package com.pmease.gitop.web.page.project; + +import com.pmease.gitop.web.page.AbstractLayoutPage; + +@SuppressWarnings("serial") +public class ProjectAdminPage extends AbstractLayoutPage { + + @Override + protected String getPageTitle() { + return "Administration - {Project}"; + } + +} diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/page/project/ProjectHomePage.java b/gitop.web/src/main/java/com/pmease/gitop/web/page/project/ProjectHomePage.java index 262455d866..c6387cfdfc 100644 --- a/gitop.web/src/main/java/com/pmease/gitop/web/page/project/ProjectHomePage.java +++ b/gitop.web/src/main/java/com/pmease/gitop/web/page/project/ProjectHomePage.java @@ -1,6 +1,6 @@ package com.pmease.gitop.web.page.project; -import org.apache.shiro.authz.annotation.RequiresAuthentication; +import org.apache.shiro.SecurityUtils; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; @@ -10,10 +10,11 @@ import com.pmease.commons.util.GeneralException; import com.pmease.gitop.core.Gitop; import com.pmease.gitop.core.manager.ProjectManager; import com.pmease.gitop.core.model.Project; +import com.pmease.gitop.core.permission.ObjectPermission; import com.pmease.gitop.web.page.AbstractLayoutPage; +import com.pmease.gitop.web.page.PageSpec; @SuppressWarnings("serial") -@RequiresAuthentication public class ProjectHomePage extends AbstractLayoutPage { private final IModel projectModel; @@ -24,8 +25,8 @@ public class ProjectHomePage extends AbstractLayoutPage { } public ProjectHomePage(PageParameters params) { - String userName = params.get("user").toString(); - String projectName = params.get("project").toString(); + String userName = params.get(PageSpec.USER).toString(); + String projectName = params.get(PageSpec.PROJECT).toString(); if (projectName.endsWith(".git")) projectName = projectName.substring(0, projectName.length() - ".git".length()); @@ -47,6 +48,11 @@ public class ProjectHomePage extends AbstractLayoutPage { }; } + @Override + protected boolean isPermitted() { + return SecurityUtils.getSubject().isPermitted(ObjectPermission.ofProjectRead(getProject())); + } + @Override protected void onPageInitialize() { super.onPageInitialize(); @@ -65,13 +71,4 @@ public class ProjectHomePage extends AbstractLayoutPage { projectModel.detach(); super.detachModels(); } - - public static PageParameters paramsOf(Project project) { - PageParameters params = new PageParameters(); - params.set("user", project.getOwner().getName()); - params.set("project", project.getName()); - - return params; - } - } diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/shiro/LoginPage.java b/gitop.web/src/main/java/com/pmease/gitop/web/shiro/LoginPage.java index 8b29ecb954..92a806868f 100644 --- a/gitop.web/src/main/java/com/pmease/gitop/web/shiro/LoginPage.java +++ b/gitop.web/src/main/java/com/pmease/gitop/web/shiro/LoginPage.java @@ -17,7 +17,7 @@ import com.pmease.gitop.web.common.form.checkbox.CheckBoxElement; import com.pmease.gitop.web.common.form.flatcheckbox.FlatCheckBoxElement; import com.pmease.gitop.web.page.AbstractLayoutPage; import com.pmease.gitop.web.page.PageSpec; -import com.pmease.gitop.web.page.account.AccountHomePage; +import com.pmease.gitop.web.page.account.home.AccountHomePage; import com.pmease.gitop.web.util.WicketUtils; @SuppressWarnings("serial") diff --git a/gitop.web/src/main/java/com/pmease/gitop/web/util/DateUtils.java b/gitop.web/src/main/java/com/pmease/gitop/web/util/DateUtils.java new file mode 100644 index 0000000000..3657f13ce8 --- /dev/null +++ b/gitop.web/src/main/java/com/pmease/gitop/web/util/DateUtils.java @@ -0,0 +1,158 @@ +package com.pmease.gitop.web.util; + +import java.util.Calendar; +import java.util.Date; + +import org.apache.commons.lang3.time.DurationFormatUtils; +import org.joda.time.DateTime; +import org.joda.time.DateTimeConstants; +import org.joda.time.DateTimeZone; +import org.joda.time.Days; +import org.joda.time.Duration; +import org.joda.time.Hours; +import org.joda.time.Period; +import org.joda.time.PeriodType; +import org.joda.time.chrono.ISOChronology; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.PeriodFormatter; +import org.joda.time.format.PeriodFormatterBuilder; + +import com.google.common.base.Preconditions; +import com.pmease.gitop.web.Constants; + +public class DateUtils extends org.apache.commons.lang3.time.DateUtils { + + public static final String AGE = "age"; + + static enum DurationUnit { + year, month, week, day, hour, minute, second + } + + static String formatAge(DateTime dtBefore, DateTime dtNow) { + return formatAge(dtBefore, dtNow, Constants.DATETIME_FORMAT); + } + + static String formatAge(DateTime dtBefore, DateTime dtNow, + String fullDateFormat) { + Preconditions.checkArgument(dtBefore != null && dtNow != null); + + Period period = new Period(dtBefore, dtNow); + + int years = period.getYears(); + int months = period.getMonths(); + int weeks = period.getWeeks(); + int days = Days.daysBetween(dtBefore.toDateMidnight(), + dtNow.toDateMidnight()).getDays(); + + if (years > 0 || months > 0 || weeks > 0 || days > 6) { + return DateUtils.formatDate(dtBefore.toDate(), fullDateFormat); + } + + if (days > 1) { + return formatDurationPart(days, DurationUnit.day); + } + + // acutal hours + int hours = Hours.hoursBetween( + dtBefore.hourOfDay().roundHalfFloorCopy(), + dtNow.hourOfDay().roundHalfFloorCopy()).getHours(); + + if ((hours >= DateTimeConstants.HOURS_PER_DAY) + || (days == 1 && hours > 12)) { + return formatDurationPart(days, DurationUnit.day); + } + + if (hours > 0) { + return formatDurationPart(hours, DurationUnit.hour); + } + + int minutes = period.getMinutes(); + if (minutes > 0) { + return formatDurationPart(minutes, DurationUnit.minute); + } + return formatDurationPart(period.getSeconds(), DurationUnit.second); + } + + private static String formatDurationPart(int dur, DurationUnit unit) { + if (unit == DurationUnit.second && dur < 5) { // < 30 seconds + return "just now"; + } + + StringBuffer sb = new StringBuffer(); + sb.append("about ").append(dur).append(" ").append(unit); + ; + if (dur > 1) { + sb.append("s"); + } + sb.append(" ago"); + + return sb.toString(); + } + + public static String formatAge(Date date) { + return formatAge(new DateTime(date), DateTime.now()); + } + + public static String formatDate(Date date) { + return formatDate(date, Constants.DATE_FORMAT); + } + + public static String formatDateTime(Date date) { + return formatDate(date, Constants.DATETIME_FORMAT); + } + + public static String formatDate(Date date, String pattern) { + return DateTimeFormat.forPattern(pattern) + .withZone(DateTimeZone.UTC) + .print(new DateTime(date)); + } + + public static String formatDuration(long durationMillis) { + if (durationMillis < 1000) { + return durationMillis + " ms"; + } else { + return DurationFormatUtils.formatDurationWords(durationMillis, + true, true); + } + } + + public static String formatDurationShortWords(long durationMills) { + if (durationMills < DateTimeConstants.MILLIS_PER_SECOND) { + return durationMills + " ms"; + } else { + Duration duration = new Duration(durationMills); + return shortWordFormatter.print(duration.toPeriod( + PeriodType.yearMonthDayTime(), + ISOChronology.getInstanceUTC())); + } + } + + public static void main(String[] argv) { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, 2012); + cal.set(Calendar.MONTH, 9); // Oct. + cal.set(Calendar.DAY_OF_MONTH, 21); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.setTimeZone(DateTimeZone.forID("America/Sao_Paulo").toTimeZone()); + DateTime dt = new DateTime(cal); + try { + dt.withMillisOfDay(0); + } catch (IllegalArgumentException e) { + // Illegal instant due to time zone offset transition + e.printStackTrace(); + } + + System.out.println(dt.toDateMidnight()); + } + + static PeriodFormatter shortWordFormatter = new PeriodFormatterBuilder() + .appendDays().appendSuffix("d").appendSeparator(", ").appendHours() + .appendSuffix("h").appendSeparator(":").appendMinutes() + .appendSuffix("m").appendSeparator(":").appendSeconds() + .appendSuffix("s") + // .appendSeparator(", ") + // .appendMillis() + .toFormatter(); +} \ No newline at end of file