├── .github ├── ISSUE_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.adoc ├── admin-template-regions.png ├── faces-config.NavData ├── menu-highlight.png ├── pom.xml ├── release-snapshots.sh ├── release.adoc ├── release.sh ├── settings.xml ├── src └── main │ ├── java │ └── com │ │ └── github │ │ └── adminfaces │ │ └── template │ │ ├── bean │ │ ├── BreadCrumbMB.java │ │ ├── LayoutMB.java │ │ └── SkinMB.java │ │ ├── config │ │ ├── AdminConfig.java │ │ └── ControlSidebarConfig.java │ │ ├── event │ │ └── AdminSystemEventListener.java │ │ ├── exception │ │ ├── AccessDeniedException.java │ │ ├── BusinessException.java │ │ ├── CustomExceptionHandler.java │ │ └── CustomExceptionHandlerFactory.java │ │ ├── i18n │ │ └── AdminUTF8Bundle.java │ │ ├── model │ │ └── BreadCrumb.java │ │ ├── security │ │ ├── LogoutMB.java │ │ └── LogoutServlet.java │ │ ├── session │ │ ├── AdminFilter.java │ │ ├── AdminServletContextListener.java │ │ └── AdminSession.java │ │ └── util │ │ ├── AdminUtils.java │ │ ├── Assert.java │ │ ├── Constants.java │ │ └── WebXml.java │ └── resources │ ├── META-INF │ ├── admin.taglib.xml │ ├── beans.xml │ ├── faces-config.xml │ ├── resources │ │ ├── 403.xhtml │ │ ├── 404.xhtml │ │ ├── 500.xhtml │ │ ├── admin-base.xhtml │ │ ├── admin-top.xhtml │ │ ├── admin.xhtml │ │ ├── admin │ │ │ ├── breadcrumb.xhtml │ │ │ ├── ripple.xhtml │ │ │ └── sidebar.xhtml │ │ ├── expired.xhtml │ │ ├── images │ │ │ └── ajaxloadingbar.gif │ │ ├── js │ │ │ ├── admin-lte.min.js │ │ │ ├── adminslide.js │ │ │ ├── admintemplate.js │ │ │ ├── bootstrap.min.js │ │ │ ├── control-sidebar.js │ │ │ ├── slideout.min.js │ │ │ └── slimscroll.min.js │ │ └── optimistic.xhtml │ └── web-fragment.xml │ ├── admin.properties │ ├── admin_ar.properties │ ├── admin_de_DE.properties │ ├── admin_en_US.properties │ ├── admin_es_MX.properties │ ├── admin_pt_BR.properties │ ├── admin_ru_RU.properties │ ├── admin_zh_CN.properties │ └── config │ └── admin-config.properties └── template-example.png /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ##### Issue Overview 2 | 3 | Tell us briefly what the problem is about. 4 | 5 | ##### Current Behaviour 6 | 7 | An image may worth a thousand words 8 | 9 | ##### Expected Behaviour 10 | 11 | An image may worth a thousand words 12 | 13 | ##### How to reproduce 14 | 15 | Please describe the steps to reproduce the issue. The more the details the more is likely the issue will be reproduced and fixed. 16 | 17 | A sample project or code may help. Can it be reproduced in [admin-starter](https://github.com/adminfaces/admin-starter)? 18 | 19 | ##### Additional Information 20 | 21 | * AdminFaces version: 22 | * PrimeFaces version: 23 | * JSF implementation: -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Admin Template build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-22.04 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up JDK 16 | uses: actions/setup-java@v2.5.0 17 | with: 18 | distribution: 'adopt' 19 | java-version: '11' 20 | - uses: actions/cache@v2.1.7 21 | with: 22 | path: ~/.m2/repository 23 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 24 | restore-keys: | 25 | ${{ runner.os }}-maven- 26 | - name: build 27 | run: mvn package 28 | 29 | release: 30 | name: Release Admin template to maven central 31 | runs-on: ubuntu-22.04 32 | needs: build 33 | env: 34 | user: ${{ secrets.user }} 35 | pass: ${{ secrets.pass }} 36 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 37 | steps: 38 | - uses: actions/checkout@v2 39 | - name: Set up JDK 40 | uses: actions/setup-java@v2.5.0 41 | with: 42 | distribution: 'adopt' 43 | java-version: '11' 44 | server-id: releases 45 | server-username: user 46 | server-password: pass 47 | gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} 48 | gpg-passphrase: GPG_PASSPHRASE 49 | - uses: actions/cache@v1 50 | with: 51 | path: ~/.m2/repository 52 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 53 | restore-keys: | 54 | ${{ runner.os }}-maven- 55 | - name: Deploy snapshots 56 | if: "contains(github.ref, 'master') && !contains(github.event.head_commit.message, 'prepare release')" 57 | run: | 58 | chmod +x "${GITHUB_WORKSPACE}/release-snapshots.sh" 59 | "${GITHUB_WORKSPACE}/release-snapshots.sh" 60 | - name: Release 61 | if: "contains(github.ref, 'master') && contains(github.event.head_commit.message, 'prepare release')" 62 | run: | 63 | chmod +x "${GITHUB_WORKSPACE}/release.sh" 64 | "${GITHUB_WORKSPACE}/release.sh" 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | 3 | .project 4 | 5 | .classpath 6 | 7 | .settings/ 8 | 9 | 10 | 11 | # IntelliJ 12 | 13 | *.iml 14 | 15 | *.ipr 16 | 17 | *.iws 18 | 19 | .idea 20 | 21 | .settings 22 | 23 | 24 | 25 | # Maven 26 | 27 | target/ 28 | 29 | 30 | 31 | #Gradle 32 | 33 | .gradle 34 | 35 | 36 | 37 | # TestNG 38 | 39 | test-output/ 40 | 41 | 42 | 43 | 44 | *.log 45 | 46 | #linux te files 47 | *~ 48 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: java 3 | jdk: openjdk8 4 | cache: 5 | directories: 6 | - "$HOME/.m2/repository" 7 | script: 8 | - if [[ $TRAVIS_PULL_REQUEST == "false" ]] && [[ $TRAVIS_BRANCH == "master" ]]; then mvn clean -U package deploy --settings settings.xml && mvn clean -U package deploy -Plegacy --settings settings.xml ; fi ; 9 | - if [[ $TRAVIS_PULL_REQUEST == "true" ]]; then mvn package ; fi ; 10 | env: 11 | global: 12 | - secure: yyc7C5k/ERqys8nHwui/ZqOTlQ/3VmZ5spGsgC7wQ1J75qJyhYp4N0UInQu4+YXR1+NVmxNX46pj//61+HRwdPBR2P9kffQeFRGRyKKKOgQcRnbmzQFvri9Ndu6yIZgATs5mfcxYqNWYaDZU/RQ/98J1oil+UxrI+RzZrlMnsENMCIJVwRPf64CYw+UgNtSFS/MBHERUVVBfmA2j5oyJSPHVIEPH4nq6UDStm3gsa4IEt40pKAD/a+Jnr3j+/I/X0QXb4A5s7OhvyfyZW2TFqd7JvzgFE/sumAgHdenFqFJWS57GAAFWvvY51R1+IA7YZXNtH7L752LVNYgswjPmsFJc2mxsNI0nJItDSfhfEa4eGrhJsXQw4me9dWXBYSCO+lwfFyTRf9cNeWoTooKwkm9emx6aOr+J8fr5GWdAkKF1FpvcEYOoa/6Dv9Xek84VFLOS1OjVrCYDlvmTYztQTcruRkSnb1c35T88JPfDt2kgIweQC7lTtHHH7viy5VkN36ovzpccpQl1SHZoiwPqlnDuTCRtwsdvF9qCCVwNyhVrMw+13nJTnzRkOGQ2cYn+f2e8ISLIyVJYtgNqctWQjtpW8qkh8y7lexevVfUgquZvXOkIDD+G5EM3DViGb3CzPE369oS3hud6k4+Sbbsg58ZTO+9uLngSGOE7apsk7rU= 13 | - secure: uNKJIfZL3BOtSutoCOf+xP+Ierpwon13JxYUz73Sol07WaGSjJXGpczR5ksInzoge5xJU85rv1Xm5puLPG4nRVJ0uQ5eh3sPzm32ECv+puVJISrY6THeVmV0y2W4hyZnK/PxQhY7maPZK9b7LMfvNoDbsuIp7OKsSKA72PVuRo7I8uItwO8E4TySYrZ81FTnrRjW5U4/08kD0rDN0Ce9Y9g5LH2J3aMhI/+9dlRnUto/6fLWvbh+TPl4Zd01QazynNoE+RDBjz5wrsOuqE3ud1ik4nl/P09nieP9TzfQ/bro/b2bxKdsTci+XDuZpO6x+Cn+oKWa0gm5azbrxHr/SAMhOHEDZhlPtnrQBqQAA+hJViN2Ek1Uc4OWVRRVCqUN2N2jpVCMu6mRnKmy0LnNq9q7z4XpCTdEzLMy+45+R6QkFaTUAglGxilTDlXDZstx5bonafzyF7EzFez748aWdcX8LTI7hOZiKzikhwsjfE7AfhfbhQoLuse4JeajrdPCONlDYX7bt7e0aD0OJxxUziSD0LNiuc960dEkqOEMGR1KJIxxs8nJCkrnPvKEwVWiAN29/dqN6pwRv8iK/U09QJngYAao5h93gjpm809ozr4Z+OaXddWO+GiR+fRf+ePf80IfFnybMdivQ+5+Ogk4oLt70LPI2SA9D7LlOb3yPLY= 14 | 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2018 Rafael Pestano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /admin-template-regions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adminfaces/admin-template/cd8bb5600464eaf2384f0a3fcabd78004919b64b/admin-template-regions.png -------------------------------------------------------------------------------- /faces-config.NavData: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /menu-highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adminfaces/admin-template/cd8bb5600464eaf2384f0a3fcabd78004919b64b/menu-highlight.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.sonatype.oss 7 | oss-parent 8 | 9 9 | 10 | 11 | 12 | com.github.adminfaces 13 | admin-template 14 | 2.1-SNAPSHOT 15 | 16 | admin-template 17 | https://github.com/adminfaces/admin-template 18 | 19 | 20 | A responsive JSF template based on Bootstrap and Admin LTE. 21 | 22 | 23 | 24 | 25 | Rafael M. Pestano 26 | rmpestanoATgmail.com 27 | 28 | 29 | 30 | 31 | 32 | The MIT License (MIT) 33 | https://opensource.org/licenses/MIT 34 | repo 35 | 36 | 37 | 38 | scm:git:https://github.com/adminfaces/admin-template.git 39 | scm:git:git@github.com:adminfaces/admin-template.git 40 | https://github.com/adminfaces/admin-template.git 41 | 42 | 43 | 44 | UTF-8 45 | false 46 | src/main/java 47 | 48 | 49 | 50 | 51 | jakarta.platform 52 | jakarta.jakartaee-web-api 53 | 10.0.0 54 | provided 55 | 56 | 57 | org.primefaces 58 | primefaces 59 | 12.0.0 60 | jakarta 61 | 62 | 63 | org.omnifaces 64 | omnifaces 65 | 4.1 66 | 67 | 68 | com.github.adminfaces 69 | admin-theme 70 | 1.6.0 71 | 72 | 73 | 74 | 75 | ${src.dir} 76 | 77 | 78 | true 79 | src/main/resources 80 | 81 | 82 | 83 | 84 | maven-compiler-plugin 85 | 3.1 86 | 87 | 1.8 88 | 1.8 89 | 90 | 91 | 92 | com.google.code.maven-replacer-plugin 93 | replacer 94 | 1.5.2 95 | 96 | 97 | prepare-package 98 | 99 | replace 100 | 101 | 102 | 103 | 104 | false 105 | false 106 | ${project.build.directory}/classes/META-INF/resources/admin-base.xhtml 107 | 108 | 109 | autoUpdate="true" 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-deploy-plugin 121 | 2.8.2 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | release 130 | 131 | 132 | 133 | org.sonatype.plugins 134 | nexus-staging-maven-plugin 135 | 1.6.8 136 | true 137 | 138 | releases 139 | https://oss.sonatype.org/ 140 | true 141 | 142 | 143 | 144 | org.apache.maven.plugins 145 | maven-source-plugin 146 | 3.2.1 147 | 148 | 149 | attach-sources 150 | 151 | jar 152 | 153 | 154 | 155 | 156 | 157 | maven-javadoc-plugin 158 | 3.1.1 159 | 160 | 161 | attach-javadocs 162 | 163 | jar 164 | 165 | 166 | 167 | 168 | 169 | maven-surefire-plugin 170 | 2.19.1 171 | 172 | true 173 | 174 | 175 | 176 | org.apache.maven.plugins 177 | maven-gpg-plugin 178 | 1.6 179 | 180 | 181 | sign-artifacts 182 | verify 183 | 184 | sign 185 | 186 | 187 | 188 | 189 | 190 | 191 | --pinentry-mode 192 | loopback 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | snapshots 205 | https://oss.sonatype.org/content/repositories/snapshots 206 | 207 | 208 | 209 | 210 | 211 | 212 | snapshots 213 | libs-snapshot 214 | https://oss.sonatype.org/content/repositories/snapshots 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /release-snapshots.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mvn clean deploy --settings settings.xml 4 | -------------------------------------------------------------------------------- /release.adoc: -------------------------------------------------------------------------------- 1 | = Release process 2 | :linkattrs: 3 | :sectanchors: 4 | :sectlink: 5 | :doctype: book 6 | :tip-caption: :bulb: 7 | :note-caption: :information_source: 8 | :important-caption: :heavy_exclamation_mark: 9 | :caution-caption: :fire: 10 | :warning-caption: :warning: 11 | 12 | . `mvn release:prepare -Dresume=false` 13 | . Choice next version 14 | . provide gpg password 15 | . done 16 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mvn clean install -Prelease && mvn deploy -Prelease -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | snapshots 13 | ${user} 14 | ${pass} 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/bean/BreadCrumbMB.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.bean; 2 | 3 | import com.github.adminfaces.template.config.AdminConfig; 4 | import com.github.adminfaces.template.model.BreadCrumb; 5 | import com.github.adminfaces.template.util.AdminUtils; 6 | import org.omnifaces.util.Faces; 7 | 8 | import jakarta.annotation.PostConstruct; 9 | import jakarta.enterprise.context.SessionScoped; 10 | import jakarta.faces.context.FacesContext; 11 | import jakarta.inject.Inject; 12 | import jakarta.inject.Named; 13 | import java.io.Serializable; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.logging.Level; 17 | import java.util.logging.Logger; 18 | 19 | import static com.github.adminfaces.template.util.Assert.has; 20 | 21 | /** 22 | * Created by rafael-pestano on 30/11/16. 23 | */ 24 | @Named 25 | @SessionScoped 26 | public class BreadCrumbMB implements Serializable { 27 | 28 | @Inject 29 | AdminConfig adminConfig; 30 | 31 | private int maxSize = 5; 32 | 33 | private List breadCrumbs = new ArrayList<>(); 34 | 35 | @PostConstruct 36 | public void init() { 37 | maxSize = adminConfig.getBreadCrumbMaxSize(); 38 | } 39 | 40 | public void add(String link, String title, Boolean clear) { 41 | if (clear != null && clear) { 42 | breadCrumbs.clear(); 43 | } 44 | if (title != null && !title.isEmpty()) { 45 | add(new BreadCrumb(link, title)); 46 | } 47 | } 48 | 49 | public void add(String link, String title, Boolean clear, Boolean shouldAdd) { 50 | if (shouldAdd != null && shouldAdd) { 51 | this.add(link, title, clear); 52 | } 53 | } 54 | 55 | public void add(BreadCrumb breadCrumb) { 56 | String link = breadCrumb.getLink(); 57 | if (!has(link)) { 58 | String pageUrl = FacesContext.getCurrentInstance().getViewRoot().getViewId(); 59 | link = pageUrl.replaceAll(pageUrl.substring(pageUrl.lastIndexOf('.') + 1), adminConfig.getPageSufix()); 60 | } 61 | 62 | if(!link.startsWith("/")) { 63 | link = "/"+link; 64 | } 65 | 66 | if(adminConfig.isExtensionLessUrls()) { 67 | int idx = link.lastIndexOf("."); 68 | if (idx != -1) { 69 | link = link.substring(0, idx); 70 | } 71 | } else if(!link.contains(".")) { 72 | link = link + "." + adminConfig.getPageSufix(); 73 | } 74 | breadCrumb.setLink(link); 75 | 76 | if (breadCrumbs.contains(breadCrumb)) { 77 | breadCrumbs.remove(breadCrumb); 78 | } 79 | 80 | if (breadCrumbs.size() == maxSize) { 81 | breadCrumbs.remove(0); 82 | } 83 | breadCrumbs.add(breadCrumb); 84 | } 85 | 86 | public void remove(BreadCrumb breadCrumb) { 87 | breadCrumbs.remove(breadCrumb); 88 | } 89 | 90 | public void clear() { 91 | breadCrumbs.clear(); 92 | } 93 | 94 | public void clearAndHome() { 95 | clear(); 96 | try { 97 | AdminUtils.redirect(Faces.getRequestBaseURL()); 98 | } catch (Exception e) { 99 | //see issue #177 100 | Logger.getLogger(getClass().getName()).log(Level.SEVERE,"Could not redirect to Home page.",e); 101 | } 102 | } 103 | 104 | public List getBreadCrumbs() { 105 | return breadCrumbs; 106 | } 107 | 108 | public void setBreadCrumbs(List breadCrumbs) { 109 | this.breadCrumbs = breadCrumbs; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/bean/LayoutMB.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.bean; 2 | 3 | import com.github.adminfaces.template.config.AdminConfig; 4 | import com.github.adminfaces.template.config.ControlSidebarConfig; 5 | import jakarta.annotation.PostConstruct; 6 | import jakarta.enterprise.context.SessionScoped; 7 | import jakarta.inject.Named; 8 | import java.io.Serializable; 9 | import java.util.logging.Logger; 10 | import jakarta.inject.Inject; 11 | import org.omnifaces.util.Faces; 12 | 13 | /** 14 | * Created by rmpestano on 07/05/17. 15 | */ 16 | @Named 17 | @SessionScoped 18 | public class LayoutMB implements Serializable { 19 | 20 | private static final Logger LOG = Logger.getLogger(LayoutMB.class.getName()); 21 | private static final String DEFAULT_TEMPLATE = "/admin.xhtml"; //template bundled in admin-template 22 | private static final String TEMPLATE_TOP = "/admin-top.xhtml"; //template bundled in admin-template 23 | private static final String APP_TEMPLATE_PATH = "/templates/template.xhtml"; // application template (left menu) 24 | private static final String APP_TEMPLATE_TOP_PATH = "/templates/template-top.xhtml"; //application template (top menu) 25 | private static final String RESOURCES_PREFFIX = ""; // template resources preffix path 26 | private static final String WEBAPP_PREFFIX = "/WEB-INF"; // template webapp preffix path 27 | 28 | private String template; 29 | private String templatePath; 30 | private String templateTopPath; 31 | private Boolean leftMenuTemplate; 32 | private Boolean fixedLayout; 33 | private Boolean boxedLayout; 34 | private Boolean expandOnHover; 35 | private Boolean sidebarCollapsed; 36 | private Boolean fixedControlSidebar; 37 | private Boolean darkControlSidebarSkin; 38 | 39 | @Inject 40 | AdminConfig adminConfig; 41 | 42 | @Inject 43 | SkinMB skinMB; 44 | 45 | @PostConstruct 46 | public void init() { 47 | this.templatePath = findTemplate(APP_TEMPLATE_PATH, DEFAULT_TEMPLATE); 48 | this.templateTopPath = findTemplate(APP_TEMPLATE_TOP_PATH, TEMPLATE_TOP); 49 | 50 | if (adminConfig.isLeftMenuTemplate()) { 51 | setDefaultTemplate(); 52 | } else { 53 | setTemplateTop(); 54 | } 55 | 56 | ControlSidebarConfig controlSidebarConfig = adminConfig.getControlSidebar(); 57 | this.fixedLayout = controlSidebarConfig.getFixedLayout(); 58 | this.boxedLayout = controlSidebarConfig.getBoxedLayout(); 59 | this.expandOnHover = controlSidebarConfig.getExpandOnHover(); 60 | this.sidebarCollapsed = controlSidebarConfig.getSidebarCollapsed(); 61 | this.fixedControlSidebar = controlSidebarConfig.getFixed(); 62 | this.darkControlSidebarSkin = controlSidebarConfig.getDarkSkin(); 63 | } 64 | 65 | public void restoreDefaults() { 66 | skinMB.init(); 67 | this.init(); 68 | } 69 | 70 | public String getTemplate() { 71 | return template; 72 | } 73 | 74 | public void setTemplate(String template) { 75 | this.template = template; 76 | } 77 | 78 | public void setTemplateTop() { 79 | template = templateTopPath; 80 | leftMenuTemplate = false; 81 | } 82 | 83 | public void setDefaultTemplate() { 84 | template = templatePath; 85 | leftMenuTemplate = true; 86 | } 87 | 88 | public Boolean getLeftMenuTemplate() { 89 | return leftMenuTemplate; 90 | } 91 | 92 | public void setLeftMenuTemplate(Boolean leftMenuTemplate) { 93 | this.leftMenuTemplate = leftMenuTemplate; 94 | } 95 | 96 | public Boolean getFixedLayout() { 97 | return fixedLayout; 98 | } 99 | 100 | public void setFixedLayout(Boolean fixedLayout) { 101 | this.fixedLayout = fixedLayout; 102 | } 103 | 104 | public Boolean getBoxedLayout() { 105 | return boxedLayout; 106 | } 107 | 108 | public void setBoxedLayout(Boolean boxedLayout) { 109 | this.boxedLayout = boxedLayout; 110 | } 111 | 112 | public Boolean getExpandOnHover() { 113 | return expandOnHover; 114 | } 115 | 116 | public void setExpandOnHover(Boolean expandOnHover) { 117 | this.expandOnHover = expandOnHover; 118 | } 119 | 120 | public Boolean getSidebarCollapsed() { 121 | return sidebarCollapsed; 122 | } 123 | 124 | public void setSidebarCollapsed(Boolean sidebarCollapsed) { 125 | this.sidebarCollapsed = sidebarCollapsed; 126 | } 127 | 128 | public Boolean getFixedControlSidebar() { 129 | return fixedControlSidebar; 130 | } 131 | 132 | public void setFixedControlSidebar(Boolean fixedControlSidebar) { 133 | this.fixedControlSidebar = fixedControlSidebar; 134 | } 135 | 136 | public Boolean getDarkControlSidebarSkin() { 137 | return darkControlSidebarSkin; 138 | } 139 | 140 | public void setDarkControlSidebarSkin(Boolean darkControlSidebarSkin) { 141 | this.darkControlSidebarSkin = darkControlSidebarSkin; 142 | } 143 | 144 | public void toggleTemplate() { 145 | if (isDefaultTemplate()) { 146 | setTemplateTop(); 147 | } else { 148 | setDefaultTemplate(); 149 | } 150 | } 151 | 152 | public boolean isDefaultTemplate() { 153 | return template != null && (template.endsWith("template.xhtml") || template.equals("admin.xhtml")); 154 | } 155 | 156 | private boolean templateExists(String templateName) { 157 | try { 158 | return Faces.getExternalContext().getResourceAsStream(templateName) != null; 159 | } catch (Exception e) { 160 | LOG.warning(String.format("Could not find application defined template in path '%s' due to following error: %s. Falling back to default admin template. See application template documentation for more details: https://github.com/adminfaces/admin-template#application-template", APP_TEMPLATE_PATH, e.getMessage())); 161 | return false; 162 | } 163 | } 164 | 165 | private String findTemplate(String appTemplatePath, String bundledPath) { 166 | String result; 167 | if (templateExists(WEBAPP_PREFFIX + appTemplatePath)) { 168 | result = WEBAPP_PREFFIX + appTemplatePath; 169 | } else if (templateExists(RESOURCES_PREFFIX + appTemplatePath)) { 170 | result = RESOURCES_PREFFIX + appTemplatePath; 171 | } else { 172 | result = bundledPath; 173 | } 174 | return result; 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/bean/SkinMB.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.bean; 2 | 3 | import com.github.adminfaces.template.config.AdminConfig; 4 | 5 | import jakarta.annotation.PostConstruct; 6 | import jakarta.enterprise.context.SessionScoped; 7 | import jakarta.inject.Inject; 8 | import jakarta.inject.Named; 9 | import java.io.Serializable; 10 | 11 | import static com.github.adminfaces.template.util.Assert.has; 12 | 13 | /** 14 | * Created by rmpestano on 07/01/17. 15 | */ 16 | @Named 17 | @SessionScoped 18 | public class SkinMB implements Serializable { 19 | 20 | private String skin; 21 | 22 | @Inject 23 | AdminConfig adminConfig; 24 | 25 | @PostConstruct 26 | public void init() { 27 | skin = adminConfig.getSkin(); 28 | if(!has(skin)) { 29 | skin = "skin-blue"; 30 | } 31 | } 32 | 33 | 34 | public void changeSkin(String skin){ 35 | this.skin = skin; 36 | } 37 | 38 | public String getSkin() { 39 | return skin; 40 | } 41 | 42 | public void setSkin(String skin) { 43 | this.skin = skin; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/config/AdminConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.config; 2 | 3 | 4 | import static com.github.adminfaces.template.util.Assert.has; 5 | import com.github.adminfaces.template.util.Constants; 6 | import java.io.IOException; 7 | import jakarta.annotation.PostConstruct; 8 | import jakarta.enterprise.context.ApplicationScoped; 9 | import jakarta.inject.Named; 10 | import java.io.InputStream; 11 | import java.io.Serializable; 12 | import java.text.DateFormat; 13 | import java.text.SimpleDateFormat; 14 | import java.util.Properties; 15 | import java.util.logging.Level; 16 | import java.util.logging.Logger; 17 | 18 | 19 | /** 20 | * Holds global application configuration 21 | * 22 | * Created by rafael-pestano on 22/11/16. 23 | */ 24 | @Named 25 | @ApplicationScoped 26 | public class AdminConfig implements Serializable { 27 | 28 | private static final long serialVersionUID = 834212776758014169L; 29 | private static final Logger log = Logger.getLogger(AdminConfig.class.getName()); 30 | 31 | private Properties adminConfigFile;//default config 32 | private Properties userConfigFile;//user defined properties 33 | private String loginPage; 34 | private String indexPage; 35 | private String dateFormat; 36 | private String templatePath; 37 | private Integer breadCrumbMaxSize; 38 | private boolean renderMessages; 39 | private boolean skipMessageDetailIfEqualsSummary; 40 | private boolean renderAjaxStatus; 41 | private boolean disableFilter; 42 | private boolean enableRipple; 43 | private boolean renderBreadCrumb; 44 | private boolean extensionLessUrls; 45 | private boolean enableSlideMenu; 46 | private String rippleElements; 47 | private String skin; 48 | private boolean autoShowNavbar; 49 | private String ignoredResources;//comma separated resources (pages or urls) to be ignored in AdminFilter 50 | private String loadingImage; 51 | private boolean renderControlSidebar; 52 | private boolean leftMenuTemplate; 53 | private boolean renderMenuSearch; 54 | private boolean renderFormAsterisks; 55 | private boolean closableLoading; 56 | private boolean enableMobileHeader; 57 | //controlsidebar 58 | private ControlSidebarConfig controlSidebar; 59 | private String pageSuffix; 60 | private boolean rippleMobileOnly; 61 | private String messagesHideTimeout; 62 | private boolean autoHideMessages; 63 | private boolean iconsEffect; 64 | 65 | @PostConstruct 66 | public void init() { 67 | ClassLoader cl = Thread.currentThread().getContextClassLoader(); 68 | adminConfigFile = new Properties(); 69 | userConfigFile = new Properties(); 70 | try (InputStream is = cl.getResourceAsStream(("admin-config.properties"))) { 71 | if (is != null) { 72 | userConfigFile.load(is); 73 | } 74 | } catch (IOException ex) { 75 | log.log(Level.WARNING,"Could not load user defined admin template properties. Falling back to default properties."); 76 | } 77 | 78 | try (InputStream isDefault = cl.getResourceAsStream(("config/admin-config.properties"))) { 79 | adminConfigFile.load(isDefault); 80 | } catch (IOException ex) { 81 | log.log(Level.SEVERE,"Could not load admin template default properties.", ex); 82 | } 83 | 84 | loadDefaults(); 85 | } 86 | 87 | protected void loadDefaults() { 88 | loginPage = getProperty("admin.loginPage"); 89 | indexPage = getProperty("admin.indexPage"); 90 | dateFormat = getProperty("admin.dateFormat"); 91 | if(!has(dateFormat)) { 92 | dateFormat = ((SimpleDateFormat)DateFormat.getDateTimeInstance()).toLocalizedPattern(); 93 | } 94 | templatePath = getProperty("admin.templatePath"); 95 | breadCrumbMaxSize = Integer.parseInt(getProperty("admin.breadcrumbSize")); 96 | renderMessages = Boolean.parseBoolean(getProperty("admin.renderMessages")); 97 | skipMessageDetailIfEqualsSummary = Boolean.parseBoolean(getProperty("admin.skipMessageDetailIfEqualsSummary")); 98 | renderAjaxStatus = Boolean.parseBoolean(getProperty("admin.renderAjaxStatus")); 99 | disableFilter = Boolean.parseBoolean(getProperty("admin.disableFilter")); 100 | enableRipple = Boolean.parseBoolean(getProperty("admin.enableRipple")); 101 | renderBreadCrumb = Boolean.parseBoolean(getProperty("admin.renderBreadCrumb")); 102 | extensionLessUrls = Boolean.parseBoolean(getProperty("admin.extensionLessUrls")); 103 | rippleElements = getProperty("admin.rippleElements"); 104 | enableSlideMenu = Boolean.parseBoolean(getProperty("admin.enableSlideMenu")); 105 | skin = getProperty("admin.skin"); 106 | autoShowNavbar = Boolean.parseBoolean(getProperty("admin.autoShowNavbar")); 107 | autoHideMessages = Boolean.parseBoolean(getProperty("admin.autoHideMessages")); 108 | iconsEffect = Boolean.parseBoolean(getProperty("admin.iconsEffect")); 109 | ignoredResources = getProperty("admin.ignoredResources"); 110 | loadingImage = getProperty("admin.loadingImage"); 111 | renderControlSidebar = Boolean.parseBoolean(getProperty("admin.renderControlSidebar")); 112 | rippleMobileOnly = Boolean.parseBoolean(getProperty("admin.rippleMobileOnly")); 113 | renderMenuSearch = Boolean.parseBoolean(getProperty("admin.renderMenuSearch")); 114 | renderFormAsterisks = Boolean.parseBoolean(getProperty("admin.renderFormAsterisks")); 115 | enableMobileHeader = Boolean.parseBoolean(getProperty("admin.enableMobileHeader")); 116 | closableLoading = Boolean.parseBoolean(getProperty("admin.closableLoading")); 117 | messagesHideTimeout = getProperty("admin.messagesHideTimeout"); 118 | leftMenuTemplate = Boolean.parseBoolean(getProperty("admin.controlSidebar.leftMenuTemplate")); 119 | boolean controlSidebarShowOnMobile = Boolean.parseBoolean(getProperty("admin.controlSidebar.showOnMobile")); 120 | boolean fixedLayout = Boolean.parseBoolean(getProperty("admin.controlSidebar.fixedLayout")); 121 | boolean boxedLayout = Boolean.parseBoolean(getProperty("admin.controlSidebar.boxedLayout")); 122 | boolean expandOnHover = Boolean.parseBoolean(getProperty("admin.controlSidebar.expandOnHover")); 123 | boolean sidebarCollapsed = Boolean.parseBoolean(getProperty("admin.controlSidebar.sidebarCollapsed")); 124 | boolean fixedControlSidebar = Boolean.parseBoolean(getProperty("admin.controlSidebar.fixed")); 125 | boolean darkControlSidebarSkin = Boolean.parseBoolean(getProperty("admin.controlSidebar.darkSkin")); 126 | controlSidebar = new ControlSidebarConfig(controlSidebarShowOnMobile,fixedLayout, boxedLayout, expandOnHover, sidebarCollapsed, fixedControlSidebar, darkControlSidebarSkin); 127 | } 128 | 129 | /** 130 | * First tries to load the property from java system properties 131 | * secondly looks for the property into user defined admin-config.properties then if 132 | * not found load defaults from admin-config.properties provided within admin-template 133 | * 134 | * @param property name 135 | * @return the property value 136 | */ 137 | private String getProperty(String property) { 138 | return has(System.getProperty(property)) ? System.getProperty(property) 139 | : has(userConfigFile.getProperty(property)) ? userConfigFile.getProperty(property) 140 | : adminConfigFile.getProperty(property); 141 | } 142 | 143 | /** 144 | * infer page suffix from index and login page configured in admin-config.properties 145 | * 146 | * If none is configured then use default suffix: 'xhtml'. 147 | * 148 | * @return the page suffix 149 | */ 150 | public String getPageSufix() { 151 | if(has(pageSuffix)) { 152 | return pageSuffix; 153 | } 154 | if(!has(indexPage) && !has(loginPage)) { 155 | pageSuffix = Constants.DEFAULT_PAGE_FORMAT; 156 | } 157 | if(has(indexPage)) { 158 | pageSuffix = indexPage.substring(indexPage.lastIndexOf('.')+1); 159 | } else { 160 | pageSuffix = indexPage.substring(loginPage.lastIndexOf('.')+1); 161 | } 162 | return pageSuffix; 163 | } 164 | 165 | public void restoreDefaults() { 166 | loadDefaults(); 167 | } 168 | 169 | 170 | public String getLoginPage() { 171 | return loginPage; 172 | } 173 | 174 | public String getIndexPage() { 175 | return indexPage; 176 | } 177 | 178 | public String getDateFormat() { 179 | return dateFormat; 180 | } 181 | 182 | public boolean isIconsEffect() { 183 | return iconsEffect; 184 | } 185 | 186 | public void setIconsEffect(boolean iconsEffect) { 187 | this.iconsEffect = iconsEffect; 188 | } 189 | 190 | public void setDateFormat(String dateFormat) { 191 | this.dateFormat = dateFormat; 192 | } 193 | 194 | public void setLoginPage(String loginPage) { 195 | this.loginPage = loginPage; 196 | } 197 | 198 | public void setIndexPage(String indexPage) { 199 | this.indexPage = indexPage; 200 | } 201 | 202 | public boolean isDisableFilter() { 203 | return disableFilter; 204 | } 205 | 206 | public void setDisableFilter(boolean disableFilter) { 207 | this.disableFilter = disableFilter; 208 | } 209 | 210 | public boolean isLeftMenuTemplate() { 211 | return leftMenuTemplate; 212 | } 213 | 214 | public boolean isRenderMenuSearch() { 215 | return renderMenuSearch; 216 | } 217 | 218 | public void setRenderMenuSearch(boolean renderMenuSearch) { 219 | this.renderMenuSearch = renderMenuSearch; 220 | } 221 | 222 | public boolean isAutoHideMessages() { 223 | return autoHideMessages; 224 | } 225 | 226 | public void setAutoHideMessages(boolean autoHideMessages) { 227 | this.autoHideMessages = autoHideMessages; 228 | } 229 | 230 | public boolean isRenderFormAsterisks() { 231 | return renderFormAsterisks; 232 | } 233 | 234 | public void setRenderFormAsterisks(boolean renderFormAsterisks) { 235 | this.renderFormAsterisks = renderFormAsterisks; 236 | } 237 | 238 | public String getMessagesHideTimeout() { 239 | return messagesHideTimeout; 240 | } 241 | 242 | public void setMessagesHideTimeout(String messagesHideTimeout) { 243 | this.messagesHideTimeout = messagesHideTimeout; 244 | } 245 | 246 | public void setLeftMenuTemplate(boolean leftMenuTemplate) { 247 | this.leftMenuTemplate = leftMenuTemplate; 248 | } 249 | 250 | public ControlSidebarConfig getControlSidebar() { 251 | return controlSidebar; 252 | } 253 | 254 | public void setControlSidebar(ControlSidebarConfig controlSidebarConfig) { 255 | this.controlSidebar = controlSidebarConfig; 256 | } 257 | 258 | public boolean isEnableMobileHeader() { 259 | return enableMobileHeader; 260 | } 261 | 262 | public void setEnableMobileHeader(boolean enableMobileHeader) { 263 | this.enableMobileHeader = enableMobileHeader; 264 | } 265 | 266 | public boolean isRippleMobileOnly() { 267 | return rippleMobileOnly; 268 | } 269 | 270 | public void setRippleMobileOnly(boolean rippleEffectMobileOnly) { 271 | this.rippleMobileOnly = rippleEffectMobileOnly; 272 | } 273 | 274 | @Deprecated 275 | /** 276 | * @deprecated use LayoutMB#template 277 | */ 278 | public String getTemplatePath() { 279 | return templatePath; 280 | } 281 | 282 | public Integer getBreadCrumbMaxSize() { 283 | return breadCrumbMaxSize; 284 | } 285 | 286 | public void setBreadCrumbMaxSize(Integer breadCrumbMaxSize) { 287 | this.breadCrumbMaxSize = breadCrumbMaxSize; 288 | } 289 | 290 | public void setTemplatePath(String templatePath) { 291 | this.templatePath = templatePath; 292 | } 293 | 294 | public boolean isRenderMessages() { 295 | return renderMessages; 296 | } 297 | 298 | public void setRenderMessages(boolean renderMessages) { 299 | this.renderMessages = renderMessages; 300 | } 301 | 302 | public boolean isSkipMessageDetailIfEqualsSummary() { 303 | return skipMessageDetailIfEqualsSummary; 304 | } 305 | 306 | public void setSkipMessageDetailIfEqualsSummary(boolean skipMessageDetailIfEqualsSummary) { 307 | this.skipMessageDetailIfEqualsSummary = skipMessageDetailIfEqualsSummary; 308 | } 309 | 310 | public boolean isRenderAjaxStatus() { 311 | return renderAjaxStatus; 312 | } 313 | 314 | public void setRenderAjaxStatus(boolean renderAjaxStatus) { 315 | this.renderAjaxStatus = renderAjaxStatus; 316 | } 317 | 318 | public boolean isEnableRipple() { 319 | return enableRipple; 320 | } 321 | 322 | public void setEnableRipple(boolean enableRipple) { 323 | this.enableRipple = enableRipple; 324 | } 325 | 326 | public boolean isRenderBreadCrumb() { 327 | return renderBreadCrumb; 328 | } 329 | 330 | public void setRenderBreadCrumb(boolean renderBreadCrumb) { 331 | this.renderBreadCrumb = renderBreadCrumb; 332 | } 333 | 334 | public boolean isExtensionLessUrls() { 335 | return extensionLessUrls; 336 | } 337 | 338 | public void setExtensionLessUrls(boolean extensionLessUrls) { 339 | this.extensionLessUrls = extensionLessUrls; 340 | } 341 | 342 | public String getRippleElements() { 343 | return rippleElements; 344 | } 345 | 346 | public void setRippleElements(String rippleElements) { 347 | this.rippleElements = rippleElements; 348 | } 349 | 350 | public boolean isEnableSlideMenu() { 351 | return enableSlideMenu; 352 | } 353 | 354 | public void setEnableSlideMenu(boolean enableSlideMenu) { 355 | this.enableSlideMenu = enableSlideMenu; 356 | } 357 | 358 | public String getSkin() { 359 | return skin; 360 | } 361 | 362 | public void setSkin(String skin) { 363 | this.skin = skin; 364 | } 365 | 366 | public boolean isAutoShowNavbar() { 367 | return autoShowNavbar; 368 | } 369 | 370 | public void setAutoShowNavbar(boolean autoShowNavbar) { 371 | this.autoShowNavbar = autoShowNavbar; 372 | } 373 | 374 | public String getIgnoredResources() { 375 | return ignoredResources; 376 | } 377 | 378 | public void setIgnoredResources(String ignoredResources) { 379 | this.ignoredResources = ignoredResources; 380 | } 381 | 382 | public String getLoadingImage() { 383 | return loadingImage; 384 | } 385 | 386 | public void setLoadingImage(String loadingImage) { 387 | this.loadingImage = loadingImage; 388 | } 389 | 390 | public boolean isRenderControlSidebar() { 391 | return renderControlSidebar; 392 | } 393 | 394 | public void setRenderControlSidebar(boolean renderControlSidebar) { 395 | this.renderControlSidebar = renderControlSidebar; 396 | } 397 | 398 | public boolean isClosableLoading() { 399 | return closableLoading; 400 | } 401 | 402 | public void setClosableLoading(boolean closableLoading) { 403 | this.closableLoading = closableLoading; 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/config/ControlSidebarConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2018 rmpestano. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.adminfaces.template.config; 25 | 26 | import java.io.Serializable; 27 | 28 | /** 29 | * Holds control sidebar initial configuration 30 | * @author rmpestano 31 | */ 32 | public class ControlSidebarConfig implements Serializable { 33 | 34 | private final Boolean showOnMobile; 35 | private final Boolean fixedLayout; 36 | private final Boolean boxedLayout; 37 | private final Boolean expandOnHover; 38 | private final Boolean sidebarCollapsed; 39 | private final Boolean fixed; 40 | private final Boolean darkSkin; 41 | 42 | 43 | public ControlSidebarConfig(boolean showOnMobile, boolean fixedLayout, boolean boxedLayout, boolean expandOnHover, boolean sidebarCollapsed, boolean fixed, boolean darkSkin) { 44 | this.showOnMobile = showOnMobile; 45 | this.fixedLayout =fixedLayout; 46 | this.boxedLayout = boxedLayout; 47 | this.expandOnHover = expandOnHover; 48 | this.sidebarCollapsed = sidebarCollapsed; 49 | this.fixed = fixed; 50 | this.darkSkin = darkSkin; 51 | } 52 | 53 | public Boolean getShowOnMobile() { 54 | return showOnMobile; 55 | } 56 | 57 | public Boolean getFixedLayout() { 58 | return fixedLayout; 59 | } 60 | 61 | public Boolean getBoxedLayout() { 62 | return boxedLayout; 63 | } 64 | 65 | public Boolean getExpandOnHover() { 66 | return expandOnHover; 67 | } 68 | 69 | public Boolean getSidebarCollapsed() { 70 | return sidebarCollapsed; 71 | } 72 | 73 | public Boolean getFixed() { 74 | return fixed; 75 | } 76 | 77 | public Boolean getDarkSkin() { 78 | return darkSkin; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/event/AdminSystemEventListener.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.event; 2 | 3 | import static com.github.adminfaces.template.util.Assert.has; 4 | 5 | import jakarta.faces.event.SystemEvent; 6 | import jakarta.faces.event.SystemEventListener; 7 | import java.util.MissingResourceException; 8 | import java.util.ResourceBundle; 9 | import java.util.logging.Level; 10 | import java.util.logging.Logger; 11 | 12 | /** 13 | * Created by rmpestano on 28/04/17. 14 | */ 15 | public class AdminSystemEventListener implements SystemEventListener { 16 | 17 | private static final Logger log = Logger.getLogger(AdminSystemEventListener.class.getName()); 18 | 19 | 20 | public boolean isListenerForSource(final Object source) { 21 | return true; 22 | } 23 | 24 | public void processEvent(final SystemEvent event) { 25 | try { 26 | ResourceBundle adminBundle = ResourceBundle.getBundle("admin"); 27 | ResourceBundle adminPersistenceBundle = null; 28 | try { 29 | adminPersistenceBundle = ResourceBundle.getBundle("admin-persistence"); 30 | } catch (MissingResourceException mre) { 31 | //intentional 32 | } 33 | boolean isLegacyTemplate = has(adminBundle.getString("admin.legacy")) && adminBundle.getString("admin.legacy").equals("true"); 34 | StringBuilder sb = new StringBuilder("Using Admin Template ") 35 | .append(adminBundle.getString("admin.version")) 36 | .append(isLegacyTemplate ? " (legacy)" : ""); 37 | if (has(adminPersistenceBundle)) { 38 | sb.append(", Admin Persistence ").append(adminPersistenceBundle.getString("admin-persistence.version")); 39 | } 40 | sb.append(" and Admin Theme ").append(ResourceBundle.getBundle("admin-theme").getString("theme.version")); 41 | log.log(Level.INFO, sb.toString()); 42 | } catch (Exception e) { 43 | log.log(Level.WARNING, "Could not get AdminFaces version.", e); 44 | } 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/exception/AccessDeniedException.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.exception; 2 | 3 | import jakarta.ejb.ApplicationException; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * Created by rmpestano on 18/02/17. A marker exception to redirect user to 8 | * 403.xhtml. See web-fragment.xml 9 | */ 10 | @ApplicationException(rollback = true) 11 | public class AccessDeniedException extends RuntimeException implements Serializable { 12 | 13 | public AccessDeniedException() { 14 | } 15 | 16 | public AccessDeniedException(Throwable cause) { 17 | super(cause); 18 | } 19 | 20 | /** 21 | * 22 | * @param msg exception message 23 | */ 24 | public AccessDeniedException(String msg) { 25 | super(msg); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.exception; 2 | 3 | import jakarta.ejb.ApplicationException; 4 | import jakarta.faces.application.FacesMessage; 5 | import jakarta.faces.context.FacesContext; 6 | import java.io.Serializable; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import static com.github.adminfaces.template.util.Assert.has; 11 | 12 | /** 13 | * Based on https://github.com/conventions/core/blob/master/src/main/java/org/conventionsframework/exception/BusinessException.java 14 | * 15 | * Application exception used to show faces messages when business exception is thrown. 16 | * 17 | * @see CustomExceptionHandler#handleBusinessException(FacesContext, BusinessException) 18 | */ 19 | @ApplicationException(rollback = true) 20 | public class BusinessException extends RuntimeException implements Serializable { 21 | 22 | private String summary; 23 | private String detail; 24 | private String fieldId; 25 | private FacesMessage.Severity severity; 26 | private List exceptionList = new ArrayList<>(); 27 | 28 | 29 | public BusinessException() { 30 | } 31 | 32 | public BusinessException(Throwable cause) { 33 | super(cause); 34 | } 35 | 36 | /** 37 | * 38 | * @param detail exception detail 39 | */ 40 | public BusinessException(String detail) { 41 | super(detail); 42 | this.detail = detail; 43 | } 44 | 45 | /** 46 | * @param summary exception summary 47 | * @param detail exception detail 48 | */ 49 | public BusinessException(String summary, String detail) { 50 | super(summary); 51 | this.detail = detail; 52 | this.summary = summary; 53 | } 54 | 55 | 56 | /** 57 | * @param summary exception summary 58 | * @param severity Faces message severity 59 | */ 60 | public BusinessException(String summary, FacesMessage.Severity severity) { 61 | super(summary); 62 | this.summary = summary; 63 | this.severity = severity; 64 | } 65 | 66 | /** 67 | * @param summary exception summary 68 | * @param severity Faces message severity 69 | * @param idToFocus view component id to scroll to when exception occurs 70 | */ 71 | public BusinessException(String summary, FacesMessage.Severity severity, String idToFocus) { 72 | super(summary); 73 | this.summary = summary; 74 | this.severity = severity; 75 | this.fieldId = idToFocus; 76 | } 77 | 78 | /** 79 | * @param summary exception summary 80 | * @param detail exception detail 81 | * @param idToFocus view component id to scroll to when exception occurs 82 | */ 83 | public BusinessException(String summary, String detail, String idToFocus) { 84 | super(summary); 85 | this.detail = detail; 86 | this.summary = summary; 87 | this.fieldId = idToFocus; 88 | } 89 | 90 | /** 91 | * @param summary exception summary 92 | * @param detail exception detail 93 | * @param severity Faces message severity 94 | */ 95 | public BusinessException(String summary, String detail, FacesMessage.Severity severity) { 96 | super(summary); 97 | this.detail = detail; 98 | this.summary = summary; 99 | this.severity = severity; 100 | } 101 | 102 | 103 | public String getDetail() { 104 | return detail; 105 | } 106 | 107 | public BusinessException setDetail(String detail) { 108 | this.detail = detail; 109 | return this; 110 | } 111 | 112 | public String getSummary() { 113 | return summary; 114 | } 115 | 116 | public BusinessException setSummary(String summary) { 117 | this.summary = summary; 118 | return this; 119 | } 120 | 121 | public FacesMessage.Severity getSeverity() { 122 | if(severity == null){ 123 | severity = FacesMessage.SEVERITY_ERROR; 124 | } 125 | return severity; 126 | } 127 | 128 | public String getFieldId() { 129 | return fieldId; 130 | } 131 | 132 | public BusinessException setFieldId(String fieldId) { 133 | this.fieldId = fieldId; 134 | return this; 135 | } 136 | 137 | public BusinessException setSeverity(FacesMessage.Severity severity) { 138 | this.severity = severity; 139 | return this; 140 | } 141 | 142 | public List getExceptionList() { 143 | return exceptionList; 144 | } 145 | 146 | public BusinessException setExceptionList(List exceptionList) { 147 | this.exceptionList = exceptionList; 148 | return this; 149 | } 150 | 151 | public BusinessException addException(BusinessException be){ 152 | exceptionList.add(be); 153 | return this; 154 | } 155 | 156 | public void build() { 157 | if(has(summary) || has(detail) || has(exceptionList)) { 158 | throw this; 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/exception/CustomExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.exception; 2 | 3 | import com.github.adminfaces.template.util.Constants; 4 | import com.github.adminfaces.template.util.WebXml; 5 | import org.omnifaces.util.Exceptions; 6 | import org.omnifaces.util.Messages; 7 | 8 | import jakarta.ejb.EJBException; 9 | import jakarta.faces.FacesException; 10 | import jakarta.faces.application.FacesMessage; 11 | import jakarta.faces.context.ExceptionHandler; 12 | import jakarta.faces.context.ExceptionHandlerWrapper; 13 | import jakarta.faces.context.FacesContext; 14 | import jakarta.faces.event.ExceptionQueuedEvent; 15 | import jakarta.faces.event.PhaseId; 16 | import jakarta.servlet.http.HttpServletRequest; 17 | import java.io.FileNotFoundException; 18 | import java.io.IOException; 19 | import java.util.HashMap; 20 | import java.util.Iterator; 21 | import java.util.Map; 22 | import java.util.logging.Level; 23 | import java.util.logging.Logger; 24 | 25 | import static com.github.adminfaces.template.util.Assert.has; 26 | 27 | /** 28 | * Based on: 29 | * https://github.com/conventions/core/blob/master/src/main/java/org/conventionsframework/exception/ConventionsExceptionHandler.java 30 | * This handler adds FacesMessages when BusinessExceptions are thrown OR sends user to error page when unexpected 31 | * exception are raised. 32 | * 33 | * @author rafael-pestano 34 | */ 35 | public class CustomExceptionHandler extends ExceptionHandlerWrapper { 36 | 37 | private static final Logger logger = Logger.getLogger(CustomExceptionHandler.class.getName()); 38 | private ExceptionHandler wrapped; 39 | 40 | public CustomExceptionHandler(ExceptionHandler exceptionHandler) { 41 | this.wrapped = exceptionHandler; 42 | } 43 | 44 | @Override 45 | public ExceptionHandler getWrapped() { 46 | return wrapped; 47 | } 48 | 49 | @Override 50 | public void handle() throws FacesException { 51 | FacesContext context = FacesContext.getCurrentInstance(); 52 | findErrorMessages(context); 53 | handleException(context); 54 | wrapped.handle(); 55 | } 56 | 57 | /** 58 | * @param context 59 | * @throws Throwable 60 | */ 61 | private void handleException(FacesContext context) { 62 | Iterator unhandledExceptionQueuedEvents = getUnhandledExceptionQueuedEvents().iterator(); 63 | 64 | if (unhandledExceptionQueuedEvents.hasNext()) { 65 | Throwable exception = unhandledExceptionQueuedEvents.next().getContext().getException(); 66 | unhandledExceptionQueuedEvents.remove(); 67 | 68 | Throwable rootCause = Exceptions.unwrap(exception); 69 | 70 | if (rootCause instanceof BusinessException) { 71 | handleBusinessException(context, (BusinessException) rootCause); 72 | return; 73 | } 74 | 75 | //send user to error page when unexpected exceptions are raised 76 | goToErrorPage(context, rootCause); 77 | } 78 | 79 | } 80 | 81 | /** 82 | * @param context 83 | * @param e 84 | * @throws Throwable 85 | */ 86 | private void goToErrorPage(FacesContext context, Throwable e) { 87 | logger.log(Level.WARNING, "", e); 88 | if (e instanceof FileNotFoundException) { 89 | throw new FacesException(e); 90 | } 91 | HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest(); 92 | Map sessionMap = context.getExternalContext().getSessionMap(); 93 | String referer = request.getHeader("Referer"); 94 | sessionMap.put("userAgent", request.getHeader("user-agent")); 95 | sessionMap.put("requestedUri", has(referer) ? referer : request.getRequestURL()); 96 | sessionMap.put("stacktrace",e); 97 | sessionMap.put("errorMessage", e != null ? e.getMessage() : ""); 98 | sessionMap.put("exceptionType", e != null ? e.getClass().getName() : null); 99 | String userIp = request.getHeader("x-forwarded-for") != null ? request.getHeader("x-forwarded-for").split(",")[0] : request.getRemoteAddr(); 100 | sessionMap.put("userIp",userIp); 101 | 102 | String errorPage = findErrorPage(e); 103 | if (!has(errorPage)) { 104 | String errorPageParam = context.getExternalContext().getInitParameter(Constants.InitialParams.ERROR_PAGE); 105 | if (!has(errorPageParam)) { 106 | errorPage = Constants.DEFAULT_ERROR_PAGE; 107 | } 108 | } 109 | try { 110 | context.getExternalContext().redirect(context.getExternalContext().getRequestContextPath() + errorPage); 111 | } catch (IOException ex) { 112 | logger.log(Level.SEVERE, "Could not redirect user to error page: " + context.getExternalContext().getRequestContextPath() + errorPage, ex); 113 | } 114 | } 115 | 116 | /** 117 | * Find error page in web.xml 118 | * 119 | * @param exception 120 | * @return 121 | */ 122 | private String findErrorPage(Throwable exception) { 123 | if (exception instanceof EJBException && exception.getCause() != null) { 124 | exception = exception.getCause(); 125 | } 126 | String errorPage = WebXml.INSTANCE.findErrorPageLocation(exception); 127 | 128 | return errorPage; 129 | } 130 | 131 | /** 132 | * @param context 133 | * @param e application business exception 134 | */ 135 | private void handleBusinessException(FacesContext context, BusinessException e) { 136 | if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) { 137 | throw new FacesException(e); 138 | } 139 | if (has(e.getExceptionList())) { 140 | for (BusinessException be : e.getExceptionList()) { 141 | addFacesMessage(be); 142 | } 143 | } else { //Single exception 144 | addFacesMessage(e); 145 | } 146 | validationFailed(context); 147 | context.renderResponse(); 148 | } 149 | 150 | private void addFacesMessage(BusinessException be) { 151 | FacesMessage facesMessage = new FacesMessage(); 152 | if (has(be.getSummary())) { 153 | facesMessage.setSummary(be.getSummary()); 154 | } 155 | if (has(be.getDetail())) { 156 | facesMessage.setDetail(be.getDetail()); 157 | } 158 | if (has(be.getSeverity())) { 159 | facesMessage.setSeverity(be.getSeverity()); 160 | } else { 161 | facesMessage.setSeverity(FacesMessage.SEVERITY_ERROR); 162 | } 163 | Messages.add(be.getFieldId(), facesMessage); 164 | } 165 | 166 | /** 167 | * Set primefaces validationFailled callback param 168 | */ 169 | private void validationFailed(FacesContext context) { 170 | Map callbackParams = (Map) context.getAttributes().get("CALLBACK_PARAMS"); 171 | if(callbackParams == null) { 172 | callbackParams = new HashMap<>(); 173 | callbackParams.put("CALLBACK_PARAMS",callbackParams); 174 | } 175 | callbackParams.put("validationFailed",true); 176 | 177 | } 178 | 179 | /** 180 | * If there is any faces message queued add PrimeFaces validation failed 181 | * 182 | * @param context 183 | */ 184 | private void findErrorMessages(FacesContext context) { 185 | if (context.getMessageList().isEmpty() || context.isValidationFailed()) { 186 | return; 187 | } 188 | for (FacesMessage msg : context.getMessageList()) { 189 | if (msg.getSeverity().equals(FacesMessage.SEVERITY_ERROR) || msg.getSeverity().equals(FacesMessage.SEVERITY_FATAL)) { 190 | validationFailed(context); 191 | break; 192 | } 193 | } 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/exception/CustomExceptionHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.exception; 2 | 3 | import jakarta.faces.context.ExceptionHandler; 4 | import jakarta.faces.context.ExceptionHandlerFactory; 5 | 6 | public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory { 7 | 8 | private final ExceptionHandlerFactory parent; 9 | 10 | public CustomExceptionHandlerFactory(final ExceptionHandlerFactory parent) { 11 | this.parent = parent; 12 | } 13 | 14 | @Override 15 | public ExceptionHandler getExceptionHandler() { 16 | return new CustomExceptionHandler(this.parent.getExceptionHandler()); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/i18n/AdminUTF8Bundle.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.i18n; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.InputStreamReader; 6 | import java.net.URL; 7 | import java.net.URLConnection; 8 | import java.util.Enumeration; 9 | import java.util.Locale; 10 | import java.util.PropertyResourceBundle; 11 | import java.util.ResourceBundle; 12 | 13 | import jakarta.faces.context.FacesContext; 14 | 15 | /** 16 | * Adds support for UTF-8 based bundles for admin i18n messages 17 | * 18 | * Taken from: https://stackoverflow.com/a/3646601/1260910 19 | * 20 | * @author rafael-pestano 21 | */ 22 | public class AdminUTF8Bundle extends ResourceBundle { 23 | 24 | protected static final String BUNDLE_NAME = "admin"; 25 | protected static final String BUNDLE_EXTENSION = "properties"; 26 | protected static final String CHARSET = "UTF-8"; 27 | protected static final Control UTF8_CONTROL = new UTF8Control(); 28 | 29 | public AdminUTF8Bundle() { 30 | Locale locale = Locale.getDefault(); 31 | if (FacesContext.getCurrentInstance() != null && FacesContext.getCurrentInstance().getViewRoot() != null) { 32 | locale = FacesContext.getCurrentInstance().getViewRoot().getLocale(); 33 | } 34 | setParent(ResourceBundle.getBundle(BUNDLE_NAME, locale, UTF8_CONTROL)); 35 | } 36 | 37 | @Override 38 | protected Object handleGetObject(String key) { 39 | return parent.getObject(key); 40 | } 41 | 42 | @Override 43 | public Enumeration getKeys() { 44 | return parent.getKeys(); 45 | } 46 | 47 | protected static class UTF8Control extends Control { 48 | 49 | public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) 50 | throws IllegalAccessException, InstantiationException, IOException { 51 | String bundleName = toBundleName(baseName, locale); 52 | String resourceName = toResourceName(bundleName, BUNDLE_EXTENSION); 53 | ResourceBundle bundle = null; 54 | InputStream stream = null; 55 | if (reload) { 56 | URL url = loader.getResource(resourceName); 57 | if (url != null) { 58 | URLConnection connection = url.openConnection(); 59 | if (connection != null) { 60 | connection.setUseCaches(false); 61 | stream = connection.getInputStream(); 62 | } 63 | } 64 | } else { 65 | stream = loader.getResourceAsStream(resourceName); 66 | } 67 | if (stream != null) { 68 | try { 69 | bundle = new PropertyResourceBundle(new InputStreamReader(stream, CHARSET)); 70 | } finally { 71 | stream.close(); 72 | } 73 | } 74 | return bundle; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/model/BreadCrumb.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.model; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by rafael-pestano on 30/11/16. 7 | */ 8 | public class BreadCrumb implements Serializable { 9 | 10 | private String link; 11 | private String title; 12 | 13 | public BreadCrumb(String link, String title) { 14 | this.link = link; 15 | this.title = title; 16 | } 17 | 18 | public String getLink() { 19 | return link; 20 | } 21 | 22 | public void setLink(String link) { 23 | this.link = link; 24 | } 25 | 26 | public String getTitle() { 27 | return title; 28 | } 29 | 30 | public void setTitle(String title) { 31 | this.title = title; 32 | } 33 | 34 | @Override 35 | public boolean equals(Object o) { 36 | if (this == o) return true; 37 | if (o == null || getClass() != o.getClass()) return false; 38 | 39 | BreadCrumb that = (BreadCrumb) o; 40 | 41 | return !(link != null ? !link.equals(that.link) : that.link != null); 42 | 43 | } 44 | 45 | @Override 46 | public int hashCode() { 47 | return link != null ? link.hashCode() : 0; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/security/LogoutMB.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.security; 2 | 3 | import com.github.adminfaces.template.config.AdminConfig; 4 | import com.github.adminfaces.template.util.Constants; 5 | import org.omnifaces.util.Faces; 6 | 7 | import jakarta.enterprise.context.RequestScoped; 8 | import jakarta.faces.context.ExternalContext; 9 | import jakarta.inject.Inject; 10 | import jakarta.inject.Named; 11 | import java.io.IOException; 12 | 13 | /** 14 | * Created by rmpestano on 03/02/17. 15 | */ 16 | @Named 17 | @RequestScoped 18 | public class LogoutMB { 19 | 20 | @Inject 21 | AdminConfig adminConfig; 22 | 23 | 24 | public void doLogout() throws IOException { 25 | String loginPage = adminConfig.getLoginPage(); 26 | if (loginPage == null || "".equals(loginPage)) { 27 | loginPage = Constants.DEFAULT_LOGIN_PAGE; 28 | } 29 | if (!loginPage.startsWith("/")) { 30 | loginPage = "/" + loginPage; 31 | } 32 | Faces.getSession().invalidate(); 33 | ExternalContext ec = Faces.getExternalContext(); 34 | ec.redirect(ec.getRequestContextPath() + loginPage); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/security/LogoutServlet.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.security; 2 | 3 | import com.github.adminfaces.template.config.AdminConfig; 4 | import com.github.adminfaces.template.util.Constants; 5 | import java.io.IOException; 6 | import jakarta.faces.context.ExternalContext; 7 | import jakarta.inject.Inject; 8 | import jakarta.servlet.ServletException; 9 | import jakarta.servlet.annotation.WebServlet; 10 | import jakarta.servlet.http.HttpServlet; 11 | import jakarta.servlet.http.HttpServletRequest; 12 | import jakarta.servlet.http.HttpServletResponse; 13 | import org.omnifaces.util.Faces; 14 | 15 | @WebServlet(name = "adminLogoutServlet", urlPatterns = "/admin-logout") 16 | public class LogoutServlet extends HttpServlet { 17 | 18 | @Inject 19 | AdminConfig adminConfig; 20 | 21 | @Override 22 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 23 | Faces.getSession().invalidate(); 24 | ExternalContext ec = Faces.getExternalContext(); 25 | ec.redirect(ec.getRequestContextPath() + getLoginPage()); 26 | } 27 | 28 | private String getLoginPage() { 29 | String loginPage = adminConfig.getLoginPage(); 30 | if (loginPage == null || "".equals(loginPage)) { 31 | loginPage = Constants.DEFAULT_LOGIN_PAGE; 32 | } 33 | if (!loginPage.startsWith("/")) { 34 | loginPage = "/" + loginPage; 35 | } 36 | return loginPage; 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/session/AdminFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.session; 2 | 3 | import static com.github.adminfaces.template.util.Assert.*; 4 | 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | import java.io.UnsupportedEncodingException; 8 | import java.net.MalformedURLException; 9 | import java.net.URL; 10 | import java.net.URLDecoder; 11 | import java.net.URLEncoder; 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.logging.Level; 16 | import java.util.logging.Logger; 17 | 18 | import java.util.regex.Matcher; 19 | import java.util.regex.Pattern; 20 | import jakarta.enterprise.inject.spi.CDI; 21 | import jakarta.inject.Inject; 22 | import jakarta.servlet.Filter; 23 | import jakarta.servlet.FilterChain; 24 | import jakarta.servlet.FilterConfig; 25 | import jakarta.servlet.ServletException; 26 | import jakarta.servlet.ServletRequest; 27 | import jakarta.servlet.ServletResponse; 28 | import jakarta.servlet.annotation.WebFilter; 29 | import jakarta.servlet.http.HttpServletRequest; 30 | import jakarta.servlet.http.HttpServletResponse; 31 | 32 | import com.github.adminfaces.template.config.AdminConfig; 33 | import com.github.adminfaces.template.util.Constants; 34 | 35 | /** 36 | * Based on https://github.com/conventions/core/blob/master/src/main/java/org/conventionsframework/filter/ConventionsFilter.java 37 | * Created by rafael-pestano on 07/01/17. 38 | * 39 | * This filter controls when user must be redirected to logon or index page 40 | * and saves current url to redirect back when session expires 41 | */ 42 | @WebFilter(urlPatterns = {"/*"}) 43 | public class AdminFilter implements Filter { 44 | 45 | private static final String FACES_RESOURCES = "/jakarta.faces.resource"; 46 | private static final Logger log = Logger.getLogger(AdminFilter.class.getName()); 47 | 48 | private boolean disableFilter; 49 | private String loginPage; 50 | private String indexPage; 51 | private String redirectPrefix; 52 | 53 | @Inject 54 | AdminSession adminSession; 55 | 56 | @Inject 57 | AdminConfig adminConfig; 58 | 59 | private final List ignoredResources = new ArrayList<>(); 60 | 61 | @Override 62 | public void init(FilterConfig filterConfig) { 63 | if(adminConfig == null) { 64 | initBeans(); 65 | } 66 | String disableAdminFilter = filterConfig.getServletContext().getInitParameter(Constants.InitialParams.DISABLE_FILTER); 67 | if (adminConfig.isDisableFilter() || has(disableAdminFilter) && Boolean.valueOf(disableAdminFilter)) { 68 | disableFilter = true; 69 | } 70 | if (!disableFilter) { 71 | try { 72 | loginPage = filterConfig.getServletContext().getInitParameter(Constants.InitialParams.LOGIN_PAGE); 73 | if (!has(loginPage)) { 74 | loginPage = has(adminConfig) ? adminConfig.getLoginPage() : Constants.DEFAULT_LOGIN_PAGE; 75 | } 76 | String errorPage = filterConfig.getServletContext().getInitParameter(Constants.InitialParams.ERROR_PAGE); 77 | if (!has(errorPage)) { 78 | errorPage = Constants.DEFAULT_ERROR_PAGE; 79 | } 80 | indexPage = filterConfig.getServletContext().getInitParameter(Constants.InitialParams.INDEX_PAGE); 81 | if (!has(indexPage)) { 82 | indexPage = has(adminConfig) ? adminConfig.getIndexPage() : Constants.DEFAULT_INDEX_PAGE; 83 | } 84 | 85 | //removes leading '/' 86 | errorPage = errorPage.startsWith("/") ? errorPage.substring(1) : errorPage; 87 | loginPage = loginPage.startsWith("/") ? loginPage.substring(1) : loginPage; 88 | indexPage = indexPage.startsWith("/") ? indexPage.substring(1) : indexPage; 89 | 90 | ignoredResources.add("/" + loginPage.substring(0, loginPage.lastIndexOf(".")));//we need leading slash for ignoredResources 91 | ignoredResources.add("/" + errorPage.substring(0, errorPage.lastIndexOf("."))); 92 | 93 | String configuredResouces = adminConfig.getIgnoredResources(); 94 | if (has(configuredResouces)) { 95 | this.ignoredResources.addAll(Arrays.asList(configuredResouces.split(","))); 96 | for (String ignoredResource : ignoredResources) { 97 | if (!ignoredResource.startsWith("/")) { //we need leading slash for ignoredResources beucase getServletPath (in this#skipResource) returns a string with leading slash 98 | ignoredResources.set(ignoredResources.indexOf(ignoredResource), "/" + ignoredResource); 99 | } 100 | } 101 | } 102 | 103 | } catch (Exception e) { 104 | log.log(Level.SEVERE, "problem initializing admin filter", e); 105 | } 106 | } 107 | 108 | } 109 | 110 | /** 111 | * Workaround for open web beans on tomcat, see https://stackoverflow.com/questions/45205472/apache-openwebbeanscdi-servlet-injection-not-working 112 | */ 113 | private void initBeans() { 114 | try { 115 | Class.forName("jakarta.enterprise.inject.spi.CDI"); 116 | adminConfig = CDI.current().select(AdminConfig.class).get(); 117 | adminSession = CDI.current().select(AdminSession.class).get(); 118 | } catch (ClassNotFoundException e) { 119 | throw new RuntimeException("Could not initialize beans via lookup.", e); 120 | } 121 | } 122 | 123 | @Override 124 | public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { 125 | if (disableFilter) { 126 | chain.doFilter(req, resp); 127 | return; 128 | } 129 | req.setCharacterEncoding("UTF-8"); 130 | resp.setCharacterEncoding("UTF-8"); 131 | HttpServletRequest request = (HttpServletRequest) req; 132 | HttpServletResponse response = (HttpServletResponse) resp; 133 | 134 | 135 | if (request.getRequestURI().equals(request.getContextPath() + "/") 136 | || (adminSession.isLoggedIn() && request.getRequestURI().endsWith(loginPage))) { 137 | response.sendRedirect(getRedirectPrefix(request) + request.getContextPath() + "/" + indexPage); 138 | return; 139 | } 140 | 141 | if (request.getRequestURI().contains(request.getContextPath() + "/public/")) { 142 | chain.doFilter(req, resp); 143 | return; 144 | } 145 | 146 | if (skipResource(request, response) || adminSession.isLoggedIn()) { 147 | if (!adminSession.isUserRedirected() && adminSession.isLoggedIn() && has(request.getHeader("Referer")) && request.getHeader("Referer").contains("?page=")) { 148 | adminSession.setUserRedirected(true); 149 | String pageFromURL = request.getContextPath() + extractPageFromURL(request.getHeader("Referer")); 150 | log.info("Redirecting user back to " + pageFromURL); 151 | response.sendRedirect(getRedirectPrefix(request) + pageFromURL); 152 | return; 153 | } 154 | try { 155 | chain.doFilter(req, resp); 156 | } catch (FileNotFoundException e) { 157 | log.log(Level.WARNING, "File not found", e); 158 | response.sendError(404); 159 | } 160 | } else { //resource not skipped (e.g a page that is not logon page) AND user not logged in 161 | redirectToLogon(request, (HttpServletResponse) resp); 162 | return; 163 | } 164 | 165 | } 166 | 167 | private String extractPageFromURL(String referer) { 168 | try { 169 | URL url = new URL(referer); 170 | String[] params = url.getQuery().split("&"); 171 | for (String param : params) { 172 | String[] split = param.split("="); 173 | if ("page".equals(split[0])) { 174 | return URLDecoder.decode(split[1], "UTF-8"); 175 | } 176 | } 177 | } catch (MalformedURLException | UnsupportedEncodingException e) { 178 | log.log(Level.WARNING, "Could not extract page from url", e); 179 | } 180 | return indexPage; 181 | } 182 | 183 | @Override 184 | public void destroy() { 185 | 186 | } 187 | 188 | /** 189 | * skips faces-resources, index, error or logon pages 190 | * 191 | * @param request 192 | * @return true if resource must be skipped by the filter false otherwise 193 | */ 194 | private boolean skipResource(HttpServletRequest request, HttpServletResponse response) { 195 | String path = request.getServletPath(); 196 | if (path.contains(".")) { 197 | path = path.substring(0, path.lastIndexOf(".")); 198 | } 199 | boolean skip = path.startsWith(FACES_RESOURCES) || shouldIgnoreResource(path) || response.getStatus() == HttpServletResponse.SC_INTERNAL_SERVER_ERROR; 200 | return skip; 201 | } 202 | 203 | private void redirectToLogon(HttpServletRequest request, HttpServletResponse response) { 204 | try { 205 | String referer = request.getHeader("Referer"); 206 | String recoveryUrlParams; 207 | //get request parameters 208 | if (has(referer) && referer.contains("?")) { 209 | recoveryUrlParams = referer.substring(referer.lastIndexOf("?") + 1); 210 | } else { 211 | recoveryUrlParams = request.getQueryString(); 212 | } 213 | //saves page where user were 214 | String requestedPage = request.getRequestURI(); 215 | StringBuilder recoveryUrl = null; 216 | if (!loginPage.equals(requestedPage) && requestedPage.contains(".")) { 217 | if (requestedPage.startsWith(request.getContextPath())) { 218 | requestedPage = requestedPage.replaceFirst(request.getContextPath(), ""); 219 | } 220 | recoveryUrl = new StringBuilder(requestedPage); 221 | if (has(recoveryUrlParams)) { 222 | recoveryUrl.append("?").append(recoveryUrlParams); 223 | } 224 | } 225 | /* 226 | if saved page is not null and is not index page then send user to logon page and save 227 | / previous page in url param named 'page' 228 | */ 229 | String redirectUrl = request.getContextPath() + "/" + loginPage + (has(recoveryUrl) && 230 | isValidRecoveryUrl(recoveryUrl) ? "?page=" + URLEncoder.encode(recoveryUrl.toString(), "UTF-8") : ""); 231 | if ("partial/ajax".equals(request.getHeader("Faces-Request"))) { 232 | //redirect on ajax request: //http://stackoverflow.com/questions/13366936/jsf-filter-not-redirecting-after-initial-redirect 233 | response.setContentType("text/xml"); 234 | response.getWriter() 235 | .append("") 236 | .printf("", redirectUrl); 237 | } else {//normal redirect 238 | response.sendRedirect(getRedirectPrefix(request) + redirectUrl); 239 | } 240 | 241 | } catch (Exception e) { 242 | log.log(Level.SEVERE, "Could not redirect to " + loginPage, e); 243 | } 244 | 245 | } 246 | 247 | /** 248 | * Skip error pages, login and index page as recovery url because it doesn't make sense redirecting user to such pages 249 | * 250 | * @param recoveryUrl 251 | * @return 252 | */ 253 | private boolean isValidRecoveryUrl(StringBuilder recoveryUrl) { 254 | String pageSuffix = adminConfig.getPageSufix(); 255 | return !recoveryUrl.toString().contains(Constants.DEFAULT_INDEX_PAGE.replace("xhtml", pageSuffix)) && !recoveryUrl.toString().contains(Constants.DEFAULT_ACCESS_DENIED_PAGE.replace("xhtml", adminConfig.getPageSufix())) 256 | && !recoveryUrl.toString().contains(Constants.DEFAULT_EXPIRED_PAGE.replace("xhtml", pageSuffix)) && !recoveryUrl.toString().contains(Constants.DEFAULT_OPTIMISTIC_PAGE.replace("xhtml", adminConfig.getPageSufix())) 257 | && !recoveryUrl.toString().contains(Constants.DEFAULT_LOGIN_PAGE.replace("xhtml", adminConfig.getPageSufix())); 258 | } 259 | 260 | /** 261 | * @param path 262 | * @return true if requested path starts with a ignored resource (configured in admin-config.properties) 263 | */ 264 | private boolean shouldIgnoreResource(String path) { 265 | for (String ignoredResource : ignoredResources) { 266 | if (path.startsWith(ignoredResource)) { 267 | return true; 268 | } 269 | } 270 | return false; 271 | } 272 | 273 | private String getRedirectPrefix(HttpServletRequest request) { 274 | if(redirectPrefix == null) { 275 | String url = request.getRequestURL().toString(); 276 | 277 | // Find the end of the scheme in the URL. This avoids false matches in the offset 278 | // calculation below. 279 | Matcher urlWithScheme = Pattern.compile("^(https?://).*").matcher(url); 280 | int urlHostIndex; 281 | if (urlWithScheme.matches()) { 282 | urlHostIndex = urlWithScheme.group(1).length(); 283 | } else { 284 | urlHostIndex = 0; 285 | } 286 | 287 | String uri = request.getRequestURI(); 288 | int offset = url.indexOf(uri, urlHostIndex); 289 | 290 | redirectPrefix = url.substring(0, offset); 291 | 292 | if(useHttps(request)) { 293 | log.log(Level.WARNING,"Changing request scheme to https."); 294 | redirectPrefix = redirectPrefix.replace("http:","https:"); 295 | } 296 | } 297 | return redirectPrefix; 298 | } 299 | 300 | private static boolean useHttps(HttpServletRequest request) { 301 | String protocolProperty = System.getProperty("admin.protocol", System.getenv("admin.protocol")); 302 | 303 | String protoHeader = request.getHeader("X-Forwarded-Proto"); 304 | return request.isSecure() || (protoHeader != null && protoHeader.toLowerCase().equals("https")) 305 | || (protocolProperty != null && protocolProperty.toLowerCase().equals("https")); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/session/AdminServletContextListener.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.session; 2 | 3 | import jakarta.servlet.ServletContextEvent; 4 | import jakarta.servlet.ServletContextListener; 5 | import java.util.logging.Logger; 6 | 7 | /** 8 | * Created by rmpestano on 28/04/17. 9 | */ 10 | @Deprecated 11 | public class AdminServletContextListener implements ServletContextListener { 12 | 13 | private static final Logger log = Logger.getLogger(AdminServletContextListener.class.getName()); 14 | 15 | @Override 16 | public void contextInitialized(ServletContextEvent servletContextEvent) { 17 | //only here for backyard compatibility 18 | } 19 | 20 | @Override 21 | public void contextDestroyed(ServletContextEvent servletContextEvent) { 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/session/AdminSession.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.session; 2 | 3 | import jakarta.enterprise.context.SessionScoped; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * Created by rmpestano on 04/02/17 8 | * Controls if user is logged in so AdminFilter can send user to login page when it is not logged in. 9 | * 10 | * By default it is always logged in so to have this feature one must @Specializes this bean and put 11 | * the security logic in IsLoggedIn method. 12 | */ 13 | @SessionScoped 14 | public class AdminSession implements Serializable { 15 | 16 | private boolean isLoggedIn = true; 17 | 18 | //avoid multiple redirects when redirecting user back to previous page after session expiration 19 | private boolean userRedirected = false; 20 | 21 | public boolean isLoggedIn(){ 22 | return isLoggedIn; 23 | } 24 | 25 | public void setIsLoggedIn(boolean isLoggedIn) { 26 | this.isLoggedIn = isLoggedIn; 27 | } 28 | 29 | public boolean isUserRedirected() { 30 | return userRedirected; 31 | } 32 | 33 | public void setUserRedirected(boolean userRedirected) { 34 | this.userRedirected = userRedirected; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/util/AdminUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.util; 2 | 3 | import jakarta.faces.context.ExternalContext; 4 | import jakarta.faces.context.FacesContext; 5 | import jakarta.servlet.http.HttpServletRequest; 6 | import java.io.IOException; 7 | import java.io.UnsupportedEncodingException; 8 | import java.net.URLEncoder; 9 | 10 | import static java.lang.String.format; 11 | import static java.nio.charset.StandardCharsets.UTF_8; 12 | 13 | public class AdminUtils { 14 | 15 | /** 16 | * 17 | * @param url the url to redirect 18 | * @param paramValues url params 19 | */ 20 | public static void redirect(String url, Object... paramValues) { 21 | redirect(FacesContext.getCurrentInstance(), url, paramValues); 22 | } 23 | 24 | /** 25 | * Copied from OmniFaces to avoid version conflicts (see https://github.com/adminfaces/admin-template/issues/177) 26 | * 27 | * @param context facesContext 28 | * @param url url to redirect 29 | * @param paramValues url params 30 | */ 31 | private static void redirect(FacesContext context, String url, Object... paramValues) { 32 | ExternalContext externalContext = context.getExternalContext(); 33 | externalContext.getFlash().setRedirect(true); // MyFaces also requires this for a redirect in current request (which is incorrect). 34 | try { 35 | externalContext.redirect(prepareRedirectURL(getRequest(context), url, paramValues)); 36 | } catch (IOException e) { 37 | throw new RuntimeException("Could not redirect to url: "+url, e); 38 | } 39 | } 40 | 41 | public static HttpServletRequest getRequest(FacesContext context) { 42 | return (HttpServletRequest) context.getExternalContext().getRequest(); 43 | } 44 | 45 | /** 46 | * Copied from OmniFaces to avoid version conflicts 47 | * 48 | * @param request 49 | * @param url 50 | * @param paramValues 51 | * @return 52 | */ 53 | private static String prepareRedirectURL(HttpServletRequest request, String url, Object... paramValues) { 54 | String redirectURL = url; 55 | 56 | if (!Assert.startsWithOneOf(url, "http://", "https://", "/")) { 57 | redirectURL = request.getContextPath() + "/" + url; 58 | } 59 | 60 | if (!Assert.has(paramValues)) { 61 | return redirectURL; 62 | } 63 | 64 | Object[] encodedParams = new Object[paramValues.length]; 65 | 66 | for (int i = 0; i < paramValues.length; i++) { 67 | Object paramValue = paramValues[i]; 68 | encodedParams[i] = (paramValue instanceof String) ? encodeURL((String) paramValue) : paramValue; 69 | } 70 | 71 | return format(redirectURL, encodedParams); 72 | } 73 | 74 | /** 75 | * Copied from OmniFaces to avoid version conflicts URL-encode the given string 76 | * using UTF-8. 77 | * 78 | * @param string The string to be URL-encoded using UTF-8. 79 | * @return The given string, URL-encoded using UTF-8, or null if 80 | * null was given. 81 | * @throws UnsupportedOperationException When this platform does not support 82 | * UTF-8. 83 | */ 84 | public static String encodeURL(String string) { 85 | if (string == null) { 86 | return null; 87 | } 88 | 89 | try { 90 | return URLEncoder.encode(string, UTF_8.name()); 91 | } catch (UnsupportedEncodingException e) { 92 | throw new UnsupportedOperationException("UTF-8 is apparently not supported on this platform.", e); 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/util/Assert.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.util; 2 | 3 | import java.io.Serializable; 4 | import java.util.Collection; 5 | import java.util.Map; 6 | 7 | /** 8 | * Created by rafael-pestano on 26/06/2015. assertions utils 9 | * 10 | */ 11 | public class Assert implements Serializable { 12 | 13 | 14 | private static final long serialVersionUID = 1L; 15 | 16 | private Assert() { 17 | 18 | } 19 | 20 | /** 21 | * @param object object to check if null 22 | * @return TRUE assertion when given objects is not null, FALSE otherwise 23 | */ 24 | public static boolean has(Object object) { 25 | return object != null; 26 | } 27 | 28 | /** 29 | * @param text text to check for characters 30 | * @return TRUE when given text has any character, FALSE otherwise 31 | */ 32 | public static boolean has(String text) { 33 | if (text != null && text.trim().length() > 0) { 34 | return true; 35 | } 36 | return false; 37 | } 38 | 39 | /** 40 | * @param textToSearch the text to search 41 | * @param substring string to check in text 42 | * @return TRUE when given text contains the given substring, FALSE otherwise 43 | */ 44 | public static boolean contains(String textToSearch, String substring) { 45 | if (textToSearch != null && textToSearch.contains(substring)) { 46 | return true; 47 | } 48 | return false; 49 | } 50 | 51 | /** 52 | * @param array array to check for presence of elements 53 | * @return TRUE when given array has elements; that is, it must not be {@code null} and must 54 | * have at least one element. FALSE otherwise 55 | */ 56 | public static boolean has(Object[] array) { 57 | if (array == null || array.length == 0) { 58 | return false; 59 | } 60 | for (Object element : array) { 61 | if (element != null) { 62 | return true; 63 | } 64 | } 65 | return false; 66 | } 67 | 68 | /** 69 | * @param collection collection to check for presence of elements 70 | * @return TRUE when given collection has elements; that is, it must not be {@code null} and 71 | * must have at least one element. @return FALSE otherwise 72 | */ 73 | public static boolean has(Collection collection) { 74 | if (collection != null && !collection.isEmpty()) { 75 | return true; 76 | } else { 77 | return false; 78 | } 79 | } 80 | 81 | /** 82 | * @param map map to check entries 83 | * @return TRUE if given Map has entries; that is, it must not be {@code null} and must have at 84 | * least one entry. Queue FALSE otherwise 85 | */ 86 | public static boolean has(Map map) { 87 | if (map == null) { 88 | return false; 89 | } 90 | if (has(map.entrySet().toArray())) { 91 | return true; 92 | } 93 | return false; 94 | } 95 | 96 | public static boolean startsWithOneOf(String string, String... prefixes) { 97 | for (String prefix : prefixes) { 98 | if (string.startsWith(prefix)) { 99 | return true; 100 | } 101 | } 102 | return false; 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/util/Constants.java: -------------------------------------------------------------------------------- 1 | package com.github.adminfaces.template.util; 2 | 3 | /** 4 | * Created by rmpestano on 07/01/17. 5 | */ 6 | public interface Constants { 7 | 8 | 9 | String DEFAULT_INDEX_PAGE = "index.xhtml"; 10 | String DEFAULT_LOGIN_PAGE = "login.xhtml"; 11 | String DEFAULT_ERROR_PAGE = "500.xhtml"; 12 | String DEFAULT_ACCESS_DENIED_PAGE = "403.xhtml"; 13 | String DEFAULT_EXPIRED_PAGE = "expired.xhtml"; 14 | String DEFAULT_OPTIMISTIC_PAGE = "optimistic.xhtml"; 15 | String DEFAULT_DATE_FORMAT = "MM/dd/yyyy HH:mm:ss"; 16 | String DEFAULT_PAGE_FORMAT = "xhtml"; 17 | 18 | interface InitialParams { 19 | String DISABLE_FILTER = "com.github.adminfaces.DISABLE_FILTER"; 20 | String LOGIN_PAGE = "com.github.adminfaces.LOGIN_PAGE"; 21 | String ERROR_PAGE = "com.github.adminfaces.ERROR_PAGE"; 22 | String INDEX_PAGE = "com.github.adminfaces.INDEX_PAGE"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/github/adminfaces/template/util/WebXml.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2018 rmpestano. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.adminfaces.template.util; 25 | 26 | import static org.omnifaces.util.Faces.getServletContext; 27 | import static org.omnifaces.util.Faces.hasContext; 28 | import static org.omnifaces.util.Utils.isEmpty; 29 | import static org.omnifaces.util.Utils.isNumber; 30 | import static org.omnifaces.util.Xml.createDocument; 31 | import static org.omnifaces.util.Xml.getNodeList; 32 | import static org.omnifaces.util.Xml.getTextContent; 33 | 34 | import java.io.IOException; 35 | import java.net.URL; 36 | import java.util.ArrayList; 37 | import java.util.Collections; 38 | import java.util.HashSet; 39 | import java.util.LinkedHashMap; 40 | import java.util.List; 41 | import java.util.Map; 42 | import java.util.Map.Entry; 43 | import java.util.Set; 44 | import java.util.concurrent.atomic.AtomicBoolean; 45 | import java.util.logging.Level; 46 | import java.util.logging.Logger; 47 | 48 | import jakarta.faces.context.FacesContext; 49 | import jakarta.faces.webapp.FacesServlet; 50 | import jakarta.servlet.Filter; 51 | import jakarta.servlet.ServletContext; 52 | import jakarta.servlet.ServletContextListener; 53 | 54 | import org.w3c.dom.Document; 55 | import org.w3c.dom.Element; 56 | import org.w3c.dom.Node; 57 | import org.w3c.dom.NodeList; 58 | import org.xml.sax.SAXException; 59 | 60 | import javax.xml.xpath.XPath; 61 | import javax.xml.xpath.XPathExpressionException; 62 | import javax.xml.xpath.XPathFactory; 63 | 64 | /** 65 | * COPIED from OmniFaces because when users update to OmniFaces 3.x they get java.lang.IncompatibleClassChangeError: Found interface org.omnifaces.config.WebXml, but class was expected 66 | * because AdminTemplate depends on OmniFaces 2.1 and the method used by template (findErrorPages) has changed in OmniFaces 3.x 67 | * 68 | *

69 | * This configuration enum parses the /WEB-INF/web.xml and all /META-INF/web-fragment files 70 | * found in the classpath and offers methods to obtain information from them which is not available by the standard 71 | * Servlet API. 72 | * 73 | *

Usage

74 | *

75 | * Some examples: 76 | *

 77 |  * // Get the <welcome-file-list> (which are essentially path-relative filenames which needs to be served when a folder is requested).
 78 |  * List<String> welcomeFiles = WebXml.INSTANCE.getWelcomeFiles();
 79 |  * 
80 | *
 81 |  * // Get a mapping of all error page locations by exception type (a key of null represents the default error page location, if any).
 82 |  * Map<Class<Throwable>, String> errorPageLocations = WebXml.INSTANCE.getErrorPageLocations();
 83 |  * 
84 | *
 85 |  * // Get the <form-login-page> (which is a context-relative URL to the login page of FORM based authentication).
 86 |  * String formLoginPage = WebXml.INSTANCE.getFormLoginPage();
 87 |  * 
88 | *
 89 |  * // Get a mapping of all <security-constraint> URL patterns and associated roles.
 90 |  * Map<String, Set<String>> securityConstraints = WebXml.INSTANCE.getSecurityConstraints();
 91 |  * 
92 | *
 93 |  * // Check if access to certain (context-relative) URL is allowed for the given role based on <security-constraint>.
 94 |  * boolean accessAllowed = WebXml.INSTANCE.isAccessAllowed("/admin.xhtml", "admin");
 95 |  * 
96 | *
 97 |  * // Get web.xml configured session timeout (in seconds).
 98 |  * int sessionTimeout = WebXml.INSTANCE.getSessionTimeout();
 99 |  * 
100 | * 101 | * @author Bauke Scholtz 102 | * @since 1.2 103 | */ 104 | public enum WebXml { 105 | 106 | // Enum singleton ------------------------------------------------------------------------------------------------- 107 | 108 | /** 109 | * Returns the lazily loaded enum singleton instance. 110 | *

111 | * Note: if this is needed in e.g. a {@link Filter} which is called before the {@link FacesServlet} is invoked, 112 | * then it won't work if the INSTANCE hasn't been referenced before. Since JSF installs a special 113 | * "init" {@link FacesContext} during startup, one option for doing this initial referencing is in a 114 | * {@link ServletContextListener}. The data this enum encapsulates will then be available even where there is no 115 | * {@link FacesContext} available. If there's no other option, then you need to manually invoke 116 | * {@link #init(ServletContext)} whereby you pass the desired {@link ServletContext}. 117 | */ 118 | INSTANCE; 119 | 120 | // Private constants ---------------------------------------------------------------------------------------------- 121 | 122 | private static final Logger logger = Logger.getLogger(WebXml.class.getName()); 123 | 124 | private static final String WEB_XML = "/WEB-INF/web.xml"; 125 | private static final String WEB_FRAGMENT_XML = "META-INF/web-fragment.xml"; 126 | 127 | private static final String XPATH_WELCOME_FILE = 128 | "welcome-file-list/welcome-file"; 129 | private static final String XPATH_EXCEPTION_TYPE = 130 | "error-page/exception-type"; 131 | private static final String XPATH_LOCATION = 132 | "location"; 133 | private static final String XPATH_ERROR_PAGE_500_LOCATION = 134 | "error-page[error-code=500]/location"; 135 | private static final String XPATH_ERROR_PAGE_DEFAULT_LOCATION = 136 | "error-page[not(error-code) and not(exception-type)]/location"; 137 | private static final String XPATH_FORM_LOGIN_PAGE = 138 | "login-config[auth-method='FORM']/form-login-config/form-login-page"; 139 | private static final String XPATH_FORM_ERROR_PAGE = 140 | "login-config[auth-method='FORM']/form-login-config/form-error-page"; 141 | private static final String XPATH_SECURITY_CONSTRAINT = 142 | "security-constraint"; 143 | private static final String XPATH_WEB_RESOURCE_URL_PATTERN = 144 | "web-resource-collection/url-pattern"; 145 | private static final String XPATH_AUTH_CONSTRAINT = 146 | "auth-constraint"; 147 | private static final String XPATH_AUTH_CONSTRAINT_ROLE_NAME = 148 | "auth-constraint/role-name"; 149 | private static final String XPATH_SESSION_TIMEOUT = 150 | "session-config/session-timeout"; 151 | 152 | private static final String ERROR_NOT_INITIALIZED = 153 | "WebXml is not initialized yet. Please use #init(ServletContext) method to manually initialize it."; 154 | private static final String ERROR_URL_MUST_START_WITH_SLASH = 155 | "URL must start with '/': '%s'"; 156 | private static final String LOG_INITIALIZATION_ERROR = 157 | "WebXml failed to initialize. Perhaps your web.xml contains a typo?"; 158 | 159 | // Properties ----------------------------------------------------------------------------------------------------- 160 | 161 | private final AtomicBoolean initialized = new AtomicBoolean(); 162 | private List welcomeFiles; 163 | private Map, String> errorPageLocations; 164 | private String formLoginPage; 165 | private String formErrorPage; 166 | private Map> securityConstraints; 167 | private int sessionTimeout; 168 | 169 | // Init ----------------------------------------------------------------------------------------------------------- 170 | 171 | /** 172 | * Perform automatic initialization whereby the servlet context is obtained from the faces context. 173 | */ 174 | private void init() { 175 | if (!initialized.get() && hasContext()) { 176 | init(getServletContext()); 177 | } 178 | } 179 | 180 | /** 181 | * Perform manual initialization with the given servlet context, if not null and not already initialized yet. 182 | * @param servletContext The servlet context to obtain the web.xml from. 183 | * @return The current {@link WebXml} instance, initialized and all. 184 | */ 185 | public WebXml init(ServletContext servletContext) { 186 | if (servletContext != null && !initialized.getAndSet(true)) { 187 | try { 188 | Element webXml = loadWebXml(servletContext).getDocumentElement(); 189 | XPath xpath = XPathFactory.newInstance().newXPath(); 190 | welcomeFiles = parseWelcomeFiles(webXml, xpath); 191 | errorPageLocations = parseErrorPageLocations(webXml, xpath); 192 | formLoginPage = parseFormLoginPage(webXml, xpath); 193 | formErrorPage = parseFormErrorPage(webXml, xpath); 194 | securityConstraints = parseSecurityConstraints(webXml, xpath); 195 | sessionTimeout = parseSessionTimeout(webXml, xpath); 196 | } 197 | catch (Exception e) { 198 | initialized.set(false); 199 | logger.log(Level.SEVERE, LOG_INITIALIZATION_ERROR, e); 200 | throw new UnsupportedOperationException(e); 201 | } 202 | } 203 | 204 | return this; 205 | } 206 | 207 | // Actions -------------------------------------------------------------------------------------------------------- 208 | 209 | /** 210 | * Find for the given exception the right error page location. Exception types are matched as per Servlet 3.0 211 | * specification 10.9.2: 212 | *

    213 | *
  • Make a first pass through all specific exception types. If an exact match is found, use its location. 214 | *
  • Else make a second pass through all specific exception types in the order as they are declared in 215 | * web.xml. If the current exception is an instance of it, then use its location. 216 | *
  • Else use the default error page location, which can be either the java.lang.Throwable or HTTP 500 or 217 | * default one. 218 | *
219 | * @param exception The exception to find the error page location for. 220 | * @return The right error page location for the given exception. 221 | */ 222 | public String findErrorPageLocation(Throwable exception) { 223 | checkInitialized(); 224 | 225 | for (Entry, String> entry : errorPageLocations.entrySet()) { 226 | if (entry.getKey() == exception.getClass()) { 227 | return entry.getValue(); 228 | } 229 | } 230 | 231 | for (Entry, String> entry : errorPageLocations.entrySet()) { 232 | if (entry.getKey() != null && entry.getKey().isInstance(exception)) { 233 | return entry.getValue(); 234 | } 235 | } 236 | 237 | return errorPageLocations.get(null); 238 | } 239 | 240 | /** 241 | * Returns true if access to the given URL is allowed for the given role. URL patterns are matched as 242 | * per Servlet 3.0 specification 12.1: 243 | *
    244 | *
  • Make a first pass through all URL patterns. If an exact match is found, then check the role on it. 245 | *
  • Else make a recursive pass through all prefix URL patterns, stepping down the URL one directory at a time, 246 | * trying to find the longest path match. If it is found, then check the role on it. 247 | *
  • Else if the last segment in the URL path contains an extension, then make a last pass through all suffix 248 | * URL patterns. If a match is found, then check the role on it. 249 | *
  • Else assume it as unprotected resource and return true. 250 | *
251 | * @param url URL to be checked for access by the given role. It must start with '/' and be context-relative. 252 | * @param role Role to be checked for access to the given URL. 253 | * @return true if access to the given URL is allowed for the given role, otherwise false. 254 | * @throws NullPointerException If given URL is null. 255 | * @throws IllegalArgumentException If given URL does not start with '/'. 256 | * @since 1.4 257 | */ 258 | public boolean isAccessAllowed(String url, String role) { 259 | checkInitialized(); 260 | 261 | if (url.charAt(0) != ('/')) { 262 | throw new IllegalArgumentException(String.format(ERROR_URL_MUST_START_WITH_SLASH, url)); 263 | } 264 | 265 | String uri = url; 266 | 267 | if (url.length() > 1 && url.charAt(url.length() - 1) == '/') { 268 | uri = url.substring(0, url.length() - 1); // Trim trailing slash. 269 | } 270 | 271 | Set roles = findExactMatchRoles(uri); 272 | 273 | if (roles == null) { 274 | roles = findPrefixMatchRoles(uri); 275 | } 276 | 277 | if (roles == null) { 278 | roles = findSuffixMatchRoles(uri); 279 | } 280 | 281 | return isRoleMatch(roles, role); 282 | } 283 | 284 | private Set findExactMatchRoles(String url) { 285 | for (Entry> entry : securityConstraints.entrySet()) { 286 | if (isExactMatch(entry.getKey(), url)) { 287 | return entry.getValue(); 288 | } 289 | } 290 | 291 | return null; 292 | } 293 | 294 | private Set findPrefixMatchRoles(String url) { 295 | for (String path = url, urlMatch = ""; !path.isEmpty(); path = path.substring(0, path.lastIndexOf('/'))) { 296 | Set roles = null; 297 | 298 | for (Entry> entry : securityConstraints.entrySet()) { 299 | if (urlMatch.length() < entry.getKey().length() && isPrefixMatch(entry.getKey(), path)) { 300 | urlMatch = entry.getKey(); 301 | roles = entry.getValue(); 302 | } 303 | } 304 | 305 | if (roles != null) { 306 | return roles; 307 | } 308 | } 309 | 310 | return null; 311 | } 312 | 313 | private Set findSuffixMatchRoles(String url) { 314 | if (url.contains(".")) { 315 | for (Entry> entry : securityConstraints.entrySet()) { 316 | if (isSuffixMatch(url, entry.getKey())) { 317 | return entry.getValue(); 318 | } 319 | } 320 | } 321 | 322 | return null; 323 | } 324 | 325 | private static boolean isExactMatch(String urlPattern, String url) { 326 | return url.equals(urlPattern.endsWith("/*") ? urlPattern.substring(0, urlPattern.length() - 2) : urlPattern); 327 | } 328 | 329 | private static boolean isPrefixMatch(String urlPattern, String url) { 330 | return urlPattern.endsWith("/*") ? url.startsWith(urlPattern.substring(0, urlPattern.length() - 2)) : false; 331 | } 332 | 333 | private static boolean isSuffixMatch(String urlPattern, String url) { 334 | return urlPattern.startsWith("*.") ? url.endsWith(urlPattern.substring(1)) : false; 335 | } 336 | 337 | private static boolean isRoleMatch(Set roles, String role) { 338 | return roles == null || roles.contains(role) || (role != null && roles.contains("*")); 339 | } 340 | 341 | // Getters -------------------------------------------------------------------------------------------------------- 342 | 343 | /** 344 | * Returns a list of all welcome files. 345 | * @return A list of all welcome files. 346 | * @since 1.4 347 | */ 348 | public List getWelcomeFiles() { 349 | checkInitialized(); 350 | return welcomeFiles; 351 | } 352 | 353 | /** 354 | * Returns a mapping of all error page locations by exception type. The default location is identified by 355 | * null key. 356 | * @return A mapping of all error page locations by exception type. 357 | */ 358 | public Map, String> getErrorPageLocations() { 359 | checkInitialized(); 360 | return errorPageLocations; 361 | } 362 | 363 | /** 364 | * Returns the location of the FORM authentication login page, or null if it is not defined. 365 | * @return The location of the FORM authentication login page, or null if it is not defined. 366 | */ 367 | public String getFormLoginPage() { 368 | checkInitialized(); 369 | return formLoginPage; 370 | } 371 | 372 | /** 373 | * Returns the location of the FORM authentication error page, or null if it is not defined. 374 | * @return The location of the FORM authentication error page, or null if it is not defined. 375 | * @since 1.8 376 | */ 377 | public String getFormErrorPage() { 378 | checkInitialized(); 379 | return formErrorPage; 380 | } 381 | 382 | /** 383 | * Returns a mapping of all security constraint URL patterns and the associated roles in the declared order. If the 384 | * roles is null, then it means that no auth constraint is been set (i.e. the resource is publicly 385 | * accessible). If the roles is empty, then it means that an empty auth constraint is been set (i.e. the resource 386 | * is in no way accessible). 387 | * @return A mapping of all security constraint URL patterns and the associated roles in the declared order. 388 | * @since 1.4 389 | */ 390 | public Map> getSecurityConstraints() { 391 | checkInitialized(); 392 | return securityConstraints; 393 | } 394 | 395 | /** 396 | * Returns the configured session timeout in minutes, or -1 if it is not defined. 397 | * @return The configured session timeout in minutes, or -1 if it is not defined. 398 | * @since 1.7 399 | */ 400 | public int getSessionTimeout() { 401 | checkInitialized(); 402 | return sessionTimeout; 403 | } 404 | 405 | private void checkInitialized() { 406 | // This init() call is performed here instead of in constructor, because WebLogic loads this enum as a CDI 407 | // managed bean (in spite of having a VetoAnnotatedTypeExtension) which in turn implicitly invokes the enum 408 | // constructor and thus causes an init while JSF context isn't fully initialized and thus the faces context 409 | // isn't available yet. Perhaps it's fixed in newer WebLogic versions. 410 | init(); 411 | 412 | if (!initialized.get()) { 413 | throw new IllegalStateException(ERROR_NOT_INITIALIZED); 414 | } 415 | } 416 | 417 | // Helpers -------------------------------------------------------------------------------------------------------- 418 | 419 | /** 420 | * Load, merge and return all web.xml and web-fragment.xml files found in the classpath 421 | * into a single {@link Document}. 422 | */ 423 | private static Document loadWebXml(ServletContext context) throws IOException, SAXException { 424 | List webXmlURLs = new ArrayList<>(); 425 | webXmlURLs.add(context.getResource(WEB_XML)); 426 | webXmlURLs.addAll(Collections.list(Thread.currentThread().getContextClassLoader().getResources(WEB_FRAGMENT_XML))); 427 | return createDocument(webXmlURLs); 428 | } 429 | 430 | /** 431 | * Create and return a list of all welcome files. 432 | */ 433 | private static List parseWelcomeFiles(Element webXml, XPath xpath) throws XPathExpressionException { 434 | NodeList welcomeFileList = getNodeList(webXml, xpath, XPATH_WELCOME_FILE); 435 | List welcomeFiles = new ArrayList<>(welcomeFileList.getLength()); 436 | 437 | for (int i = 0; i < welcomeFileList.getLength(); i++) { 438 | welcomeFiles.add(getTextContent(welcomeFileList.item(i))); 439 | } 440 | 441 | return Collections.unmodifiableList(welcomeFiles); 442 | } 443 | 444 | /** 445 | * Create and return a mapping of all error page locations by exception type found in the given document. 446 | * @throws ClassNotFoundException 447 | */ 448 | @SuppressWarnings("unchecked") // For the cast on Class. 449 | private static Map, String> parseErrorPageLocations(Element webXml, XPath xpath) throws XPathExpressionException, ClassNotFoundException { 450 | Map, String> errorPageLocations = new LinkedHashMap<>(); 451 | NodeList exceptionTypes = getNodeList(webXml, xpath, XPATH_EXCEPTION_TYPE); 452 | 453 | for (int i = 0; i < exceptionTypes.getLength(); i++) { 454 | Node node = exceptionTypes.item(i); 455 | Class exceptionClass = (Class) Class.forName(getTextContent(node)); 456 | String exceptionLocation = xpath.compile(XPATH_LOCATION).evaluate(node.getParentNode()).trim(); 457 | Class key = (exceptionClass == Throwable.class) ? null : exceptionClass; 458 | 459 | if (!errorPageLocations.containsKey(key)) { 460 | errorPageLocations.put(key, exceptionLocation); 461 | } 462 | } 463 | 464 | if (!errorPageLocations.containsKey(null)) { 465 | String defaultLocation = xpath.compile(XPATH_ERROR_PAGE_500_LOCATION).evaluate(webXml).trim(); 466 | 467 | if (isEmpty(defaultLocation)) { 468 | defaultLocation = xpath.compile(XPATH_ERROR_PAGE_DEFAULT_LOCATION).evaluate(webXml).trim(); 469 | } 470 | 471 | if (!isEmpty(defaultLocation)) { 472 | errorPageLocations.put(null, defaultLocation); 473 | } 474 | } 475 | 476 | return Collections.unmodifiableMap(errorPageLocations); 477 | } 478 | 479 | /** 480 | * Return the location of the FORM authentication login page. 481 | */ 482 | private static String parseFormLoginPage(Element webXml, XPath xpath) throws XPathExpressionException { 483 | String formLoginPage = xpath.compile(XPATH_FORM_LOGIN_PAGE).evaluate(webXml).trim(); 484 | return isEmpty(formLoginPage) ? null : formLoginPage; 485 | } 486 | 487 | /** 488 | * Return the location of the FORM authentication error page. 489 | */ 490 | private static String parseFormErrorPage(Element webXml, XPath xpath) throws XPathExpressionException { 491 | String formErrorPage = xpath.compile(XPATH_FORM_ERROR_PAGE).evaluate(webXml).trim(); 492 | return isEmpty(formErrorPage) ? null : formErrorPage; 493 | } 494 | 495 | /** 496 | * Create and return a mapping of all security constraint URL patterns and the associated roles. 497 | */ 498 | private static Map> parseSecurityConstraints(Element webXml, XPath xpath) throws XPathExpressionException { 499 | Map> securityConstraints = new LinkedHashMap<>(); 500 | NodeList constraints = getNodeList(webXml, xpath, XPATH_SECURITY_CONSTRAINT); 501 | 502 | for (int i = 0; i < constraints.getLength(); i++) { 503 | Node constraint = constraints.item(i); 504 | Set roles = null; 505 | NodeList auth = getNodeList(constraint, xpath, XPATH_AUTH_CONSTRAINT); 506 | 507 | if (auth.getLength() > 0) { 508 | NodeList authRoles = getNodeList(constraint, xpath, XPATH_AUTH_CONSTRAINT_ROLE_NAME); 509 | roles = new HashSet<>(authRoles.getLength()); 510 | 511 | for (int j = 0; j < authRoles.getLength(); j++) { 512 | roles.add(getTextContent(authRoles.item(j))); 513 | } 514 | 515 | roles = Collections.unmodifiableSet(roles); 516 | } 517 | 518 | NodeList urlPatterns = getNodeList(constraint, xpath, XPATH_WEB_RESOURCE_URL_PATTERN); 519 | 520 | for (int j = 0; j < urlPatterns.getLength(); j++) { 521 | String urlPattern = getTextContent(urlPatterns.item(j)); 522 | securityConstraints.put(urlPattern, roles); 523 | } 524 | } 525 | 526 | return Collections.unmodifiableMap(securityConstraints); 527 | } 528 | 529 | /** 530 | * Return the configured session timeout in minutes, or -1 if it is not defined. 531 | */ 532 | private static int parseSessionTimeout(Element webXml, XPath xpath) throws XPathExpressionException { 533 | String sessionTimeout = xpath.compile(XPATH_SESSION_TIMEOUT).evaluate(webXml).trim(); 534 | return isNumber(sessionTimeout) ? Integer.parseInt(sessionTimeout) : -1; 535 | } 536 | 537 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/admin.taglib.xml: -------------------------------------------------------------------------------- 1 | 4 | http://github.com/adminfaces 5 | admin 6 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/faces-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | en 12 | en_US 13 | pt_BR 14 | es_MX 15 | zh_CN 16 | ar 17 | de_DE 18 | ru_RU 19 | 20 | 21 | 22 | com.github.adminfaces.template.i18n.AdminUTF8Bundle 23 | adm 24 | 25 | 26 | com.github.adminfaces.template.event.AdminSystemEventListener 27 | jakarta.faces.event.PostConstructApplicationEvent 28 | jakarta.faces.application.Application 29 | 30 | 31 | 32 | 33 | com.github.adminfaces.template.exception.CustomExceptionHandlerFactory 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/403.xhtml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 |
10 |

#{adm['label.403.header']}

11 | 12 |
13 |

#{adm['label.403.message']}

14 | 15 |

16 | #{adm['label.go-back']} #{adm['label.or-to']} #{adm['label.previous-page']}. 18 |

19 | 20 |
21 |
22 | 23 | 24 | 25 |
26 | 27 | 28 |
29 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/404.xhtml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 |
10 |

#{adm['label.404.header']}

11 | 12 |
13 |

#{adm['label.404.message']}.

14 | 15 |

16 | #{adm['label.go-back']} #{adm['label.or-to']} #{adm['label.previous-page']}. 18 |

19 | 20 |
21 |
22 | 23 |
24 | 25 | 26 |
27 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/500.xhtml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |

#{adm['label.500.header']}

14 | 15 |
16 |

17 | #{adm['label.500.message']}. 18 |

19 | 20 |

21 | #{adm['label.go-back']} #{adm['label.or-to']} #{adm['label.previous-page']}. 23 |

24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 |
32 |
33 |

#{adm['label.500.title']}

34 |
35 |
36 |
    37 |
  • Date/time: #{of:formatDate(now, adminConfig.dateFormat)}
  • 38 |
  • User agent: #{sessionScope.userAgent}
  • 39 |
  • 40 | User IP: #{sessionScope.userIp} 41 |
  • 42 |
  • Request URI: #{sessionScope.requestedUri} 44 |
  • 45 |
  • Ajax request: #{facesContext.partialViewContext.ajaxRequest ? 'Yes' : 'No'}
  • 46 |
  • Exception type: #{sessionScope.exceptionType}
  • 47 |
  • Message: #{sessionScope.errorMessage}
  • 48 |
49 |
50 |
51 |
52 |
53 |

#{adm['label.500.detail']}

54 |
55 |
56 |
57 | #{of:printStackTrace(sessionScope.stacktrace)}
58 | 					
59 |
60 | 61 |
62 |
63 |
64 | 65 |
66 | 67 |
68 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/admin-top.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/admin.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 21 | 36 | 37 | 38 | 39 | 40 | 41 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/admin/breadcrumb.xhtml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/admin/ripple.xhtml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/admin/sidebar.xhtml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 27 | 28 | 29 | 30 | 43 | 44 | 45 | 47 | 59 | 60 | 61 | 62 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/expired.xhtml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/images/ajaxloadingbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adminfaces/admin-template/cd8bb5600464eaf2384f0a3fcabd78004919b64b/src/main/resources/META-INF/resources/images/ajaxloadingbar.gif -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/js/admin-lte.min.js: -------------------------------------------------------------------------------- 1 | /*! AdminLTE app.js 2 | * ================ 3 | * Main JS application file for AdminLTE v2. This file 4 | * should be included in all pages. It controls some layout 5 | * options and implements exclusive AdminLTE plugins. 6 | * 7 | * @Author Almsaeed Studio 8 | * @Support 9 | * @Email 10 | * @version 2.4.2 11 | * @repository git://github.com/almasaeed2010/AdminLTE.git 12 | * @license MIT 13 | */ 14 | if("undefined"==typeof jQuery)throw new Error("AdminLTE requires jQuery");+function(a){"use strict";function b(b){return this.each(function(){var e=a(this),g=e.data(c);if(!g){var h=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,g=new f(e,h))}if("string"==typeof g){if(void 0===g[b])throw new Error("No method named "+b);g[b]()}})}var c="lte.boxrefresh",d={source:"",params:{},trigger:".refresh-btn",content:".box-body",loadInContent:!0,responseType:"",overlayTemplate:'
',onLoadStart:function(){},onLoadDone:function(a){return a}},e={data:'[data-widget="box-refresh"]'},f=function(b,c){if(this.element=b,this.options=c,this.$overlay=a(c.overlay),""===c.source)throw new Error("Source url was not defined. Please specify a url in your BoxRefresh source option.");this._setUpListeners(),this.load()};f.prototype.load=function(){this._addOverlay(),this.options.onLoadStart.call(a(this)),a.get(this.options.source,this.options.params,function(b){this.options.loadInContent&&a(this.options.content).html(b),this.options.onLoadDone.call(a(this),b),this._removeOverlay()}.bind(this),""!==this.options.responseType&&this.options.responseType)},f.prototype._setUpListeners=function(){a(this.element).on("click",e.trigger,function(a){a&&a.preventDefault(),this.load()}.bind(this))},f.prototype._addOverlay=function(){a(this.element).append(this.$overlay)},f.prototype._removeOverlay=function(){a(this.element).remove(this.$overlay)};var g=a.fn.boxRefresh;a.fn.boxRefresh=b,a.fn.boxRefresh.Constructor=f,a.fn.boxRefresh.noConflict=function(){return a.fn.boxRefresh=g,this},a(window).on("load",function(){a(e.data).each(function(){b.call(a(this))})})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var g=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new h(e,g))}if("string"==typeof b){if(void 0===f[b])throw new Error("No method named "+b);f[b]()}})}var c="lte.boxwidget",d={animationSpeed:500,collapseTrigger:'[data-widget="collapse"]',removeTrigger:'[data-widget="remove"]',collapseIcon:"fa-minus",expandIcon:"fa-plus",removeIcon:"fa-times"},e={data:".box",collapsed:".collapsed-box",header:".box-header",body:".box-body",footer:".box-footer",tools:".box-tools"},f={collapsed:"collapsed-box"},g={collapsed:"collapsed.boxwidget",expanded:"expanded.boxwidget",removed:"removed.boxwidget"},h=function(a,b){this.element=a,this.options=b,this._setUpListeners()};h.prototype.toggle=function(){a(this.element).is(e.collapsed)?this.expand():this.collapse()},h.prototype.expand=function(){var b=a.Event(g.expanded),c=this.options.collapseIcon,d=this.options.expandIcon;a(this.element).removeClass(f.collapsed),a(this.element).children(e.header+", "+e.body+", "+e.footer).children(e.tools).find("."+d).removeClass(d).addClass(c),a(this.element).children(e.body+", "+e.footer).slideDown(this.options.animationSpeed,function(){a(this.element).trigger(b)}.bind(this))},h.prototype.collapse=function(){var b=a.Event(g.collapsed),c=this.options.collapseIcon,d=this.options.expandIcon;a(this.element).children(e.header+", "+e.body+", "+e.footer).children(e.tools).find("."+c).removeClass(c).addClass(d),a(this.element).children(e.body+", "+e.footer).slideUp(this.options.animationSpeed,function(){a(this.element).addClass(f.collapsed),a(this.element).trigger(b)}.bind(this))},h.prototype.remove=function(){var b=a.Event(g.removed);a(this.element).slideUp(this.options.animationSpeed,function(){a(this.element).trigger(b),a(this.element).remove()}.bind(this))},h.prototype._setUpListeners=function(){var b=this;a(this.element).on("click",this.options.collapseTrigger,function(c){return c&&c.preventDefault(),b.toggle(a(this)),!1}),a(this.element).on("click",this.options.removeTrigger,function(c){return c&&c.preventDefault(),b.remove(a(this)),!1})};var i=a.fn.boxWidget;a.fn.boxWidget=b,a.fn.boxWidget.Constructor=h,a.fn.boxWidget.noConflict=function(){return a.fn.boxWidget=i,this},a(window).on("load",function(){a(e.data).each(function(){b.call(a(this))})})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var g=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new h(e,g))}"string"==typeof b&&f.toggle()})}var c="lte.controlsidebar",d={slide:!0},e={sidebar:".control-sidebar",data:'[data-toggle="control-sidebar"]',open:".control-sidebar-open",bg:".control-sidebar-bg",wrapper:".wrapper",content:".content-wrapper",boxed:".layout-boxed"},f={open:"control-sidebar-open",fixed:"fixed"},g={collapsed:"collapsed.controlsidebar",expanded:"expanded.controlsidebar"},h=function(a,b){this.element=a,this.options=b,this.hasBindedResize=!1,this.init()};h.prototype.init=function(){a(this.element).is(e.data)||a(this).on("click",this.toggle),this.fix(),a(window).resize(function(){this.fix()}.bind(this))},h.prototype.toggle=function(b){b&&b.preventDefault(),this.fix(),a(e.sidebar).is(e.open)||a("body").is(e.open)?this.collapse():this.expand()},h.prototype.expand=function(){this.options.slide?a(e.sidebar).addClass(f.open):a("body").addClass(f.open),a(this.element).trigger(a.Event(g.expanded))},h.prototype.collapse=function(){a("body, "+e.sidebar).removeClass(f.open),a(this.element).trigger(a.Event(g.collapsed))},h.prototype.fix=function(){a("body").is(e.boxed)&&this._fixForBoxed(a(e.bg))},h.prototype._fixForBoxed=function(b){b.css({position:"absolute",height:a(e.wrapper).height()})};var i=a.fn.controlSidebar;a.fn.controlSidebar=b,a.fn.controlSidebar.Constructor=h,a.fn.controlSidebar.noConflict=function(){return a.fn.controlSidebar=i,this},a(document).on("click",e.data,function(c){c&&c.preventDefault(),b.call(a(this),"toggle")})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data(c);e||d.data(c,e=new f(d)),"string"==typeof b&&e.toggle(d)})}var c="lte.directchat",d={data:'[data-widget="chat-pane-toggle"]',box:".direct-chat"},e={open:"direct-chat-contacts-open"},f=function(a){this.element=a};f.prototype.toggle=function(a){a.parents(d.box).first().toggleClass(e.open)};var g=a.fn.directChat;a.fn.directChat=b,a.fn.directChat.Constructor=f,a.fn.directChat.noConflict=function(){return a.fn.directChat=g,this},a(document).on("click",d.data,function(c){c&&c.preventDefault(),b.call(a(this),"toggle")})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var h=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new g(h))}if("string"==typeof b){if(void 0===f[b])throw new Error("No method named "+b);f[b]()}})}var c="lte.layout",d={slimscroll:!0,resetHeight:!0},e={wrapper:".wrapper",contentWrapper:".content-wrapper",layoutBoxed:".layout-boxed",mainFooter:".main-footer",mainHeader:".main-header",sidebar:".sidebar",controlSidebar:".control-sidebar",fixed:".fixed",sidebarMenu:".sidebar-menu",logo:".main-header .logo"},f={fixed:"fixed",holdTransition:"hold-transition"},g=function(a){this.options=a,this.bindedResize=!1,this.activate()};g.prototype.activate=function(){this.fix(),this.fixSidebar(),a("body").removeClass(f.holdTransition),this.options.resetHeight&&a("body, html, "+e.wrapper).css({height:"auto","min-height":"100%"}),this.bindedResize||(a(window).resize(function(){this.fix(),this.fixSidebar(),a(e.logo+", "+e.sidebar).one("webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend",function(){this.fix(),this.fixSidebar()}.bind(this))}.bind(this)),this.bindedResize=!0),a(e.sidebarMenu).on("expanded.tree",function(){this.fix(),this.fixSidebar()}.bind(this)),a(e.sidebarMenu).on("collapsed.tree",function(){this.fix(),this.fixSidebar()}.bind(this))},g.prototype.fix=function(){a(e.layoutBoxed+" > "+e.wrapper).css("overflow","hidden");var b=a(e.mainFooter).outerHeight()||0,c=a(e.mainHeader).outerHeight()+b,d=a(window).height(),g=a(e.sidebar).height()||0;if(a("body").hasClass(f.fixed))a(e.contentWrapper).css("min-height",d-b);else{var h;d>=g?(a(e.contentWrapper).css("min-height",d-c),h=d-c):(a(e.contentWrapper).css("min-height",g),h=g);var i=a(e.controlSidebar);void 0!==i&&i.height()>h&&a(e.contentWrapper).css("min-height",i.height())}},g.prototype.fixSidebar=function(){if(!a("body").hasClass(f.fixed))return void(void 0!==a.fn.slimScroll&&a(e.sidebar).slimScroll({destroy:!0}).height("auto"));this.options.slimscroll&&void 0!==a.fn.slimScroll&&a(e.sidebar).slimScroll({height:a(window).height()-a(e.mainHeader).height()+"px"})};var h=a.fn.layout;a.fn.layout=b,a.fn.layout.Constuctor=g,a.fn.layout.noConflict=function(){return a.fn.layout=h,this},a(window).on("load",function(){b.call(a("body"))})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var g=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new h(g))}"toggle"===b&&f.toggle()})}var c="lte.pushmenu",d={collapseScreenSize:767,expandOnHover:!1,expandTransitionDelay:200},e={collapsed:".sidebar-collapse",open:".sidebar-open",mainSidebar:".main-sidebar",contentWrapper:".content-wrapper",searchInput:".sidebar-form .form-control",button:'[data-toggle="push-menu"]',mini:".sidebar-mini",expanded:".sidebar-expanded-on-hover",layoutFixed:".fixed"},f={collapsed:"sidebar-collapse",open:"sidebar-open",mini:"sidebar-mini",expanded:"sidebar-expanded-on-hover",expandFeature:"sidebar-mini-expand-feature",layoutFixed:"fixed"},g={expanded:"expanded.pushMenu",collapsed:"collapsed.pushMenu"},h=function(a){this.options=a,this.init()};h.prototype.init=function(){(this.options.expandOnHover||a("body").is(e.mini+e.layoutFixed))&&(this.expandOnHover(),a("body").addClass(f.expandFeature)),a(e.contentWrapper).click(function(){a(window).width()<=this.options.collapseScreenSize&&a("body").hasClass(f.open)&&this.close()}.bind(this)),a(e.searchInput).click(function(a){a.stopPropagation()})},h.prototype.toggle=function(){var b=a(window).width(),c=!a("body").hasClass(f.collapsed);b<=this.options.collapseScreenSize&&(c=a("body").hasClass(f.open)),c?this.close():this.open()},h.prototype.open=function(){a(window).width()>this.options.collapseScreenSize?a("body").removeClass(f.collapsed).trigger(a.Event(g.expanded)):a("body").addClass(f.open).trigger(a.Event(g.expanded))},h.prototype.close=function(){a(window).width()>this.options.collapseScreenSize?a("body").addClass(f.collapsed).trigger(a.Event(g.collapsed)):a("body").removeClass(f.open+" "+f.collapsed).trigger(a.Event(g.collapsed))},h.prototype.expandOnHover=function(){a(e.mainSidebar).hover(function(){a("body").is(e.mini+e.collapsed)&&a(window).width()>this.options.collapseScreenSize&&this.expand()}.bind(this),function(){a("body").is(e.expanded)&&this.collapse()}.bind(this))},h.prototype.expand=function(){setTimeout(function(){a("body").removeClass(f.collapsed).addClass(f.expanded)},this.options.expandTransitionDelay)},h.prototype.collapse=function(){setTimeout(function(){a("body").removeClass(f.expanded).addClass(f.collapsed)},this.options.expandTransitionDelay)};var i=a.fn.pushMenu;a.fn.pushMenu=b,a.fn.pushMenu.Constructor=h,a.fn.pushMenu.noConflict=function(){return a.fn.pushMenu=i,this},a(document).on("click",e.button,function(c){c.preventDefault(),b.call(a(this),"toggle")}),a(window).on("load",function(){b.call(a(e.button))})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var h=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new g(e,h))}if("string"==typeof f){if(void 0===f[b])throw new Error("No method named "+b);f[b]()}})}var c="lte.todolist",d={onCheck:function(a){return a},onUnCheck:function(a){return a}},e={data:'[data-widget="todo-list"]'},f={done:"done"},g=function(a,b){this.element=a,this.options=b,this._setUpListeners()};g.prototype.toggle=function(a){if(a.parents(e.li).first().toggleClass(f.done),!a.prop("checked"))return void this.unCheck(a);this.check(a)},g.prototype.check=function(a){this.options.onCheck.call(a)},g.prototype.unCheck=function(a){this.options.onUnCheck.call(a)},g.prototype._setUpListeners=function(){var b=this;a(this.element).on("change ifChanged","input:checkbox",function(){b.toggle(a(this))})};var h=a.fn.todoList;a.fn.todoList=b,a.fn.todoList.Constructor=g,a.fn.todoList.noConflict=function(){return a.fn.todoList=h,this},a(window).on("load",function(){a(e.data).each(function(){b.call(a(this))})})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this);if(!e.data(c)){var f=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,new h(e,f))}})}var c="lte.tree",d={animationSpeed:500,accordion:!0,followLink:!1,trigger:".treeview a"},e={tree:".tree",treeview:".treeview",treeviewMenu:".treeview-menu",open:".menu-open, .active",li:"li",data:'[data-widget="tree"]',active:".active"},f={open:"menu-open",tree:"tree"},g={collapsed:"collapsed.tree",expanded:"expanded.tree"},h=function(b,c){this.element=b,this.options=c,a(this.element).addClass(f.tree),a(e.treeview+e.active,this.element).addClass(f.open),this._setUpListeners()};h.prototype.toggle=function(a,b){var c=a.next(e.treeviewMenu),d=a.parent(),g=d.hasClass(f.open);d.is(e.treeview)&&(this.options.followLink&&"#"!==a.attr("href")||b.preventDefault(),g?this.collapse(c,d):this.expand(c,d))},h.prototype.expand=function(b,c){var d=a.Event(g.expanded);if(this.options.accordion){var h=c.siblings(e.open),i=h.children(e.treeviewMenu);this.collapse(i,h)}c.addClass(f.open),b.slideDown(this.options.animationSpeed,function(){a(this.element).trigger(d)}.bind(this))},h.prototype.collapse=function(b,c){var d=a.Event(g.collapsed);b.find(e.open).removeClass(f.open),c.removeClass(f.open),b.slideUp(this.options.animationSpeed,function(){b.find(e.open+" > "+e.treeview).slideUp(),a(this.element).trigger(d)}.bind(this))},h.prototype._setUpListeners=function(){var b=this;a(this.element).on("click",this.options.trigger,function(c){b.toggle(a(this),c)})};var i=a.fn.tree;a.fn.tree=b,a.fn.tree.Constructor=h,a.fn.tree.noConflict=function(){return a.fn.tree=i,this},a(window).on("load",function(){a(e.data).each(function(){b.call(a(this))})})}(jQuery); -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/js/adminslide.js: -------------------------------------------------------------------------------- 1 | //slideoutjs integration 2 | $(document).ready(function () { 3 | initSlideout(); 4 | }); 5 | 6 | $(window).on('resize', function () { 7 | initSlideout(); 8 | }); 9 | 10 | 11 | function initSlideout() { 12 | $("a.sidebar-toggle").on('click', function () { 13 | setTimeout(function () { 14 | if (!isMobile() && document.getElementById('sidebar')) { 15 | document.getElementById('content').style.transform = 'initial'; 16 | document.getElementById('sidebar').style.display = 'block'; 17 | } 18 | }, 30); 19 | }); 20 | 21 | if (isMobile() && !$(document.body).hasClass("layout-top-nav") && document.getElementById('sidebar')) { 22 | var sidebar = $('#sidebar'); 23 | var slideout = new Slideout({ 24 | 'panel': document.getElementById('content'), 25 | 'menu': document.getElementById('sidebar'), 26 | 'padding': 230, 27 | 'tolerance': 70 28 | }); 29 | 30 | $("a[data-toggle='push-menu']").on('click', function () { 31 | if ($("body").hasClass('sidebar-open')) { 32 | slideout.close(); 33 | document.getElementById('sidebar').style.display = 'none'; 34 | document.getElementById('content').style.transform = 'initial'; 35 | 36 | } else { 37 | adjustSidebarPosition(); 38 | slideout.open(); 39 | document.getElementById('sidebar').style.display = 'block'; 40 | document.getElementById('content').style.transform = '230px'; 41 | } 42 | }); 43 | 44 | slideout.on('translatestart', function () { 45 | setBodyClass('sidebar-open'); 46 | sidebar.show(500); 47 | }); 48 | 49 | 50 | slideout.on('translateend', function () { 51 | adjustSidebarPosition(); 52 | }); 53 | 54 | slideout.on('close', function () { 55 | slideoutClose(); 56 | }); 57 | 58 | slideout.on('beforeclose', function () { 59 | document.getElementById('sidebar').style.display = 'none'; 60 | }); 61 | 62 | $(".content-wrapper").click(function () { 63 | if (!$("body").hasClass("sidebar-open") && document.getElementById('content').style.transform !== 'initial') { 64 | document.getElementById('content').style.transform = 'initial'; 65 | document.getElementById('sidebar').style.display = 'none'; 66 | initSlideout(); 67 | } 68 | }); 69 | } 70 | else if (document.getElementById('sidebar')){ 71 | document.getElementById('content').style.transform = 'initial'; 72 | document.getElementById('sidebar').style.display = 'block'; 73 | } 74 | } 75 | 76 | function slideoutClose() { 77 | removeBodyClass('sidebar-open'); 78 | } 79 | 80 | function slideoutOpen() { 81 | var sidebar = $('#sidebar'); 82 | sidebar.show(500); 83 | removeBodyClass('sidebar-open'); 84 | } 85 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/js/control-sidebar.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | 'use strict' 4 | 5 | /** 6 | * Get access to plugins 7 | */ 8 | 9 | $('[data-toggle="control-sidebar"]').controlSidebar() 10 | $('[data-toggle="push-menu"]').pushMenu() 11 | 12 | 13 | var $pushMenu = $('[data-toggle="push-menu"]').data('lte.pushmenu') 14 | var $controlSidebar = $('[data-toggle="control-sidebar"]').data('lte.controlsidebar') 15 | 16 | 17 | function changeLayout(cls) { 18 | $('body').toggleClass(cls) 19 | $controlSidebar.fix() 20 | } 21 | 22 | function updateSidebarSkin(sidebarSkin) { 23 | var $sidebar = $('.control-sidebar'); 24 | if (sidebarSkin === 'control-sidebar-light') { 25 | $sidebar.removeClass('control-sidebar-dark'); 26 | if (PF('controlSidebarSkin').input.is(':checked')) {//it will not be checked when the value comes from browser local storage 27 | PF('controlSidebarSkin').toggle(); 28 | } 29 | } else { 30 | $sidebar.removeClass('control-sidebar-light'); 31 | if (!PF('controlSidebarSkin').input.is(':checked')) {//it will not be checked when the value comes from browser local storage 32 | PF('controlSidebarSkin').toggle(); 33 | } 34 | } 35 | 36 | $sidebar.addClass(sidebarSkin); 37 | 38 | store('layout.sidebar-skin', sidebarSkin); 39 | } 40 | 41 | function updateBoxedLayout(boxedLayout) { 42 | if (isMobile()) { //boxed layout is not compatible with mobile screens neither fixed layout 43 | disableControlSidebarOption('#boxed-layout'); 44 | store('layout.boxed', false); 45 | } 46 | 47 | if (boxedLayout === true || boxedLayout === 'true') { 48 | if (!$('body').hasClass('layout-boxed')) { 49 | $('body').addClass('layout-boxed'); 50 | } 51 | if (!PF('boxedLayout').input.is(':checked')) {//it will not be checked when the value comes from browser local storage 52 | PF('boxedLayout').toggle(); 53 | } 54 | 55 | enableControlSidebarOption('#boxed-layout'); 56 | disableControlSidebarOption('#fixed-layout'); 57 | } else { 58 | enableControlSidebarOption('#fixed-layout'); 59 | if ($('body').hasClass('layout-boxed')) { 60 | $('body').removeClass('layout-boxed'); 61 | } 62 | if (PF('boxedLayout').input.is(':checked')) {//update input when value comes from local storage 63 | PF('boxedLayout').toggle(); 64 | } 65 | } 66 | store('layout.boxed', boxedLayout); 67 | 68 | } 69 | 70 | function updateFixedLayout(fixedLayout) { 71 | if (isMobile()) { //fixed layout not compatible with small screens (admin-template already has behaviour for navbar when on mobile) neither boxed layout 72 | disableControlSidebarOption('#fixed-layout'); 73 | store('layout.fixed', false); 74 | return; 75 | } 76 | if (fixedLayout === true || fixedLayout === 'true') { 77 | if (!$('body').hasClass('fixed')) { 78 | $('body').addClass('fixed'); 79 | } 80 | if (!PF('fixedLayout').input.is(':checked')) {//it will not be checked when the value comes from browser local storage 81 | PF('fixedLayout').toggle(); 82 | } 83 | enableControlSidebarOption('#fixed-layout'); 84 | disableControlSidebarOption('#boxed-layout'); 85 | } else { 86 | enableControlSidebarOption('#boxed-layout'); 87 | if ($('body').hasClass('fixed')) { 88 | $('body').removeClass('fixed'); 89 | } 90 | if (PF('fixedLayout').input.is(':checked')) {//update input when value comes from local storage 91 | PF('fixedLayout').toggle(); 92 | } 93 | } 94 | store('layout.fixed', fixedLayout); 95 | } 96 | 97 | function updateSidebarCollapded(sidebarCollapsed) { 98 | if (isMobile() || isLayoutTop()) { //fixed layout not compatible with small screens (admin-template already has behaviour for navbar when on mobile) neither boxed layout 99 | disableControlSidebarOption('#sidebar-collapsed'); 100 | store('layout.sidebar-collapsed', false); 101 | return; 102 | } 103 | if (sidebarCollapsed === true || sidebarCollapsed === 'true') { 104 | if (!$('body').hasClass('sidebar-collapse')) { 105 | $('body').addClass('sidebar-collapse'); 106 | } 107 | if (!PF('sidebarCollapsed').input.is(':checked')) {//it will not be checked when the value comes from browser local storage 108 | PF('sidebarCollapsed').toggle(); 109 | } 110 | } else { 111 | if ($('body').hasClass('sidebar-collapse')) { 112 | $('body').removeClass('sidebar-collapse'); 113 | } 114 | if (PF('sidebarCollapsed').input.is(':checked')) {//update input when value comes from local storage 115 | PF('sidebarCollapsed').toggle(); 116 | } 117 | } 118 | store('layout.sidebar-collapsed', sidebarCollapsed); 119 | 120 | } 121 | 122 | function updateSidebarExpand(expandOnHover) { 123 | if (isMobile() || isLayoutTop()) { 124 | disableControlSidebarOption('#sidebar-expand-hover'); 125 | store('layout.sidebar-expand-hover', false); 126 | return; 127 | } 128 | 129 | if (expandOnHover === true || expandOnHover === 'true') { 130 | if (!PF('sidebarExpand').input.is(':checked')) {//it will not be checked when the value comes from browser local storage 131 | PF('sidebarExpand').toggle(); 132 | } 133 | $pushMenu.expandOnHover(); 134 | collapseSidebar(); 135 | } else { 136 | if (PF('sidebarExpand').input.is(':checked')) { 137 | PF('sidebarExpand').toggle(); 138 | } 139 | var sidebarCollapsed = get('layout.sidebar-collapsed'); 140 | if (sidebarCollapsed !== true && sidebarCollapsed !== "true") { 141 | expandSidebar(); 142 | } 143 | $('[data-toggle="push-menu"]').data('lte.pushmenu', null); //not working, see https://github.com/almasaeed2010/AdminLTE/issues/1843#issuecomment-379550396 144 | $('[data-toggle="push-menu"]').pushMenu({expandOnHover: false}); 145 | $pushMenu = $('[data-toggle="push-menu"]').data('lte.pushmenu'); 146 | } 147 | 148 | store('layout.sidebar-expand-hover', expandOnHover); 149 | 150 | } 151 | 152 | function updateTemplate() { 153 | var isDefaultTemplate = PF('toggleLayout').input.is(':checked'); 154 | if (isDefaultTemplate === true || isDefaultTemplate === "true") { 155 | if (isLayoutTop()) { 156 | $('body').removeClass('layout-top-nav'); 157 | } 158 | } else if (!isLayoutTop()) { 159 | $('body').addClass('layout-top-nav'); 160 | } 161 | store('layout.default-template', isDefaultTemplate); 162 | } 163 | 164 | function updateSidebarToggle(sidebarControlOpen) { 165 | if (sidebarControlOpen === true || sidebarControlOpen === 'true') { 166 | $('.control-sidebar').addClass('control-sidebar-open'); 167 | $('body').addClass('control-sidebar-open'); 168 | if (!PF('fixedControlSidebar').input.is(':checked')) { 169 | PF('fixedControlSidebar').toggle(); 170 | } 171 | } else { 172 | $('.control-sidebar').removeClass('control-sidebar-open') 173 | $('body').removeClass('control-sidebar-open'); 174 | if (PF('fixedControlSidebar').input.is(':checked')) { 175 | PF('fixedControlSidebar').toggle(); 176 | } 177 | } 178 | store('layout.sidebar-control-open', sidebarControlOpen); 179 | 180 | } 181 | 182 | function loadSkin() { 183 | var skin = get('layout.skin'); 184 | if (skin && !$('body').hasClass(skin)) { 185 | $('#btn-' + skin).click(); 186 | } 187 | } 188 | 189 | function loadTemplate() { 190 | var isDefaultTemplate = get('layout.default-template'); 191 | if(isDefaultTemplate === "null" || isDefaultTemplate === null) { 192 | isDefaultTemplate = PF('toggleLayout').input.is(':checked'); 193 | } 194 | if ((isDefaultTemplate === "true" || isDefaultTemplate === true) && isLayoutTop()) { 195 | PF('toggleLayout').toggle(); 196 | } else if ((isDefaultTemplate === "false" || isDefaultTemplate === false) && !isLayoutTop()) { 197 | PF('toggleLayout').toggle(); 198 | } 199 | 200 | } 201 | 202 | function disableControlSidebarOption(id) { 203 | var optionSelector = id.concat(", ").concat(id).concat(" span.ui-chkbox-icon, ").concat(id).concat("-label"); 204 | $(optionSelector).addClass('ui-state-disabled'); 205 | } 206 | 207 | function enableControlSidebarOption(id) { 208 | var optionSelector = id.concat(" ,").concat(id).concat(" span.ui-chkbox-icon, ").concat(id).concat("-label"); 209 | $(optionSelector).removeClass('ui-state-disabled'); 210 | } 211 | 212 | /** 213 | * Retrieve stored settings and apply them to the template 214 | * 215 | * @returns void 216 | */ 217 | function setup() { 218 | 219 | var sidebarSkin = get('layout.sidebar-skin'); 220 | 221 | if (sidebarSkin === null || sidebarSkin === "null") { 222 | if (PF('controlSidebarSkin').input.is(':checked')) { 223 | sidebarSkin = 'control-sidebar-dark'; 224 | } else { 225 | sidebarSkin = 'control-sidebar-light'; 226 | } 227 | } 228 | 229 | updateSidebarSkin(sidebarSkin); 230 | 231 | var controlSidebarOpen = get('layout.sidebar-control-open'); 232 | 233 | if (controlSidebarOpen === null || controlSidebarOpen === "null") { 234 | controlSidebarOpen = PF('fixedControlSidebar').input.is(':checked'); 235 | } 236 | 237 | updateSidebarToggle(controlSidebarOpen); 238 | 239 | 240 | var boxedLayout = get('layout.boxed'); 241 | 242 | if (boxedLayout === null || boxedLayout === "null") { 243 | boxedLayout = PF('boxedLayout').input.is(':checked'); 244 | } 245 | 246 | updateBoxedLayout(boxedLayout); 247 | 248 | var fixedLayout = get('layout.fixed'); 249 | 250 | 251 | if (fixedLayout === null || fixedLayout === "null") { 252 | fixedLayout = PF('fixedLayout').input.is(':checked'); 253 | } 254 | 255 | updateFixedLayout(fixedLayout); 256 | 257 | var sidebarCollapsed = get('layout.sidebar-collapsed'); 258 | 259 | 260 | if (sidebarCollapsed === null || sidebarCollapsed === "null") { 261 | sidebarCollapsed = PF('sidebarCollapsed').input.is(':checked'); 262 | } 263 | 264 | updateSidebarCollapded(sidebarCollapsed); 265 | 266 | var expandOnHover = get('layout.sidebar-expand-hover'); 267 | 268 | if (expandOnHover === null || expandOnHover === "null") { 269 | expandOnHover = PF('sidebarExpand').input.is(':checked'); 270 | } 271 | 272 | updateSidebarExpand(expandOnHover); 273 | 274 | 275 | $('#sidebar-skin').on('click', function () { 276 | var sidebarSkin; 277 | if ($('.control-sidebar').hasClass('control-sidebar-dark')) { 278 | sidebarSkin = 'control-sidebar-light'; 279 | } else { 280 | sidebarSkin = 'control-sidebar-dark'; 281 | } 282 | setTimeout(function () { 283 | updateSidebarSkin(sidebarSkin); 284 | }, 20); 285 | }); 286 | 287 | $('#boxed-layout .ui-chkbox-box, #boxed-layout-label').on('click', function () { 288 | var boxedLayout = $('body').hasClass('layout-boxed'); 289 | setTimeout(function () { 290 | updateBoxedLayout(!boxedLayout); //negate current value in order to update it's state from boxed to not boxed and vive versa 291 | updateFixedLayout(get('layout.fixed')); 292 | }, 20); 293 | }); 294 | 295 | $('#fixed-layout .ui-chkbox-box, #fixed-layout-label').on('click', function () { 296 | var fixedLayout = $('body').hasClass('fixed'); 297 | setTimeout(function () { 298 | updateFixedLayout(!fixedLayout); //negate it's current value so we can change it's state 299 | updateBoxedLayout(get('layout.boxed')); 300 | }, 20); 301 | }); 302 | 303 | $('#sidebar-collapsed .ui-chkbox-box, #sidebar-collapsed-label').on('click', function () { 304 | var sidebarCollapsed = $('body').hasClass('sidebar-collapse'); 305 | setTimeout(function () { 306 | updateSidebarCollapded(!sidebarCollapsed);//negate because we want to toggle its state from collpased to not collapsed and vice versa 307 | }, 20); 308 | }); 309 | 310 | $('#control-sidebar-toggle .ui-chkbox-box, #control-sidebar-toggle-label').on('click', function () { 311 | var controlSidebarFixed = $('body').hasClass('control-sidebar-open'); 312 | setTimeout(function () { 313 | updateSidebarToggle(!controlSidebarFixed); 314 | }, 20); 315 | 316 | }); 317 | 318 | 319 | $('#sidebar-expand-hover .ui-chkbox-box').on('click', function () { 320 | var expandOnHover = PF('sidebarExpand').input.is(':checked'); 321 | setTimeout(function () { 322 | updateSidebarExpand(expandOnHover); 323 | }, 20); 324 | }); 325 | 326 | $('#sidebar-expand-hover-label').on('click', function () { 327 | $('#sidebar-expand-hover .ui-chkbox-box').click(); 328 | }); 329 | 330 | 331 | $('#content').click(function () { 332 | $('.control-sidebar').removeClass('control-sidebar-open'); 333 | }); 334 | 335 | $('#toggle-menu-layout .ui-chkbox-box, #toggle-menu-layout').on('click', function () { 336 | setTimeout(function () { 337 | updateTemplate(); 338 | }, 20); 339 | }); 340 | 341 | loadTemplate(); 342 | 343 | loadSkin(); 344 | 345 | } 346 | 347 | 348 | $(document).on("pfAjaxComplete", function () { 349 | setTimeout(function () { 350 | setup(); 351 | }, 20); 352 | }); 353 | 354 | 355 | $(document).ready(function () { 356 | setTimeout(function () { 357 | setup(); 358 | }, 20); 359 | }); 360 | 361 | 362 | }); 363 | 364 | function restoreLayoutDefaults() { 365 | store('layout.skin', null); 366 | store('layout.default-template', null); 367 | store('layout.sidebar-expand-hover', null); 368 | store('layout.sidebar-control-open', null); 369 | store('layout.fixed', null); 370 | store('layout.boxed', null); 371 | store('layout.sidebar-collapsed', null); 372 | store('layout.sidebar-skin', null); 373 | } 374 | 375 | function toggleTemplate() { 376 | store('layout.default-template', null); 377 | $('#toggle-menu-layout-label').click(); 378 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/js/slideout.min.js: -------------------------------------------------------------------------------- 1 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),e.Slideout=t()}}(function(){var t,e,n;return function i(t,e,n){function o(r,a){if(!e[r]){if(!t[r]){var u=typeof require=="function"&&require;if(!a&&u)return u(r,!0);if(s)return s(r,!0);var l=new Error("Cannot find module '"+r+"'");throw l.code="MODULE_NOT_FOUND",l}var f=e[r]={exports:{}};t[r][0].call(f.exports,function(e){var n=t[r][1][e];return o(n?n:e)},f,f.exports,i,t,e,n)}return e[r].exports}var s=typeof require=="function"&&require;for(var r=0;rt._tolerance?t.open():t.close()}t._moved=false};this.panel.addEventListener(f.end,this._onTouchEndFn);this._onTouchMoveFn=function(e){if(r||t._preventOpen||typeof e.touches==="undefined"||d(e.target)){return}var n=e.touches[0].clientX-t._startOffsetX;var i=t._currentOffsetX=n;if(Math.abs(i)>t._padding){return}if(Math.abs(n)>20){t._opening=true;var o=n*t._orientation;if(t._opened&&o>0||!t._opened&&o<0){return}if(!t._moved){t.emit("translatestart")}if(o<=0){i=n+t._padding*t._orientation;t._opening=false}if(!(t._moved&&u.classList.contains("slideout-open"))){u.classList.add("slideout-open")}t.panel.style[h+"transform"]=t.panel.style.transform="translateX("+i+"px)";t.emit("translate",i);t._moved=true}};this.panel.addEventListener(f.move,this._onTouchMoveFn);return this};_.prototype.enableTouch=function(){this._touch=true;return this};_.prototype.disableTouch=function(){this._touch=false;return this};_.prototype.destroy=function(){this.close();a.removeEventListener(f.move,this._preventMove);this.panel.removeEventListener(f.start,this._resetTouchFn);this.panel.removeEventListener("touchcancel",this._onTouchCancelFn);this.panel.removeEventListener(f.end,this._onTouchEndFn);this.panel.removeEventListener(f.move,this._onTouchMoveFn);a.removeEventListener("scroll",this._onScrollFn);this.open=this.close=function(){};return this};e.exports=_},{decouple:2,emitter:3}],2:[function(t,e,n){"use strict";var i=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||function(t){window.setTimeout(t,1e3/60)}}();function o(t,e,n){var o,s=false;function r(t){o=t;a()}function a(){if(!s){i(u);s=true}}function u(){n.call(t,o);s=false}t.addEventListener(e,r,false);return r}e.exports=o},{}],3:[function(t,e,n){"use strict";var i=function(t,e){if(!(t instanceof e)){throw new TypeError("Cannot call a class as a function")}};n.__esModule=true;var o=function(){function t(){i(this,t)}t.prototype.on=function e(t,n){this._eventCollection=this._eventCollection||{};this._eventCollection[t]=this._eventCollection[t]||[];this._eventCollection[t].push(n);return this};t.prototype.once=function n(t,e){var n=this;function i(){n.off(t,i);e.apply(this,arguments)}i.listener=e;this.on(t,i);return this};t.prototype.off=function o(t,e){var n=undefined;if(!this._eventCollection||!(n=this._eventCollection[t])){return this}n.forEach(function(t,i){if(t===e||t.listener===e){n.splice(i,1)}});if(n.length===0){delete this._eventCollection[t]}return this};t.prototype.emit=function s(t){var e=this;for(var n=arguments.length,i=Array(n>1?n-1:0),o=1;o=b.outerHeight()?k=!0:(c.stop(!0, 11 | !0).fadeIn("fast"),a.railVisible&&m.stop(!0,!0).fadeIn("fast"))}function p(){a.alwaysVisible||(B=setTimeout(function(){a.disableFadeOut&&r||y||z||(c.fadeOut("slow"),m.fadeOut("slow"))},1E3))}var r,y,z,B,A,u,l,C,k=!1,b=e(this);if(b.parent().hasClass(a.wrapperClass)){var q=b.scrollTop(),c=b.siblings("."+a.barClass),m=b.siblings("."+a.railClass);x();if(e.isPlainObject(f)){if("height"in f&&"auto"==f.height){b.parent().css("height","auto");b.css("height","auto");var h=b.parent().parent().height();b.parent().css("height", 12 | h);b.css("height",h)}else"height"in f&&(h=f.height,b.parent().css("height",h),b.css("height",h));if("scrollTo"in f)q=parseInt(a.scrollTo);else if("scrollBy"in f)q+=parseInt(a.scrollBy);else if("destroy"in f){c.remove();m.remove();b.unwrap();return}n(q,!1,!0)}}else if(!(e.isPlainObject(f)&&"destroy"in f)){a.height="auto"==a.height?b.parent().height():a.height;q=e("
").addClass(a.wrapperClass).css({position:"relative",overflow:"hidden",width:a.width,height:a.height});b.css({overflow:"hidden", 13 | width:a.width,height:a.height});var m=e("
").addClass(a.railClass).css({width:a.size,height:"100%",position:"absolute",top:0,display:a.alwaysVisible&&a.railVisible?"block":"none","border-radius":a.railBorderRadius,background:a.railColor,opacity:a.railOpacity,zIndex:90}),c=e("
").addClass(a.barClass).css({background:a.color,width:a.size,position:"absolute",top:0,opacity:a.opacity,display:a.alwaysVisible?"block":"none","border-radius":a.borderRadius,BorderRadius:a.borderRadius,MozBorderRadius:a.borderRadius, 14 | WebkitBorderRadius:a.borderRadius,zIndex:99}),h="right"==a.position?{right:a.distance}:{left:a.distance};m.css(h);c.css(h);b.wrap(q);b.parent().append(c);b.parent().append(m);a.railDraggable&&c.bind("mousedown",function(a){var b=e(document);z=!0;t=parseFloat(c.css("top"));pageY=a.pageY;b.bind("mousemove.slimscroll",function(a){currTop=t+a.pageY-pageY;c.css("top",currTop);n(0,c.position().top,!1)});b.bind("mouseup.slimscroll",function(a){z=!1;p();b.unbind(".slimscroll")});return!1}).bind("selectstart.slimscroll", 15 | function(a){a.stopPropagation();a.preventDefault();return!1});m.hover(function(){w()},function(){p()});c.hover(function(){y=!0},function(){y=!1});b.hover(function(){r=!0;w();p()},function(){r=!1;p()});b.bind("touchstart",function(a,b){a.originalEvent.touches.length&&(A=a.originalEvent.touches[0].pageY)});b.bind("touchmove",function(b){k||b.originalEvent.preventDefault();b.originalEvent.touches.length&&(n((A-b.originalEvent.touches[0].pageY)/a.touchScrollStep,!0),A=b.originalEvent.touches[0].pageY)}); 16 | x();"bottom"===a.start?(c.css({top:b.outerHeight()-c.outerHeight()}),n(0,!0)):"top"!==a.start&&(n(e(a.start).position().top,null,!0),a.alwaysVisible||c.hide());window.addEventListener?(this.addEventListener("DOMMouseScroll",v,!1),this.addEventListener("mousewheel",v,!1)):document.attachEvent("onmousewheel",v)}});return this}});e.fn.extend({slimscroll:e.fn.slimScroll})})(jQuery); -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/optimistic.xhtml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 |
9 |
10 |
11 |

#{adm['label.optimistic.title']}

12 |
13 |
14 |

#{adm['label.optimistic.message']} 15 | #{adm['label.optimistic.click-here']} 16 |

17 | 18 |
19 |
20 |
21 | 22 | 23 | 24 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/web-fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | admin_template_fragment 9 | 10 | 11 | 12 | 13 | primefaces.THEME 14 | admin 15 | 16 | 17 | 403 18 | /403.xhtml 19 | 20 | 21 | com.github.adminfaces.template.exception.AccessDeniedException 22 | /403.xhtml 23 | 24 | 25 | jakarta.ejb.AccessLocalException 26 | /403.xhtml 27 | 28 | 29 | 404 30 | /404.xhtml 31 | 32 | 33 | 500 34 | /500.xhtml 35 | 36 | 37 | java.lang.Throwable 38 | /500.xhtml 39 | 40 | 41 | jakarta.faces.application.ViewExpiredException 42 | /expired.xhtml 43 | 44 | 45 | jakarta.persistence.OptimisticLockException 46 | /optimistic.xhtml 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/main/resources/admin.properties: -------------------------------------------------------------------------------- 1 | #general 2 | admin.version=${project.version} 3 | admin.legacy=${admin.legacy} 4 | label.go-back=Go back to 5 | label.home=Home 6 | label.or-to=or to 7 | label.previous-page=previous page 8 | 9 | #403 10 | label.403.header=403 11 | label.403.message=Access denied! You do not have access to the requested page. 12 | 13 | #404 14 | label.404.header=404 15 | label.404.message=Oops! Page not found 16 | 17 | #500 18 | label.500.header=500 19 | label.500.message=Oops! Something went wrong 20 | label.500.title=Unexpected error 21 | label.500.detail=Details 22 | 23 | #expired 24 | label.expired.title=View expired 25 | label.expired.message= The requested page could not be recovered. 26 | label.expired.click-here= Click here to reload the page. 27 | 28 | #optimistic 29 | label.optimistic.title=Record already updated 30 | label.optimistic.message= The requested record has been already updated by another user. 31 | label.optimistic.click-here= Click here to reload the updated record from database. 32 | 33 | #controlsidebar 34 | controlsidebar.header=Layout Options 35 | controlsidebar.label.restore-defaults=Restore defaults 36 | controlsidebar.label.menu-horientation=Left menu layout 37 | controlsidebar.txt.menu-horientation=Toggle menu orientation between left and top menu. 38 | controlsidebar.label.fixed-layout=Fixed Layout 39 | controlsidebar.txt.fixed-layout=Activate the fixed layout, if checked the top bar will be fixed on the page. 40 | controlsidebar.label.boxed-layout=Boxed Layout 41 | controlsidebar.txt.boxed-layout=Activate the boxed layout. 42 | controlsidebar.label.sidebar-collapsed=Collapsed Sidebar 43 | controlsidebar.txt.sidebar-collapsed=If checked the sidebar menu will be collapsed. 44 | controlsidebar.label.sidebar-expand-hover=Sidebar Expand on Hover 45 | controlsidebar.txt.sidebar-expand-hover=If checked the left sidebar will expand on hover. 46 | controlsidebar.label.sidebar-slide=Control Sidebar fixed 47 | controlsidebar.txt.sidebar-slide=If checked control sidebar will be fixed on the page. 48 | controlsidebar.label.sidebar-skin=Dark Sidebar Skin 49 | controlsidebar.txt.sidebar-skin=If checked dark skin will be used for control sidebar, otherwise light skin will be used. 50 | controlsidebar.header.skins=Skins 51 | 52 | 53 | #menu search 54 | menu.search.placeholder=Search menu items... -------------------------------------------------------------------------------- /src/main/resources/admin_ar.properties: -------------------------------------------------------------------------------- 1 | #general 2 | admin.version=${project.version} 3 | admin.legacy=${admin.legacy} 4 | label.go-back=\u0627\u0644\u0631\u062c\u0648\u0639 \u0644\u0644\u062e\u0644\u0641 5 | label.home=\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629 6 | 7 | #403 8 | label.403.header=403 9 | label.403.message=\u0627\u0644\u0648\u0635\u0648\u0644 \u0645\u0645\u0646\u0648\u0639! \u0644\u064a\u0633 \u0644\u062f\u064a\u0643 \u0635\u0644\u0627\u062d\u064a\u0629 \u0644\u0644\u0648\u0635\u0648\u0644 \u0644\u0644\u0635\u0641\u062d\u0629 \u0627\u0644\u0645\u0637\u0644\u0648\u0628\u0629. 10 | 11 | #404 12 | label.404.header=404 13 | label.404.message=\u0639\u0630\u0631\u0627\u064b! \u0627\u0644\u0635\u0641\u062d\u0629 \u063a\u064a\u0631 \u0645\u0648\u062c\u0648\u062f\u0629\n 14 | 15 | #500 16 | label.500.header=500 17 | label.500.message=\u0639\u0630\u0631\u0627\u064b! \u062d\u062f\u062b \u062e\u0637\u0623 18 | label.500.title=\u062e\u0637\u0623 \u063a\u064a\u0631 \u0645\u062a\u0648\u0642\u0639\n 19 | label.500.detail=\u0627\u0644\u062a\u0641\u0627\u0635\u064a\u0644 20 | 21 | #expired 22 | label.expired.title=\u0627\u0646\u062a\u0647\u062a \u0635\u0644\u0627\u062d\u064a\u0629 \u0627\u0644\u0635\u0641\u062d\u0629 23 | label.expired.message= \u0644\u0627\u064a\u0645\u0643\u0646 \u0627\u0633\u062a\u0639\u0627\u062f\u0629 \u0627\u0644\u0635\u0641\u062d\u0629 \u0627\u0644\u0645\u0637\u0644\u0648\u0628\u0629 24 | label.expired.click-here= \u0627\u0636\u063a\u0637 \u0647\u0646\u0627 \u0644\u0625\u0639\u0627\u062f\u0629 \u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0635\u0641\u062d\u0629. 25 | 26 | #optimistic 27 | label.optimistic.title=\u062a\u0645 \u062a\u062d\u062f\u064a\u062b \u0627\u0644\u0633\u062c\u0644 \u0645\u0633\u0628\u0642\u0627\u064b 28 | label.optimistic.message= \u062a\u0645 \u062a\u062d\u062f\u064a\u062b \u0627\u0644\u0633\u062c\u0644 \u0627\u0644\u0645\u0637\u0644\u0648\u0628 \u0645\u0646 \u0642\u0628\u0644 \u0645\u0633\u062a\u062e\u062f\u0645 \u0622\u062e\u0631 \u0645\u0633\u0628\u0642\u0627\u064b 29 | label.optimistic.click-here= \u0627\u0636\u063a\u0637 \u0647\u0646\u0627 \u0644\u0625\u0639\u0627\u062f\u0629 \u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0633\u062c\u0644 \u0627\u0644\u0645\u062d\u062f\u062b \u0645\u0646 \u0642\u0627\u0639\u062f\u0629 \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a 30 | 31 | #controlsidebar 32 | controlsidebar.header=\u062e\u064a\u0627\u0631\u0627\u062a \u0627\u0644\u062a\u062e\u0637\u064a\u0637 33 | controlsidebar.label.restore-defaults=\u0627\u0633\u062a\u0639\u0627\u062f\u0629 \u0627\u0644\u0627\u0641\u062a\u0631\u0627\u0636\u064a 34 | controlsidebar.label.menu-horientation=\u062a\u062e\u0637\u064a\u0637 \u0642\u0627\u0626\u0645\u0629 \u0628\u0627\u0644\u064a\u0633\u0627\u0631 35 | controlsidebar.txt.menu-horientation=\u062a\u0628\u062f\u064a\u0644 \u0627\u062a\u062c\u0627\u0629 \u0627\u0644\u0642\u0627\u0626\u0645\u0629 \u0628\u064a\u0646 \u0642\u0627\u0626\u0645\u0629 \u0628\u0627\u0644\u064a\u0633\u0627\u0631 \u0648 \u0642\u0627\u0626\u0645\u0629 \u0639\u0644\u0648\u064a\u0629 36 | controlsidebar.label.fixed-layout=\u0627\u0644\u062a\u062e\u0637\u064a\u0637 \u0627\u0644\u062b\u0627\u0628\u062a 37 | controlsidebar.txt.fixed-layout=\u062a\u0641\u0639\u064a\u0644 \u0627\u0644\u062a\u062e\u0637\u064a\u0637 \u0627\u0644\u062b\u0627\u0628\u062a\u060c \u0639\u0646\u062f \u0627\u0644\u062a\u0641\u0639\u064a\u0644 \u062a\u062b\u0628\u062a \u0627\u0644\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0639\u0644\u0648\u064a\u0629 \u0627\u0639\u0644\u064a \u0627\u0644\u0635\u0641\u062d\u0629 38 | controlsidebar.label.boxed-layout=\u0627\u0644\u062a\u062e\u0637\u064a\u0637 \u0627\u0644\u0645\u0631\u0628\u0639 39 | controlsidebar.txt.boxed-layout=\u062a\u0641\u0639\u064a\u0644 \u0627\u0644\u062a\u062e\u0637\u064a\u0637 \u0627\u0644\u0645\u0631\u0628\u0639. 40 | controlsidebar.label.sidebar-collapsed=\u0642\u0627\u0626\u0645\u0629 \u062c\u0627\u0646\u0628\u064a\u0629 \u0645\u0637\u0648\u064a\u0629 41 | controlsidebar.txt.sidebar-collapsed=\u0639\u0646\u062f \u0627\u0644\u062a\u0641\u0639\u064a\u0644 \u0633\u062a\u0643\u0648\u0646 \u0627\u0644\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u062c\u0627\u0646\u0628\u064a\u0629 \u0645\u0637\u0648\u064a\u0629 42 | controlsidebar.label.sidebar-expand-hover=\u062a\u0648\u0633\u064a\u0639 \u0627\u0644\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u062c\u0627\u0646\u0628\u064a\u0629 \u0639\u0646\u062f \u0645\u0631\u0648\u0631 \u0627\u0644\u0645\u0624\u0634\u0631 43 | controlsidebar.txt.sidebar-expand-hover=\u0639\u0646\u062f \u0627\u0644\u062a\u0641\u0639\u064a\u0644 \u0633\u062a\u062a\u0648\u0633\u0639 \u0627\u0644\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u062c\u0627\u0646\u0628\u064a\u0629 \u0639\u0646\u062f \u0645\u0631\u0648\u0631 \u0627\u0644\u0645\u0624\u0634\u0631 44 | controlsidebar.label.sidebar-slide=\u062a\u062b\u0628\u064a\u062a \u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u062a\u062d\u0643\u0645 \u0627\u0644\u062c\u0627\u0646\u0628\u064a\u0629 45 | controlsidebar.txt.sidebar-slide=\u0639\u0646\u062f \u0627\u0644\u062a\u0641\u0639\u064a\u0644 \u0633\u062a\u0643\u0648\u0646 \u0642\u0627\u0621\u0645\u0629 \u0627\u0644\u062a\u062d\u0643\u0645 \u0627\u0644\u062c\u0627\u0646\u0628\u064a\u0629 \u062b\u0627\u0628\u062a\u0629 46 | controlsidebar.label.sidebar-skin=\u0642\u0627\u0626\u0645\u0629 \u062c\u0627\u0646\u0628\u064a\u0629 \u062f\u0627\u0643\u0646\u0629 47 | #If checked dark skin will be used for control sidebar, otherwise light skin will be used. 48 | controlsidebar.txt.sidebar-skin=\u0639\u0646\u062f \u0627\u0644\u062a\u0641\u0639\u064a\u0644 \u0633\u064a\u062a\u0645 \u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0644\u0648\u0646 \u062f\u0627\u0643\u0646 \u0644\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u062a\u062d\u0643\u0645 \u0627\u0644\u062c\u0627\u0646\u0628\u064a\u0629\u060c \u0639\u062f\u0627 \u0630\u0644\u0643 \u0633\u064a\u062a\u0645 \u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0644\u0648\u0646 \u0641\u0627\u062a\u062d 49 | controlsidebar.header.skins=\u0627\u0644\u0648\u0627\u0646 \u0627\u0644\u0639\u0631\u0636 50 | 51 | 52 | #menu search 53 | menu.search.placeholder=\u0627\u0644\u0628\u062d\u062b \u0641\u064a \u0639\u0646\u0627\u0635\u0631 \u0627\u0644\u0642\u0627\u0626\u0645\u0629 54 | label.or-to=\u0627\u0648 \u0627\u0644\u064a 55 | label.previous-page=\u0627\u0644\u0635\u0641\u062d\u0629 \u0627\u0644\u0633\u0627\u0628\u0642\u0629 56 | -------------------------------------------------------------------------------- /src/main/resources/admin_de_DE.properties: -------------------------------------------------------------------------------- 1 | #general 2 | admin.version=${project.version} 3 | admin.legacy=${admin.legacy} 4 | label.go-back=Gehe zur\u00fcck zur 5 | label.home=Startseite 6 | label.or-to=oder zur 7 | label.previous-page=vorherigen Seite 8 | 9 | 10 | #403 11 | label.403.header=403 12 | label.403.message=Zugriff verweigert! Sie haben keinen Zugriff auf die angeforderte Seite. 13 | 14 | #404 15 | label.404.header=404 16 | label.404.message=Hoppla! Seite nicht gefunden 17 | 18 | #500 19 | label.500.header=500 20 | label.500.message=Hoppla! Da ist was schief gegangen 21 | label.500.title=Unerwarteter Fehler 22 | label.500.detail=Details 23 | 24 | #expired 25 | label.expired.title=Ansicht abgelaufen 26 | label.expired.message= Die angeforderte Seite konnte nicht wiederhergestellt werden. 27 | label.expired.click-here= Klicken Sie hier um die Seite neu zu laden. 28 | 29 | #optimistic 30 | label.optimistic.title=Datensatz bereits aktualisiert 31 | label.optimistic.message= Der angeforderte Datensatz wurde bereits durch einen anderen Benutzer aktualisiert. 32 | label.optimistic.click-here= Klicken Sie hier um den Datensatz erneut von der Datenbank zu laden. 33 | 34 | #controlsidebar 35 | controlsidebar.header=Layout Optionen 36 | controlsidebar.label.restore-defaults=Standardwerte wiederherstellen 37 | controlsidebar.label.menu-horientation=Men\u00fc links 38 | controlsidebar.txt.menu-horientation=Wechsle Men\u00fcausrichtung zwischen links und oben. 39 | controlsidebar.label.fixed-layout=Festes Layout 40 | controlsidebar.txt.fixed-layout=Wenn ausgew\u00e4hlt dann wird die obere Leiste fixiert. 41 | controlsidebar.label.boxed-layout=Kasten Layout 42 | controlsidebar.txt.boxed-layout=Aktiviere das Katen Layout 43 | controlsidebar.label.sidebar-collapsed=eingeklappte Seitenleiste 44 | controlsidebar.txt.sidebar-collapsed=Wenn ausgew\u00e4hlt, dann wird die Seitenleiste eingeklappt. 45 | controlsidebar.label.sidebar-expand-hover=Seitenleiste ausklappen durch Mausover. 46 | controlsidebar.txt.sidebar-expand-hover=Wenn ausgew\u00e4hlt, dann wir die linke Leiste durch Mausover ausgeklappt. 47 | controlsidebar.label.sidebar-slide=Kontrollleiste fixiert 48 | controlsidebar.txt.sidebar-slide=Wenn ausgew\u00e4hlt, dann wird die Kontrollleiste fixiert. 49 | controlsidebar.label.sidebar-skin=Dunkle Ansicht der Seitenleiste 50 | controlsidebar.txt.sidebar-skin=Wenn ausgew\u00e4hlt, dann wird die Seitenleiste dunkel dargestellt - sonst wird die helle Darstellung benutzt. 51 | controlsidebar.header.skins=Skins 52 | 53 | 54 | #menu search 55 | menu.search.placeholder=Suche im Men\u00fc... 56 | -------------------------------------------------------------------------------- /src/main/resources/admin_en_US.properties: -------------------------------------------------------------------------------- 1 | #general 2 | admin.version=${project.version} 3 | admin.legacy=${admin.legacy} 4 | label.go-back=Go back to 5 | label.home=Home 6 | label.or-to=or to 7 | label.previous-page=previous page 8 | 9 | #403 10 | label.403.header=403 11 | label.403.message=Access denied! You do not have access to the requested page. 12 | 13 | #404 14 | label.404.header=404 15 | label.404.message=Oops! Page not found 16 | 17 | #500 18 | label.500.header=500 19 | label.500.message=Oops! Something went wrong 20 | label.500.title=Unexpected error 21 | label.500.detail=Details 22 | 23 | #expired 24 | label.expired.title=View expired 25 | label.expired.message= The requested page could not be recovered. 26 | label.expired.click-here= Click here to reload the page. 27 | 28 | #optimistic 29 | label.optimistic.title=Record already updated 30 | label.optimistic.message= The requested record has been already updated by another user. 31 | label.optimistic.click-here= Click here to reload the updated record from database. 32 | 33 | #controlsidebar 34 | controlsidebar.header=Layout Options 35 | controlsidebar.label.restore-defaults=Restore defaults 36 | controlsidebar.label.menu-horientation=Left menu layout 37 | controlsidebar.txt.menu-horientation=Toggle menu orientation between left and top menu. 38 | controlsidebar.label.fixed-layout=Fixed Layout 39 | controlsidebar.txt.fixed-layout=Activate the fixed layout, if checked the top bar will be fixed on the page. 40 | controlsidebar.label.boxed-layout=Boxed Layout 41 | controlsidebar.txt.boxed-layout=Activate the boxed layout. 42 | controlsidebar.label.sidebar-collapsed=Collapsed Sidebar 43 | controlsidebar.txt.sidebar-collapsed=If checked the sidebar menu will be collapsed. 44 | controlsidebar.label.sidebar-expand-hover=Sidebar Expand on Hover 45 | controlsidebar.txt.sidebar-expand-hover=If checked the left sidebar will expand on hover. 46 | controlsidebar.label.sidebar-slide=Control Sidebar fixed 47 | controlsidebar.txt.sidebar-slide=If checked control sidebar will be fixed on the page. 48 | controlsidebar.label.sidebar-skin=Dark Sidebar Skin 49 | controlsidebar.txt.sidebar-skin=If checked dark skin will be used for control sidebar, otherwise light skin will be used. 50 | controlsidebar.header.skins=Skins 51 | 52 | 53 | #menu search 54 | menu.search.placeholder=Search menu items... -------------------------------------------------------------------------------- /src/main/resources/admin_es_MX.properties: -------------------------------------------------------------------------------- 1 | #general 2 | admin.version=${project.version} 3 | admin.legacy=${admin.legacy} 4 | label.go-back=Regresar 5 | label.home=Inicio 6 | label.or-to=o para 7 | label.previous-page=pagina anterior 8 | 9 | #403 10 | label.403.header=403 11 | label.403.message=Accesso negado! Usted no tien acceso a la página solicitada. 12 | 13 | #404 14 | label.404.header=404 15 | label.404.message=Oops! Página no encontrada 16 | 17 | #500 18 | label.500.header=500 19 | label.500.message=Oops! Algo ocurrió mal 20 | label.500.title=Error inesperado 21 | label.500.detail=Detalles 22 | 23 | #expired 24 | label.expired.title=Página expirada 25 | label.expired.message=La página solicitada no puede ser recuperada. 26 | label.expired.click-here=Clic aquí para recargar la página. 27 | 28 | #optimistic 29 | label.optimistic.title=Registro ya actualizado 30 | label.optimistic.message=El registro solicitado ha sido actualizado por otro usuario. 31 | label.optimistic.click-here=Clic aquí para cargar el registro actualizado de la base de datos. 32 | 33 | #controlsidebar 34 | controlsidebar.header=Opciones de Layout 35 | controlsidebar.label.restore-defaults=Restaurar valores predeterminados 36 | controlsidebar.label.menu-horientation=Layout con menú lateral 37 | controlsidebar.txt.menu-horientation=Alternar la posición del menú entre lateral y horizontal. Por default se activa el menú lateral. 38 | controlsidebar.label.fixed-layout=Layout fijo 39 | controlsidebar.txt.fixed-layout=Activar el layout fijo, si está seleccionada, el menú quedara fijo en la página. Esta opción no puede ser utilizada con layout en caja. 40 | controlsidebar.label.boxed-layout=Layout cuadrado 41 | controlsidebar.txt.boxed-layout=Activar el layout cuadrado. 42 | controlsidebar.label.sidebar-collapsed=Alternar el menú lateral 43 | controlsidebar.txt.sidebar-collapsed=Expandir o contraer el menú lateral. 44 | controlsidebar.label.sidebar-expand-hover=Expandir el menú lateral con el mouse 45 | controlsidebar.txt.sidebar-expand-hover=Si se activa el menú lateral se expande al pasar el mouse. 46 | controlsidebar.label.sidebar-slide=Fijar el menú de control 47 | controlsidebar.txt.sidebar-slide=Si se activa el menú lateral quedará fijo en la página. 48 | controlsidebar.label.sidebar-skin=Tema del menú de control 49 | controlsidebar.txt.sidebar-skin=Alternar el menú de controloscuro y claro. Por default el tema oscuro está seleccionado. 50 | controlsidebar.header.skins=Temas 51 | 52 | #menu search 53 | menu.search.placeholder=Buscar elementos del menú ... -------------------------------------------------------------------------------- /src/main/resources/admin_pt_BR.properties: -------------------------------------------------------------------------------- 1 | #general 2 | admin.version=${project.version} 3 | admin.legacy=${admin.legacy} 4 | label.go-back=Voltar para 5 | label.home=Inicio 6 | label.or-to=ou para 7 | label.previous-page=página anterior 8 | 9 | #403 10 | label.403.header=403 11 | label.403.message=Acesso negado! Você não possui permissão para acessar a página. 12 | 13 | #404 14 | label.404.header=404 15 | label.404.message=Oops! Página não encontrada 16 | 17 | #500 18 | label.500.header=500 19 | label.500.message=Oops! Alguma coisa deu errado 20 | label.500.title=Erro inesperado 21 | label.500.detail=Detalhes 22 | 23 | #expired 24 | label.expired.title=Página expirada 25 | label.expired.message= A página solicitada não pôde ser recuperada. 26 | label.expired.click-here= Clique aqui para recarregar a página. 27 | 28 | #optimistic 29 | label.optimistic.title=Registro alterado 30 | label.optimistic.message=O registro solicitado foi alterado por outro usuário. 31 | label.optimistic.click-here= Clique aqui para recarregar o registro atualizado. 32 | 33 | #controlsidebar 34 | controlsidebar.header=Opções de layout 35 | controlsidebar.label.restore-defaults=Restaurar valores padrão 36 | controlsidebar.label.menu-horientation=Layout com menu lateral 37 | controlsidebar.txt.menu-horientation=Altera a posição do menu entre lateral e topo. Por padrão o menu é lateral. 38 | controlsidebar.label.fixed-layout=Layout fixo 39 | controlsidebar.txt.fixed-layout=Ativa o layout fixo onde a barra superior será fixa na página. 40 | controlsidebar.label.boxed-layout=Layout Boxed 41 | controlsidebar.txt.boxed-layout=Ativa o layout boxed. 42 | controlsidebar.label.sidebar-collapsed=Menu lateral recolhido 43 | controlsidebar.txt.sidebar-collapsed=Se marcada, esta opção deixa o menu lateral recolhido. 44 | controlsidebar.label.sidebar-expand-hover=Expandir menu lateral com o mouse 45 | controlsidebar.txt.sidebar-expand-hover=Se ativa, essa opção irá expandir o menu lateral com o passar do mouse. 46 | controlsidebar.label.sidebar-slide=Fixar menu de controle 47 | controlsidebar.txt.sidebar-slide=Se ativado, o menu de controle será fixado na página. 48 | controlsidebar.label.sidebar-skin=Tema do menu de controle 49 | controlsidebar.txt.sidebar-skin=Se ativado, o tema dark será usado para menu de controle, caso contrário o tema light será usado. 50 | controlsidebar.header.skins=Temas 51 | 52 | #menu search 53 | menu.search.placeholder=Buscar itens de menu... -------------------------------------------------------------------------------- /src/main/resources/admin_ru_RU.properties: -------------------------------------------------------------------------------- 1 | #general 2 | admin.version=1.0.0 3 | admin.legacy=false 4 | label.go-back=\u041D\u0430\u0437\u0430\u0434 5 | label.home=\u0414\u043E\u043C\u043E\u0439 6 | label.or-to=\u0438\u043B\u0438 \u043A 7 | label.previous-page = \u041F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0430\u044F \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 8 | 9 | # 403 10 | label.403.header = 403 11 | label.403.message = \u0414\u043E\u0441\u0442\u0443\u043F \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D! \u0423 \u0432\u0430\u0441 \u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0437\u0430\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043C\u043E\u0439 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435. 12 | 13 | # 404 14 | label.404.header = 404 15 | label.404.message = \u0423\u043F\u0441! \u0421\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 16 | 17 | # 500 18 | label.500.header = 500 19 | label.500.message = \u041E\u0439-\u0435\u0439! \u0427\u0442\u043E-\u0442\u043E \u043F\u043E\u0448\u043B\u043E \u043D\u0435 \u0442\u0430\u043A 20 | label.500.title = \u041D\u0435\u043F\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043D\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430 21 | label.500.detail = \u041F\u043E\u0434\u0440\u043E\u0431\u043D\u043E\u0441\u0442\u0438 22 | 23 | #expired 24 | label.expired.title=\u0421\u0440\u043E\u043A \u0445\u0440\u0430\u043D\u0435\u043D\u0438\u044F \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0438\u0441\u0442\u0435\u043A 25 | label.expired.message=\u0417\u0430\u043F\u0440\u043E\u0448\u0435\u043D\u043D\u0430\u044F \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 26 | label.expired.click-here = \u041D\u0430\u0436\u043C\u0438\u0442\u0435 \u0437\u0434\u0435\u0441\u044C, \u0447\u0442\u043E\u0431\u044B \u043F\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443. 27 | 28 | #optimistic 29 | label.optimistic.title = \u0417\u0430\u043F\u0438\u0441\u044C \u0443\u0436\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0430 30 | label.optimistic.message = \u0417\u0430\u043F\u0440\u043E\u0448\u0435\u043D\u043D\u0430\u044F \u0437\u0430\u043F\u0438\u0441\u044C \u0443\u0436\u0435 \u0431\u044B\u043B\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u0434\u0440\u0443\u0433\u0438\u043C \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0435\u043C. 31 | label.optimistic.click-here = \u041D\u0430\u0436\u043C\u0438\u0442\u0435 \u0437\u0434\u0435\u0441\u044C, \u0447\u0442\u043E\u0431\u044B \u043F\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u0443\u044E \u0437\u0430\u043F\u0438\u0441\u044C \u0438\u0437 \u0431\u0430\u0437\u044B \u0434\u0430\u043D\u043D\u044B\u0445. 32 | 33 | #controlsidebar 34 | controlsidebar.header = \u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B \u043C\u0430\u043A\u0435\u0442\u0430 35 | controlsidebar.label.restore-defaults = \u0412\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E 36 | controlsidebar.label.menu-horientation = \u041B\u0435\u0432\u044B\u0439 \u043C\u0430\u043A\u0435\u0442 \u043C\u0435\u043D\u044E 37 | controlsidebar.txt.menu-horientation=\u041F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043E\u0440\u0438\u0435\u043D\u0442\u0430\u0446\u0438\u044E \u043C\u0435\u043D\u044E \u043C\u0435\u0436\u0434\u0443 \u043C\u0435\u043D\u044E \u0441\u043B\u0435\u0432\u0430 \u0438 \u0441\u0432\u0435\u0440\u0445\u0443 . 38 | controlsidebar.label.fixed-layout = \u0444\u0438\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u043C\u0430\u043A\u0435\u0442 39 | controlsidebar.txt.fixed-layout = \u0410\u043A\u0442\u0438\u0432\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0444\u0438\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u043C\u0430\u043A\u0435\u0442, \u0435\u0441\u043B\u0438 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u0444\u043B\u0430\u0436\u043E\u043A, \u0432\u0435\u0440\u0445\u043D\u044F\u044F \u043F\u0430\u043D\u0435\u043B\u044C \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0444\u0438\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u043D\u0430 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435. 40 | controlsidebar.label.boxed-layout=\u0411\u043B\u043E\u0447\u043D\u0430\u044F \u043C\u043E\u0434\u0435\u043B\u044C 41 | controlsidebar.txt.boxed-layout=\u0410\u043A\u0442\u0438\u0432\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432 \u0431\u043B\u043E\u0447\u043D\u043E\u0439 \u043C\u043E\u0434\u0435\u043B\u0438. 42 | controlsidebar.label.sidebar-collapsed = \u0421\u0432\u0435\u0440\u043D\u0443\u0442\u0430\u044F \u0431\u043E\u043A\u043E\u0432\u0430\u044F \u043F\u0430\u043D\u0435\u043B\u044C 43 | controlsidebar.txt.sidebar-collapsed = \u0415\u0441\u043B\u0438 \u0444\u043B\u0430\u0436\u043E\u043A \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D, \u0431\u043E\u043A\u043E\u0432\u043E\u0435 \u043C\u0435\u043D\u044E \u0431\u0443\u0434\u0435\u0442 \u0441\u0432\u0435\u0440\u043D\u0443\u0442\u043E. 44 | controlsidebar.label.sidebar-expand-hover=\u0420\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0431\u043E\u043A\u043E\u0432\u0430\u044F \u043F\u0430\u043D\u0435\u043B\u044C \u043F\u0440\u0438 \u043D\u0430\u0432\u0435\u0434\u0435\u043D\u0438\u0438 45 | controlsidebar.txt.sidebar-expand-hover=\u0415\u0441\u043B\u0438 \u043E\u0442\u043C\u0435\u0447\u0435\u043D\u043E, \u043B\u0435\u0432\u0430\u044F \u0431\u043E\u043A\u043E\u0432\u0430\u044F \u043F\u0430\u043D\u0435\u043B\u044C \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u0432\u043E\u0440\u0430\u0447\u0438\u0432\u0430\u0442\u044C\u0441\u044F \u043F\u0440\u0438 \u043D\u0430\u0432\u0435\u0434\u0435\u043D\u0438\u0438 \u043A\u0443\u0440\u0441\u043E\u0440\u0430. 46 | controlsidebar.label.sidebar-slide = \u0411\u043E\u043A\u043E\u0432\u0430\u044F \u043F\u0430\u043D\u0435\u043B\u044C \u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F \u0437\u0430\u0444\u0438\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u043D\u0430 47 | controlsidebar.txt.sidebar-slide = \u0415\u0441\u043B\u0438 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u0444\u043B\u0430\u0436\u043E\u043A, \u0431\u043E\u043A\u043E\u0432\u0430\u044F \u043F\u0430\u043D\u0435\u043B\u044C \u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0444\u0438\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u043D\u0430 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435. 48 | controlsidebar.label.sidebar-skin=\u0422\u0435\u043C\u043D\u0430\u044F \u0442\u0435\u043C\u0430 \u0431\u043E\u043A\u043E\u0432\u043E\u0439 \u043F\u0430\u043D\u0435\u043B\u0438 49 | controlsidebar.txt.sidebar-skin=\u0415\u0441\u043B\u0438 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u044D\u0442\u043E\u0442 \u0444\u043B\u0430\u0436\u043E\u043A, \u0442\u0435\u043C\u043D\u0430\u044F \u0442\u0435\u043C\u0430 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C\u0441\u044F \u0434\u043B\u044F \u0431\u043E\u043A\u043E\u0432\u043E\u0439 \u043F\u0430\u043D\u0435\u043B\u0438 \u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F, \u0432 \u043F\u0440\u043E\u0442\u0438\u0432\u043D\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435 \u0441\u0432\u0435\u0442\u043B\u0430\u044F . 50 | controlsidebar.header.skins=\u0422\u0435\u043C\u044B 51 | 52 | #menu search 53 | menu.search.placeholder = \u0418\u0441\u043A\u0430\u0442\u044C \u0432 \u043C\u0435\u043D\u044E ... 54 | -------------------------------------------------------------------------------- /src/main/resources/admin_zh_CN.properties: -------------------------------------------------------------------------------- 1 | #general 2 | admin.version=${project.version} 3 | admin.legacy=${admin.legacy} 4 | label.go-back=\u8FD4\u56DE 5 | label.home=\u4E3B\u9875 6 | 7 | #403 8 | label.403.header=403 9 | label.403.message=\u62D2\u7EDD\u8BBF\u95EE! \u60A8\u6CA1\u6709\u8BE5\u9875\u9762\u7684\u8BBF\u95EE\u6743\u9650 10 | 11 | #404 12 | label.404.header=404 13 | label.404.message=Oops! \u9875\u9762\u672A\u627E\u5230 14 | 15 | #500 16 | label.500.header=500 17 | label.500.message=Oops! \u7CFB\u7EDF\u51FA\u9519\u4E86 18 | label.500.title=\u672A\u77E5\u9519\u8BEF 19 | label.500.detail=\u8BE6\u60C5 20 | 21 | #expired 22 | label.expired.title=\u9875\u9762\u8FC7\u671F 23 | label.expired.message=\u65E0\u6CD5\u6062\u590D\u8BF7\u6C42\u7684\u9875\u9762 24 | label.expired.click-here=\u70B9\u51FB\u6B64\u5904\u91CD\u65B0\u52A0\u8F7D 25 | 26 | #optimistic 27 | label.optimistic.title=\u8BB0\u5F55\u5DF2\u88AB\u66F4\u65B0 28 | label.optimistic.message=\u8BF7\u6C42\u7684\u8BB0\u5F55\u5DF2\u88AB\u53E6\u4E00\u7528\u6237\u66F4\u65B0 29 | label.optimistic.click-here=\u70B9\u51FB\u6B64\u5904\u4ECE\u6570\u636E\u5E93\u91CD\u65B0\u52A0\u8F7D 30 | 31 | #controlsidebar 32 | controlsidebar.header=\u5E03\u5C40 33 | controlsidebar.label.restore-defaults=\u6062\u590D\u9ED8\u8BA4\u503C 34 | controlsidebar.label.menu-horientation=\u5DE6\u4FA7\u83DC\u5355\u5E03\u5C40 35 | controlsidebar.txt.menu-horientation=\u5207\u6362\u83DC\u5355\u65B9\u5411 \u5DE6\u4FA7 \u9876\u90E8 36 | controlsidebar.label.fixed-layout=\u56FA\u5B9A\u5E03\u5C40 37 | controlsidebar.txt.fixed-layout=\u5982\u679C\u52FE\u9009\uFF0C\u83DC\u5355\u680F\u5C06\u5728\u9875\u9762\u4E0A\u56FA\u5B9A 38 | controlsidebar.label.boxed-layout=\u7BB1\u5F0F\u5E03\u5C40 39 | controlsidebar.txt.boxed-layout=\u6FC0\u6D3B\u7BB1\u5F0F\u5E03\u5C40 40 | controlsidebar.label.sidebar-collapsed=\u6298\u53E0\u8FB9\u680F 41 | controlsidebar.txt.sidebar-collapsed=\u5982\u679C\u52FE\u9009\uFF0C\u8FB9\u680F\u5C06\u88AB\u6298\u53E0 42 | controlsidebar.label.sidebar-expand-hover=\u60AC\u505C\u65F6\u5C55\u5F00\u8FB9\u680F 43 | controlsidebar.txt.sidebar-expand-hover=\u5982\u679C\u52FE\u9009\uFF0C\u5DE6\u4FA7\u8FB9\u680F\u5C06\u5728\u9F20\u6807\u60AC\u505C\u65F6\u5C55\u5F00 44 | controlsidebar.label.sidebar-slide=\u63A7\u5236\u8FB9\u680F\u56FA\u5B9A 45 | controlsidebar.txt.sidebar-slide=\u5982\u679C\u52FE\u9009\uFF0C\u63A7\u5236\u8FB9\u680F\u5C06\u56FA\u5B9A\u5728\u9875\u9762\u4E0A 46 | controlsidebar.label.sidebar-skin=\u63A7\u5236\u8FB9\u680F\u6697\u8272\u76AE\u80A4 47 | controlsidebar.txt.sidebar-skin=\u5982\u679C\u52FE\u9009\uFF0C\u63A7\u5236\u8FB9\u680F\u5C06\u4F7F\u7528\u6697\u8272 \u76AE\u80A4\uFF0C\u5426\u5219\u4F7F\u7528 \u4EAE\u8272 \u76AE\u80A4 48 | controlsidebar.header.skins=\u76AE\u80A4 49 | 50 | 51 | #menu search 52 | menu.search.placeholder=\u67E5\u627E\u83DC\u5355\u9879... -------------------------------------------------------------------------------- /src/main/resources/config/admin-config.properties: -------------------------------------------------------------------------------- 1 | admin.loginPage=login.xhtml 2 | admin.indexPage=index.xhtml 3 | admin.dateFormat= 4 | admin.breadcrumbSize=5 5 | admin.renderMessages=true 6 | admin.skipMessageDetailIfEqualsSummary=true 7 | admin.renderAjaxStatus=true 8 | admin.disableFilter=false 9 | admin.renderBreadCrumb=true 10 | admin.extensionLessUrls=false 11 | admin.enableSlideMenu=true 12 | admin.enableRipple=true 13 | admin.rippleMobileOnly=true 14 | admin.renderMenuSearch=true 15 | admin.renderControlSidebar=false 16 | admin.controlSidebar.showOnMobile=false 17 | admin.controlSidebar.leftMenuTemplate=true 18 | admin.controlSidebar.fixedLayout=false 19 | admin.controlSidebar.boxedLayout=false 20 | admin.controlSidebar.sidebarCollapsed=false 21 | admin.controlSidebar.expandOnHover=false 22 | admin.controlSidebar.fixed=false 23 | admin.controlSidebar.darkSkin=true 24 | admin.autoHideMessages=true 25 | admin.renderFormAsterisks=false 26 | admin.enableMobileHeader=true 27 | admin.closableLoading=true 28 | admin.messagesHideTimeout=2500 29 | admin.skin=skin-blue 30 | admin.autoShowNavbar=true 31 | admin.loadingImage=ajaxloadingbar.gif 32 | admin.iconsEffect=true 33 | admin.rippleElements=.ripplelink,button.ui-button:not([class*=ui-picklist]):not([class*=ui-orderlist]),li.ui-selectlistbox-item,li.ui-multiselectlistbox-item,.ui-selectonemenu-label,.ui-selectcheckboxmenu,\ 34 | .ui-autocomplete-dropdown, .ui-autocomplete-item, .ui-splitbutton-menubutton, .ui-splitbutton button.ui-button,.input-group, .ui-selectbooleanbutton, \ 35 | div.ui-button,.ui-chkbox-icon, .ui-link, .form-control, .btn, .ui-sortable-column,.ui-link, .ui-tabs-nav > li,.ui-selectonemenu-trigger, \ 36 | .ui-accordion-header, .treeview, .sidebar-toggle, .ui-radiobutton-icon, td[role="gridcell"], .ui-selectcheckboxmenu-trigger,.ui-paginator-page, \ 37 | .ui-panelmenu-header > a, a#layout-setup, .control-sidebar div#restore-defaults > a, .control-sidebar div.ui-selectbooleancheckbox .ui-chkbox, \ 38 | .control-sidebar span.control-sidebar-subheading > label, .control-sidebar a.skin-link, button.navbar-toggle, li.dropdown > a 39 | -------------------------------------------------------------------------------- /template-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adminfaces/admin-template/cd8bb5600464eaf2384f0a3fcabd78004919b64b/template-example.png --------------------------------------------------------------------------------