From d55805ba4ef77a56a25e28eaf469518964feb056 Mon Sep 17 00:00:00 2001 From: robin shen Date: Thu, 9 Nov 2017 20:46:24 +0800 Subject: [PATCH] Add license management --- .../server/git/GitPreReceiveCallback.java | 28 ++- .../gitplex/server/manager/ConfigManager.java | 6 + .../manager/impl/DefaultConfigManager.java | 29 +++ .../manager/impl/DefaultDataManager.java | 5 + .../server/migration/DatabaseMigrator.java | 18 ++ .../java/com/gitplex/server/model/Config.java | 2 +- web/pom.xml | 2 +- .../gitplex/server/web/GitPlexUrlMapper.java | 2 + .../web/page/admin/AdministrationPage.java | 2 + .../server/web/page/admin/administration.css | 12 + .../LicenseManagementPage.html | 52 ++++ .../LicenseManagementPage.java | 226 ++++++++++++++++++ .../server/web/page/layout/LayoutPage.html | 5 + .../server/web/page/layout/LayoutPage.java | 24 +- .../gitplex/server/web/page/layout/layout.css | 6 + 15 files changed, 413 insertions(+), 6 deletions(-) create mode 100644 web/src/main/java/com/gitplex/server/web/page/admin/licensemanagement/LicenseManagementPage.html create mode 100644 web/src/main/java/com/gitplex/server/web/page/admin/licensemanagement/LicenseManagementPage.java diff --git a/core/src/main/java/com/gitplex/server/git/GitPreReceiveCallback.java b/core/src/main/java/com/gitplex/server/git/GitPreReceiveCallback.java index 4c8f76156f..1d52263a18 100644 --- a/core/src/main/java/com/gitplex/server/git/GitPreReceiveCallback.java +++ b/core/src/main/java/com/gitplex/server/git/GitPreReceiveCallback.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.net.InetAddress; import java.util.List; +import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import javax.servlet.ServletException; @@ -17,6 +18,7 @@ import org.apache.shiro.SecurityUtils; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; +import com.gitplex.server.manager.ConfigManager; import com.gitplex.server.manager.ProjectManager; import com.gitplex.server.manager.UserManager; import com.gitplex.server.model.Project; @@ -26,9 +28,11 @@ import com.gitplex.server.model.User; import com.gitplex.server.model.support.BranchProtection; import com.gitplex.server.model.support.TagProtection; import com.gitplex.server.persistence.annotation.Sessional; +import com.gitplex.server.persistence.dao.EntityCriteria; import com.gitplex.server.security.ProjectPrivilege; import com.gitplex.server.security.permission.ProjectPermission; import com.gitplex.utils.StringUtils; +import com.gitplex.utils.license.LicenseDetail; import com.google.common.base.Preconditions; @SuppressWarnings("serial") @@ -41,18 +45,24 @@ public class GitPreReceiveCallback extends HttpServlet { private final UserManager userManager; + private final ConfigManager configManager; + @Inject - public GitPreReceiveCallback(ProjectManager projectManager, UserManager userManager) { + public GitPreReceiveCallback(ProjectManager projectManager, UserManager userManager, ConfigManager configManager) { this.projectManager = projectManager; this.userManager = userManager; + this.configManager = configManager; } - private void error(Output output, String refName, String... messages) { + private void error(Output output, @Nullable String refName, String... messages) { output.markError(); output.writeLine(); output.writeLine("*******************************************************"); output.writeLine("*"); - output.writeLine("* ERROR PUSHING REF: " + refName); + if (refName != null) + output.writeLine("* ERROR PUSHING REF: " + refName); + else + output.writeLine("* ERROR PUSHING"); output.writeLine("-------------------------------------------------------"); for (String message: messages) output.writeLine("* " + message); @@ -64,6 +74,18 @@ public class GitPreReceiveCallback extends HttpServlet { @Sessional @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + int userCount = userManager.count(EntityCriteria.of(User.class)); + int licenseLimit = LicenseDetail.FREE_LICENSE_USERS; + LicenseDetail license = configManager.getLicense(); + if (license != null && license.getRemainingDays() >= 0) { + licenseLimit += license.getLicensedUsers(); + } + if (userCount > licenseLimit) { + String message = String.format("Push is disabled as number of users in system exceeds license limit"); + response.sendError(HttpServletResponse.SC_FORBIDDEN, message); + return; + } + String clientIp = request.getHeader("X-Forwarded-For"); if (clientIp == null) clientIp = request.getRemoteAddr(); diff --git a/core/src/main/java/com/gitplex/server/manager/ConfigManager.java b/core/src/main/java/com/gitplex/server/manager/ConfigManager.java index 238debd263..17684d6c16 100644 --- a/core/src/main/java/com/gitplex/server/manager/ConfigManager.java +++ b/core/src/main/java/com/gitplex/server/manager/ConfigManager.java @@ -9,6 +9,7 @@ import com.gitplex.server.model.support.setting.SecuritySetting; import com.gitplex.server.model.support.setting.SystemSetting; import com.gitplex.server.persistence.dao.EntityManager; import com.gitplex.server.security.authenticator.Authenticator; +import com.gitplex.utils.license.LicenseDetail; public interface ConfigManager extends EntityManager { @@ -93,4 +94,9 @@ public interface ConfigManager extends EntityManager { void saveAuthenticator(@Nullable Authenticator authenticator); + @Nullable + LicenseDetail getLicense(); + + void saveLicense(@Nullable LicenseDetail license); + } diff --git a/core/src/main/java/com/gitplex/server/manager/impl/DefaultConfigManager.java b/core/src/main/java/com/gitplex/server/manager/impl/DefaultConfigManager.java index d3d8472918..540f708e80 100644 --- a/core/src/main/java/com/gitplex/server/manager/impl/DefaultConfigManager.java +++ b/core/src/main/java/com/gitplex/server/manager/impl/DefaultConfigManager.java @@ -20,6 +20,7 @@ import com.gitplex.server.persistence.dao.AbstractEntityManager; import com.gitplex.server.persistence.dao.Dao; import com.gitplex.server.persistence.dao.EntityCriteria; import com.gitplex.server.security.authenticator.Authenticator; +import com.gitplex.utils.license.LicenseDetail; import com.google.common.base.Preconditions; @Singleton @@ -37,6 +38,8 @@ public class DefaultConfigManager extends AbstractEntityManager implemen private volatile Long authenticatorConfigId; + private volatile Long licenseConfigId; + @Inject public DefaultConfigManager(Dao dao, DataManager dataManager) { super(dao); @@ -190,5 +193,31 @@ public class DefaultConfigManager extends AbstractEntityManager implemen config.setSetting(null); dao.persist(config); } + + @Sessional + @Override + public LicenseDetail getLicense() { + Config config; + if (licenseConfigId == null) { + config = getConfig(Key.LICENSE); + Preconditions.checkNotNull(config); + licenseConfigId = config.getId(); + } else { + config = load(licenseConfigId); + } + return (LicenseDetail) config.getSetting(); + } + + @Transactional + @Override + public void saveLicense(LicenseDetail license) { + Config config = getConfig(Key.LICENSE); + if (config == null) { + config = new Config(); + config.setKey(Key.LICENSE); + } + config.setSetting(license); + dao.persist(config); + } } diff --git a/core/src/main/java/com/gitplex/server/manager/impl/DefaultDataManager.java b/core/src/main/java/com/gitplex/server/manager/impl/DefaultDataManager.java index c7b27fbdba..ae340b0f2c 100644 --- a/core/src/main/java/com/gitplex/server/manager/impl/DefaultDataManager.java +++ b/core/src/main/java/com/gitplex/server/manager/impl/DefaultDataManager.java @@ -186,6 +186,11 @@ public class DefaultDataManager implements DataManager, Serializable { }); } + Config licenseKeyConfig = configManager.getConfig(Key.LICENSE); + if (licenseKeyConfig == null) { + configManager.saveLicense(null); + } + return manualConfigs; } diff --git a/core/src/main/java/com/gitplex/server/migration/DatabaseMigrator.java b/core/src/main/java/com/gitplex/server/migration/DatabaseMigrator.java index 885dafb1cf..4c569f43cd 100644 --- a/core/src/main/java/com/gitplex/server/migration/DatabaseMigrator.java +++ b/core/src/main/java/com/gitplex/server/migration/DatabaseMigrator.java @@ -387,4 +387,22 @@ public class DatabaseMigrator { } } + private void migrate11(File dataDir, Stack versions) { + for (File file: dataDir.listFiles()) { + if (file.getName().startsWith("Configs.xml")) { + VersionedDocument dom = VersionedDocument.fromFile(file); + long maxId = 0; + for (Element element: dom.getRootElement().elements()) { + Long id = Long.parseLong(element.elementTextTrim("id")); + if (maxId < id) + maxId = id; + } + Element licenseConfigElement = dom.getRootElement().addElement("com.gitplex.server.model.Config"); + licenseConfigElement.addElement("id").setText(String.valueOf(maxId+1)); + licenseConfigElement.addElement("key").setText("LICENSE"); + dom.writeToFile(file, false); + } + } + } + } diff --git a/core/src/main/java/com/gitplex/server/model/Config.java b/core/src/main/java/com/gitplex/server/model/Config.java index bcbc9e3ed6..963e5d1404 100644 --- a/core/src/main/java/com/gitplex/server/model/Config.java +++ b/core/src/main/java/com/gitplex/server/model/Config.java @@ -23,7 +23,7 @@ public class Config extends AbstractEntity { private static final long serialVersionUID = 1L; - public enum Key {SYSTEM, MAIL, BACKUP, SECURITY, AUTHENTICATOR}; + public enum Key {SYSTEM, MAIL, BACKUP, SECURITY, AUTHENTICATOR, LICENSE}; /* * Optimistic lock is necessary to ensure database integrity when update diff --git a/web/pom.xml b/web/pom.xml index 5722959d44..6c422a6451 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -53,7 +53,7 @@ org.webjars font-awesome - 4.5.0 + 4.7.0 org.webjars diff --git a/web/src/main/java/com/gitplex/server/web/GitPlexUrlMapper.java b/web/src/main/java/com/gitplex/server/web/GitPlexUrlMapper.java index a4598bb40e..c0fdb857f3 100644 --- a/web/src/main/java/com/gitplex/server/web/GitPlexUrlMapper.java +++ b/web/src/main/java/com/gitplex/server/web/GitPlexUrlMapper.java @@ -11,6 +11,7 @@ import org.apache.wicket.request.mapper.CompoundRequestMapper; import com.gitplex.server.web.page.admin.authenticator.AuthenticatorPage; import com.gitplex.server.web.page.admin.databasebackup.DatabaseBackupPage; +import com.gitplex.server.web.page.admin.licensemanagement.LicenseManagementPage; import com.gitplex.server.web.page.admin.mailsetting.MailSettingPage; import com.gitplex.server.web.page.admin.securitysetting.SecuritySettingPage; import com.gitplex.server.web.page.admin.serverinformation.ServerInformationPage; @@ -103,6 +104,7 @@ public class GitPlexUrlMapper extends CompoundRequestMapper { add(new GitPlexPageMapper("administration/settings/authenticator", AuthenticatorPage.class)); add(new GitPlexPageMapper("administration/server-log", ServerLogPage.class)); add(new GitPlexPageMapper("administration/server-information", ServerInformationPage.class)); + add(new GitPlexPageMapper("administration/license-management", LicenseManagementPage.class)); } private void addUserPages() { diff --git a/web/src/main/java/com/gitplex/server/web/page/admin/AdministrationPage.java b/web/src/main/java/com/gitplex/server/web/page/admin/AdministrationPage.java index 31e613ea22..ea192ba221 100644 --- a/web/src/main/java/com/gitplex/server/web/page/admin/AdministrationPage.java +++ b/web/src/main/java/com/gitplex/server/web/page/admin/AdministrationPage.java @@ -16,6 +16,7 @@ import com.gitplex.server.web.component.tabbable.PageTab; import com.gitplex.server.web.component.tabbable.Tabbable; import com.gitplex.server.web.page.admin.authenticator.AuthenticatorPage; import com.gitplex.server.web.page.admin.databasebackup.DatabaseBackupPage; +import com.gitplex.server.web.page.admin.licensemanagement.LicenseManagementPage; import com.gitplex.server.web.page.admin.mailsetting.MailSettingPage; import com.gitplex.server.web.page.admin.securitysetting.SecuritySettingPage; import com.gitplex.server.web.page.admin.serverinformation.ServerInformationPage; @@ -43,6 +44,7 @@ public abstract class AdministrationPage extends LayoutPage { tabs.add(new AdministrationTab("Database Backup", "fa fa-fw fa-database", DatabaseBackupPage.class)); tabs.add(new AdministrationTab("Server Log", "fa fa-fw fa-file-text-o", ServerLogPage.class)); tabs.add(new AdministrationTab("Server Information", "fa fa-fw fa-desktop", ServerInformationPage.class)); + tabs.add(new AdministrationTab("License Management", "fa fa-fw fa-vcard-o", LicenseManagementPage.class)); add(new Tabbable("tabs", tabs)); } diff --git a/web/src/main/java/com/gitplex/server/web/page/admin/administration.css b/web/src/main/java/com/gitplex/server/web/page/admin/administration.css index d9027163ce..03da80d924 100644 --- a/web/src/main/java/com/gitplex/server/web/page/admin/administration.css +++ b/web/src/main/java/com/gitplex/server/web/page/admin/administration.css @@ -27,3 +27,15 @@ color: white; } +.license-detail .properties td { + padding: 0 16px 16px 0; +} +.license-detail .properties tr:last-child td { + padding-bottom: 0; +} +.license-detail .properties td.name { + width: 200px; +} +.license-management .actions a { + margin-right: 16px; +} diff --git a/web/src/main/java/com/gitplex/server/web/page/admin/licensemanagement/LicenseManagementPage.html b/web/src/main/java/com/gitplex/server/web/page/admin/licensemanagement/LicenseManagementPage.html new file mode 100644 index 0000000000..3e3986c6bf --- /dev/null +++ b/web/src/main/java/com/gitplex/server/web/page/admin/licensemanagement/LicenseManagementPage.html @@ -0,0 +1,52 @@ + +
+
+

Current License

+
+
+
+
+
+ + +
+
+
+ + + + + + + + + + + + + +
Users
Issue Date
Expiration Date
+
+
+ +
+ + + +
+
+
\ No newline at end of file diff --git a/web/src/main/java/com/gitplex/server/web/page/admin/licensemanagement/LicenseManagementPage.java b/web/src/main/java/com/gitplex/server/web/page/admin/licensemanagement/LicenseManagementPage.java new file mode 100644 index 0000000000..3dc389ff01 --- /dev/null +++ b/web/src/main/java/com/gitplex/server/web/page/admin/licensemanagement/LicenseManagementPage.java @@ -0,0 +1,226 @@ +package com.gitplex.server.web.page.admin.licensemanagement; + +import javax.annotation.Nullable; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; +import org.apache.wicket.ajax.markup.html.form.AjaxButton; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.TextArea; +import org.apache.wicket.markup.html.link.Link; +import org.apache.wicket.markup.html.panel.Fragment; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.LoadableDetachableModel; +import org.joda.time.DateTime; + +import com.gitplex.server.GitPlex; +import com.gitplex.server.manager.ConfigManager; +import com.gitplex.server.manager.UserManager; +import com.gitplex.server.model.User; +import com.gitplex.server.persistence.dao.EntityCriteria; +import com.gitplex.server.web.WebConstants; +import com.gitplex.server.web.component.modal.ModalLink; +import com.gitplex.server.web.component.modal.ModalPanel; +import com.gitplex.server.web.page.admin.AdministrationPage; +import com.gitplex.server.web.util.ConfirmOnClick; +import com.gitplex.utils.license.LicenseDetail; + +import de.agilecoders.wicket.core.markup.html.bootstrap.common.NotificationPanel; + +@SuppressWarnings("serial") +public class LicenseManagementPage extends AdministrationPage { + + private static final String LICENSE_DETAIL_ID = "licenseDetail"; + + private String licenseKey; + + private final IModel licenseModel = new LoadableDetachableModel() { + + @Override + protected LicenseDetail load() { + return GitPlex.getInstance(ConfigManager.class).getLicense(); + } + + }; + + private final IModel userCountModel = new LoadableDetachableModel() { + + @Override + protected Integer load() { + return GitPlex.getInstance(UserManager.class).count(EntityCriteria.of(User.class)); + } + + }; + + @Override + protected void onInitialize() { + super.onInitialize(); + + LicenseDetail licenseDetail = getLicense(); + if (licenseDetail != null) { + Fragment fragment = new Fragment(LICENSE_DETAIL_ID, "licenseDetailFrag", this); + fragment.add(new Label("expiredNotice", new LoadableDetachableModel() { + + @Override + protected String load() { + return String.format("License was expired. The free %d-user license is now taking effect", + LicenseDetail.FREE_LICENSE_USERS); + } + + }).setVisible(licenseDetail.getRemainingDays()<0).setEscapeModelStrings(false)); + + fragment.add(new Label("aboutToExpireNotice", new LoadableDetachableModel() { + + @Override + protected String load() { + if (getUserCount() > LicenseDetail.FREE_LICENSE_USERS) { + return String.format("" + + "License will expire in %d days. The free %d-user license will take effect " + + "after expiration", licenseDetail.getRemainingDays(), LicenseDetail.FREE_LICENSE_USERS); + } else { + return String.format("License will expire in %d days, The free %d-user license will take " + + "effect after expiration", licenseDetail.getRemainingDays(), + LicenseDetail.FREE_LICENSE_USERS); + } + } + + }).setVisible(licenseDetail.getRemainingDays()>=0 && licenseDetail.getRemainingDays() < LicenseDetail.ABOUT_TO_EXPIRE_DAYS).setEscapeModelStrings(false)); + + String usersInfo = String.format("%d free + %d licensed (add more)", + LicenseDetail.FREE_LICENSE_USERS, licenseDetail.getLicensedUsers()); + fragment.add(new Label("users", usersInfo).setEscapeModelStrings(false)); + fragment.add(new Label("issueDate", + WebConstants.DATE_FORMATTER.print(new DateTime(licenseDetail.getIssueDate())))); + fragment.add(new Label("expirationDate", + WebConstants.DATE_FORMATTER.print(new DateTime(licenseDetail.getExpirationDate())) + " (renew)").setEscapeModelStrings(false)); + add(fragment); + } else { + if (getUserCount() > LicenseDetail.FREE_LICENSE_USERS) { + String message = String.format("" + + "Free %d-user license. Add additional users", + LicenseDetail.FREE_LICENSE_USERS); + add(new Label(LICENSE_DETAIL_ID, message).setEscapeModelStrings(false)); + } else { + String message = String.format("" + + "Free %d-user license. Add additional users", + LicenseDetail.FREE_LICENSE_USERS); + add(new Label(LICENSE_DETAIL_ID, message).setEscapeModelStrings(false)); + } + } + + add(new ModalLink("inputLicenseKey") { + + @Override + protected Component newContent(String id, ModalPanel modal) { + Fragment fragment = new Fragment(id, "licenseKeyInputFrag", LicenseManagementPage.this); + licenseKey = null; + Form form = new Form("form"); + form.add(new TextArea("licenseKey", new IModel() { + + @Override + public void detach() { + } + + @Override + public String getObject() { + return licenseKey; + } + + @Override + public void setObject(String object) { + licenseKey = object; + } + + })); + + NotificationPanel feedback = new NotificationPanel("feedback", fragment); + feedback.setOutputMarkupId(true); + form.add(feedback); + + form.add(new AjaxButton("ok") { + + @Override + protected void onSubmit(AjaxRequestTarget target, Form form) { + super.onSubmit(target, form); + if (licenseKey == null) { + error("Please input license key"); + target.add(feedback); + } else { + LicenseDetail license = LicenseDetail.decode(licenseKey); + if (license == null) { + error("Invalid license key"); + target.add(feedback); + } else { + GitPlex.getInstance(ConfigManager.class).saveLicense(license); + setResponsePage(LicenseManagementPage.class); + getSession().success("License key applied successfully"); + } + } + } + + }); + + form.add(new AjaxLink("cancel") { + + @Override + public void onClick(AjaxRequestTarget target) { + modal.close(); + } + + }); + + form.add(new AjaxLink("close") { + + @Override + public void onClick(AjaxRequestTarget target) { + modal.close(); + } + + }); + + fragment.add(form); + + return fragment; + } + + }); + + String confirmMessage = String.format("" + + "The free-%d user license will take effect after removal. Do you really want to continue?", + LicenseDetail.FREE_LICENSE_USERS); + add(new Link("removeLicense") { + + @Override + public void onClick() { + GitPlex.getInstance(ConfigManager.class).saveLicense(null); + setResponsePage(LicenseManagementPage.class); + } + + @Override + protected void onConfigure() { + super.onConfigure(); + setVisible(getLicense() != null); + } + + }.add(new ConfirmOnClick(confirmMessage))); + } + + @Nullable + private LicenseDetail getLicense() { + return licenseModel.getObject(); + } + + private int getUserCount() { + return userCountModel.getObject(); + } + + @Override + protected void onDetach() { + userCountModel.detach(); + licenseModel.detach(); + super.onDetach(); + } + +} diff --git a/web/src/main/java/com/gitplex/server/web/page/layout/LayoutPage.html b/web/src/main/java/com/gitplex/server/web/page/layout/LayoutPage.html index 524ce07a7d..8fba46b7aa 100644 --- a/web/src/main/java/com/gitplex/server/web/page/layout/LayoutPage.html +++ b/web/src/main/java/com/gitplex/server/web/page/layout/LayoutPage.html @@ -26,6 +26,11 @@
+ +
+ Git Push Disabled +
+
diff --git a/web/src/main/java/com/gitplex/server/web/page/layout/LayoutPage.java b/web/src/main/java/com/gitplex/server/web/page/layout/LayoutPage.java index 1c1d4fd270..cb78f7b608 100644 --- a/web/src/main/java/com/gitplex/server/web/page/layout/LayoutPage.java +++ b/web/src/main/java/com/gitplex/server/web/page/layout/LayoutPage.java @@ -25,9 +25,12 @@ import com.gitplex.launcher.loader.AppLoader; import com.gitplex.launcher.loader.Plugin; import com.gitplex.server.GitPlex; import com.gitplex.server.manager.ConfigManager; +import com.gitplex.server.manager.UserManager; import com.gitplex.server.model.User; +import com.gitplex.server.persistence.dao.EntityCriteria; import com.gitplex.server.security.SecurityUtils; import com.gitplex.server.web.ComponentRenderer; +import com.gitplex.server.web.behavior.TooltipBehavior; import com.gitplex.server.web.component.avatar.AvatarLink; import com.gitplex.server.web.component.floating.AlignPlacement; import com.gitplex.server.web.component.floating.FloatingPanel; @@ -43,6 +46,10 @@ import com.gitplex.server.web.page.user.UserProfilePage; import com.gitplex.server.web.websocket.PageDataChanged; import com.gitplex.server.web.websocket.TaskChangedRegion; import com.gitplex.server.web.websocket.WebSocketRegion; +import com.gitplex.utils.license.LicenseDetail; + +import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipConfig; +import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipConfig.Placement; @SuppressWarnings("serial") public abstract class LayoutPage extends BasePage { @@ -74,7 +81,22 @@ public abstract class LayoutPage extends BasePage { } }); - + + int userCount = GitPlex.getInstance(UserManager.class).count(EntityCriteria.of(User.class)); + int licenseLimit = LicenseDetail.FREE_LICENSE_USERS; + LicenseDetail license = GitPlex.getInstance(ConfigManager.class).getLicense(); + if (license != null && license.getRemainingDays()>=0) + licenseLimit += license.getLicensedUsers(); + if (userCount > licenseLimit) { + String tooltip = String.format("" + + "Git push is disabled as number of users (%d) in system exceeds license limit (%d).", + userCount, licenseLimit); + TooltipBehavior tooltipBehavior = new TooltipBehavior(Model.of(tooltip), + new TooltipConfig().withPlacement(Placement.bottom)); + head.add(new WebMarkupContainer("pushDisabled").add(tooltipBehavior)); + } else { + head.add(new WebMarkupContainer("pushDisabled").setVisible(false)); + } head.add(new ExternalLink("docLink", GitPlex.getInstance().getDocLink())); User user = getLoginUser(); diff --git a/web/src/main/java/com/gitplex/server/web/page/layout/layout.css b/web/src/main/java/com/gitplex/server/web/page/layout/layout.css index d37fb95695..67b2f5170e 100644 --- a/web/src/main/java/com/gitplex/server/web/page/layout/layout.css +++ b/web/src/main/java/com/gitplex/server/web/page/layout/layout.css @@ -13,6 +13,12 @@ html, body { text-decoration: none; color: #CCC; } +#main>.head .push-disabled { + padding-top: 6px; +} +#main>.head .push-disabled .label { + font-size: 15px; +} #main>.head>.logo .fa { font-size: 28px; color: #EEE;