Dashboard app for ccm-admin

Jens Pelzetter 2020-08-28 14:49:38 +02:00
parent 982f4f1fd5
commit 680e708e37
23 changed files with 13719 additions and 10 deletions

View File

@ -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>

View File

@ -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?

View File

@ -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/).

View File

@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"]
};

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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,
}

View File

@ -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");

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -0,0 +1,4 @@
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}

View File

@ -0,0 +1,11 @@
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {}
});

View File

@ -0,0 +1,5 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View File

@ -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>

View File

@ -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"
]
}

View File

@ -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
}); });

View File

@ -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());

View File

@ -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();
}
} }

View File

@ -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

View File

@ -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>