Dashboard app for ccm-admin
parent
982f4f1fd5
commit
680e708e37
|
|
@ -476,6 +476,26 @@
|
||||||
<nodeVersion>${nodeVersion}</nodeVersion>
|
<nodeVersion>${nodeVersion}</nodeVersion>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>npm install ccm-admin-dashboard</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<arguments>install</arguments>
|
||||||
|
<workingDirectory>src/ccm-admin-dashboard</workingDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>build ccm-admin-dashboard</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<arguments>run build</arguments>
|
||||||
|
<workingDirectory>src/ccm-admin-dashboard</workingDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
<id>npm install ccm-admin-systeminformation</id>
|
<id>npm install ccm-admin-systeminformation</id>
|
||||||
<goals>
|
<goals>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# ccm-admin-dashboard
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
presets: ["@vue/cli-plugin-babel/preset"]
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"name": "ccm-admin-dashboard",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build --dest ../../target/generated-resources/_admin/dashboard",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bootstrap": "^4.5.2",
|
||||||
|
"bootstrap-vue": "^2.16.0",
|
||||||
|
"core-js": "^3.6.5",
|
||||||
|
"vue": "^2.6.11",
|
||||||
|
"vue-class-component": "^7.2.3",
|
||||||
|
"vue-property-decorator": "^8.4.2",
|
||||||
|
"vue-router": "^3.2.0",
|
||||||
|
"vuex": "^3.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||||
|
"@typescript-eslint/parser": "^2.33.0",
|
||||||
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-router": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||||
|
"@vue/cli-service": "~4.5.0",
|
||||||
|
"@vue/eslint-config-prettier": "^6.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^5.0.2",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
|
"eslint-plugin-prettier": "^3.1.3",
|
||||||
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
"node-sass": "^4.12.0",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"sass-loader": "^8.0.2",
|
||||||
|
"typescript": "~3.9.3",
|
||||||
|
"vue-template-compiler": "^2.6.11"
|
||||||
|
},
|
||||||
|
"vue": {
|
||||||
|
"filenameHashing": false,
|
||||||
|
"publicPath": "./"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/essential",
|
||||||
|
"eslint:recommended",
|
||||||
|
"@vue/typescript/recommended",
|
||||||
|
"@vue/prettier",
|
||||||
|
"@vue/prettier/@typescript-eslint"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2020
|
||||||
|
},
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not dead"
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<b-card-group deck>
|
||||||
|
<b-card v-for="app in apps" :key="app.slug" :title="app.label">
|
||||||
|
<b-card-text>
|
||||||
|
{{ app.description }}
|
||||||
|
</b-card-text>
|
||||||
|
</b-card>
|
||||||
|
</b-card-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss"></style>
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||||
|
import { AdminApp } from "./admin-ui";
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class App extends Vue {
|
||||||
|
get apps(): AdminApp[] {
|
||||||
|
const appElem: Element | null = document.querySelector("#app");
|
||||||
|
if (appElem === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const appsValue: string | null = appElem.getAttribute("data-apps");
|
||||||
|
if (appsValue === null) {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
return JSON.parse(appsValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
export interface AdminApp {
|
||||||
|
slug: String,
|
||||||
|
label: string,
|
||||||
|
description: string,
|
||||||
|
order: number,
|
||||||
|
jsFilesUrls: string[],
|
||||||
|
cssFilesUrls: string[],
|
||||||
|
iconName: string | undefined,
|
||||||
|
symbolUrl: string | undefined,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import router from "./router";
|
||||||
|
import store from "./store";
|
||||||
|
|
||||||
|
import {BootstrapVue, IconsPlugin} from "bootstrap-vue";
|
||||||
|
|
||||||
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
|
Vue.use(BootstrapVue);
|
||||||
|
Vue.use(IconsPlugin);
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
router,
|
||||||
|
store,
|
||||||
|
render: h => h(App)
|
||||||
|
}).$mount("#app");
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
import VueRouter, { RouteConfig } from "vue-router";
|
||||||
|
import Home from "../views/Home.vue";
|
||||||
|
|
||||||
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
|
const routes: Array<RouteConfig> = [
|
||||||
|
// {
|
||||||
|
// path: "/",
|
||||||
|
// name: "Home",
|
||||||
|
// component: Home
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: "/about",
|
||||||
|
// name: "About",
|
||||||
|
// // route level code-splitting
|
||||||
|
// // this generates a separate chunk (about.[hash].js) for this route
|
||||||
|
// // which is lazy-loaded when the route is visited.
|
||||||
|
// component: () =>
|
||||||
|
// import(/* webpackChunkName: "about" */ "../views/About.vue")
|
||||||
|
// }
|
||||||
|
];
|
||||||
|
|
||||||
|
const router = new VueRouter({
|
||||||
|
mode: "hash",
|
||||||
|
routes
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Vue, { VNode } from "vue";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace JSX {
|
||||||
|
// tslint:disable no-empty-interface
|
||||||
|
interface Element extends VNode {}
|
||||||
|
// tslint:disable no-empty-interface
|
||||||
|
interface ElementClass extends Vue {}
|
||||||
|
interface IntrinsicElements {
|
||||||
|
[elem: string]: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
declare module "*.vue" {
|
||||||
|
import Vue from "vue";
|
||||||
|
export default Vue;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
import Vuex from "vuex";
|
||||||
|
|
||||||
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
export default new Vuex.Store({
|
||||||
|
state: {},
|
||||||
|
mutations: {},
|
||||||
|
actions: {},
|
||||||
|
modules: {}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<div class="about">
|
||||||
|
<h1>This is an about page</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<template>
|
||||||
|
<div class="home">
|
||||||
|
<img alt="Vue logo" src="../assets/logo.png" />
|
||||||
|
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
|
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
HelloWorld
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export default class Home extends Vue {}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"importHelpers": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"types": [
|
||||||
|
"webpack-env"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"tests/**/*.ts",
|
||||||
|
"tests/**/*.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -14,16 +14,12 @@ const routes: Array<RouteConfig> = [
|
||||||
{
|
{
|
||||||
path: "/about",
|
path: "/about",
|
||||||
name: "About",
|
name: "About",
|
||||||
// route level code-splitting
|
|
||||||
// this generates a separate chunk (about.[hash].js) for this route
|
|
||||||
// which is lazy-loaded when the route is visited.
|
|
||||||
component: About
|
component: About
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
mode: "hash",
|
mode: "hash",
|
||||||
base: process.env.BASE_URL,
|
|
||||||
routes
|
routes
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import freemarker.template.TemplateException;
|
||||||
import freemarker.template.TemplateExceptionHandler;
|
import freemarker.template.TemplateExceptionHandler;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.libreccm.api.JsonArrayCollector;
|
||||||
import org.libreccm.security.Shiro;
|
import org.libreccm.security.Shiro;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -39,6 +40,8 @@ import java.util.Map;
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.enterprise.context.RequestScoped;
|
import javax.enterprise.context.RequestScoped;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.json.Json;
|
||||||
|
import javax.json.JsonObjectBuilder;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.NotFoundException;
|
import javax.ws.rs.NotFoundException;
|
||||||
|
|
@ -160,6 +163,15 @@ public class AdminUi {
|
||||||
|
|
||||||
final Map<String, Object> data = new HashMap<>();
|
final Map<String, Object> data = new HashMap<>();
|
||||||
data.put("adminUiApps", adminUiApps.getAdminUiApps());
|
data.put("adminUiApps", adminUiApps.getAdminUiApps());
|
||||||
|
data.put(
|
||||||
|
"adminUiAppsJson",
|
||||||
|
adminUiApps
|
||||||
|
.getAdminUiApps()
|
||||||
|
.stream()
|
||||||
|
.map(AdminUiApp::buildJson)
|
||||||
|
.map(JsonObjectBuilder::build)
|
||||||
|
.collect(new JsonArrayCollector())
|
||||||
|
);
|
||||||
data.put("activeApp", app);
|
data.put("activeApp", app);
|
||||||
data.put("currentUser", shiro.getUser().get());
|
data.put("currentUser", shiro.getUser().get());
|
||||||
data.put("contextPath", servletContext.getContextPath());
|
data.put("contextPath", servletContext.getContextPath());
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,14 @@
|
||||||
*/
|
*/
|
||||||
package org.libreccm.ui.admin;
|
package org.libreccm.ui.admin;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import javax.json.Json;
|
||||||
|
import javax.json.JsonObject;
|
||||||
|
import javax.json.JsonObjectBuilder;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
||||||
|
|
@ -88,4 +94,27 @@ public interface AdminUiApp {
|
||||||
*/
|
*/
|
||||||
Optional<String> getSymbolUrl();
|
Optional<String> getSymbolUrl();
|
||||||
|
|
||||||
|
default JsonObjectBuilder buildJson() {
|
||||||
|
return Json
|
||||||
|
.createObjectBuilder()
|
||||||
|
.add("slug", getSlug())
|
||||||
|
.add("label", getLabel())
|
||||||
|
.add("description", getDescription())
|
||||||
|
.add("order", getOrder())
|
||||||
|
.add(
|
||||||
|
"jsFilesUrls",
|
||||||
|
Json.createArrayBuilder(Arrays.asList(getJsFilesUrls()))
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
"cssFilesurls",
|
||||||
|
Json.createArrayBuilder(Arrays.asList(getCssFilesUrls()))
|
||||||
|
)
|
||||||
|
.add("iconName", getIconName().orElse(null))
|
||||||
|
.add("symbolUrl", getSymbolUrl().orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
default JsonObject toJson() {
|
||||||
|
return buildJson().build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,12 +69,18 @@ public class DashboardApp implements AdminUiApp {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getJsFilesUrls() {
|
public String[] getJsFilesUrls() {
|
||||||
return new String[]{};
|
return new String[]{
|
||||||
|
"/_admin/systeminformation/js/chunk-vendors.js",
|
||||||
|
"/_admin/systeminformation/js/app.js"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getCssFilesUrls() {
|
public String[] getCssFilesUrls() {
|
||||||
return new String[]{};
|
return new String[]{
|
||||||
|
"/_admin/systeminformation/css/chunk-vendors.css",
|
||||||
|
"/_admin/systeminformation/css/app.css"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<title>LibreCCM - ${activeApp.label}</title>
|
<title>LibreCCM - ${activeApp.label}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app" data-apps="${adminUiAppsJson}"></div>
|
||||||
<#list activeApp.jsFilesUrls as jsFile>
|
<#list activeApp.jsFilesUrls as jsFile>
|
||||||
<script src="${contextPath}${jsFile}"></script>
|
<script src="${contextPath}${jsFile}"></script>
|
||||||
</#list>
|
</#list>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue