Skip to content
This repository was archived by the owner on Apr 10, 2021. It is now read-only.

Commit 5ffa2e4

Browse files
authored
Dashboard (#138)
Dashboard
2 parents a24f055 + b4b4f72 commit 5ffa2e4

25 files changed

+365
-35
lines changed

raw-cms-app/src/config/router.js

+8
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,14 @@ const _router = new VueRouter({
209209
},
210210
],
211211
},
212+
{
213+
path: '/about',
214+
name: 'about',
215+
component: async (res, rej) => {
216+
const cmp = await import('/modules/core/views/about-view/about-view.js');
217+
await cmp.default(res, rej);
218+
},
219+
},
212220
],
213221
});
214222

raw-cms-app/src/config/vue-chartjs.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const _vueChartJs = window.VueChartJs;
2+
3+
export const vueChartJs = _vueChartJs;
4+
export const mixins = _vueChartJs.mixins;
5+
export const Pie = _vueChartJs.Pie;

raw-cms-app/src/config/vuetify.js

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ const _vuetify = new Vuetify({
1313
});
1414

1515
export const vuetify = _vuetify;
16+
export const vuetifyColors = colors;

raw-cms-app/src/index.html

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
<script src="https://cdn.jsdelivr.net/combine/npm/[email protected]/dist/validators.min.js,npm/[email protected]/dist/vuelidate.min.js"></script>
6868
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/lib/epic-spinners.min.js"></script>
6969
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-formly.min.js"></script>
70+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script>
71+
<script src="https://unpkg.com/vue-chartjs/dist/vue-chartjs.min.js"></script>
72+
<script src="https://d3js.org/d3-color.v1.min.js"></script>
73+
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script>
7074
<script src="https://unpkg.com/[email protected]/dist/vue-monaco.js"></script>
7175
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.18.0/min/vs/loader.js"></script>
7276

raw-cms-app/src/modules/core/assets/i18n/i18n.en.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,12 @@
4949
"title": "Entities"
5050
},
5151
"home": {
52-
"helpText": "Welcome! Please select a menu entry on the left."
52+
"apiCallsText": "API calls (last week):",
53+
"entitiesNumText": "Entities number",
54+
"recordsQuotaText": "Records quota",
55+
"title": "Home",
56+
"totalRecordsText": "Total records",
57+
"welcomeText": "Welcome back! Here you can find some insights about your app."
5358
},
5459
"lambdas": {
5560
"deleteConfirmMsgTpl": "Are you sure you want to delete lambda {name}?",
-97 Bytes
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { optionalChain } from '../../../../utils/object.utils.js';
2+
import { SimplePieChart } from '../../../shared/components/charts/simple-pie-chart/simple-pie-chart.js';
3+
import { dashboardService } from '../../services/dashboard.service.js';
4+
5+
const _DashboardDef = async () => {
6+
const tpl = await RawCMS.loadComponentTpl(
7+
'/modules/core/components/dashboard/dashboard.tpl.html'
8+
);
9+
10+
return {
11+
components: {
12+
PieChart: SimplePieChart,
13+
},
14+
computed: {
15+
totalRecordsNum: function() {
16+
const quotasObj = optionalChain(() => this.info.recordQuotas);
17+
if (quotasObj === undefined) {
18+
return undefined;
19+
}
20+
21+
return Object.keys(quotasObj)
22+
.map(x => quotasObj[x])
23+
.reduce((acc, v) => acc + v, 0);
24+
},
25+
recordQuotasChartData: function() {
26+
const quotasObj = optionalChain(() => this.info.recordQuotas, { fallbackValue: {} });
27+
const labels = [];
28+
const data = [];
29+
Object.keys(quotasObj).forEach(x => {
30+
labels.push(x);
31+
data.push(quotasObj[x]);
32+
});
33+
34+
return { data, labels };
35+
},
36+
},
37+
created: async function() {
38+
this.info = await this.dashboardService.getDashboardInfo();
39+
this.isLoading = false;
40+
},
41+
data: function() {
42+
return {
43+
chartOptions: {
44+
lowerIsBetter: true,
45+
},
46+
dashboardService: dashboardService,
47+
isLoading: true,
48+
info: undefined,
49+
optionalChain: optionalChain,
50+
};
51+
},
52+
template: tpl,
53+
};
54+
};
55+
56+
const _Dashboard = async (res, rej) => {
57+
const cmpDef = await _DashboardDef();
58+
res(cmpDef);
59+
};
60+
61+
export const DashboardDef = _DashboardDef;
62+
export const Dashboard = _Dashboard;
63+
export default _Dashboard;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<v-row>
2+
<v-col cols="3">
3+
<v-card tile height="300" :loading="isLoading" class="mb-4">
4+
<v-card-title>
5+
{{ $t('core.home.totalRecordsText') }}
6+
</v-card-title>
7+
<v-card-text class="display-4 font-weight-black">
8+
{{ totalRecordsNum }}
9+
</v-card-text>
10+
</v-card>
11+
<v-card tile height="300" :loading="isLoading">
12+
<v-card-title>
13+
{{ $t('core.home.entitiesNumText') }}
14+
</v-card-title>
15+
<v-card-text class="display-4 font-weight-black">
16+
{{ optionalChain(() => this.info.entitiesNum) }}
17+
</v-card-text>
18+
</v-card>
19+
</v-col>
20+
<v-col cols="9">
21+
<v-card tile height="616" :loading="isLoading">
22+
<v-card-title>
23+
{{ $t('core.home.recordsQuotaText') }}
24+
</v-card-title>
25+
<v-card-text class="d-flex justify-center" style="height:552px">
26+
<pie-chart :context="recordQuotasChartData" :options="chartOptions" />
27+
</v-card-text>
28+
</v-card>
29+
</v-col>
30+
</v-row>

raw-cms-app/src/modules/core/components/left-menu/left-menu.js

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const _LeftMenu = async (resolve, reject) => {
2424
isVisible: false,
2525
isUserMenuVisible: false,
2626
items: [
27+
{ icon: 'mdi-home', text: 'Home', route: 'home' },
2728
{ icon: 'mdi-account', text: 'Users', route: 'users' },
2829
{ icon: 'mdi-cube', text: 'Entities', route: 'entities' },
2930
{ icon: 'mdi-book-open', text: 'Collections', route: 'collections' },
@@ -35,6 +36,7 @@ const _LeftMenu = async (resolve, reject) => {
3536
extLink: RawCMS.env.api.baseUrl,
3637
},
3738
],
39+
bottomItem: { icon: 'mdi-information', text: 'About', route: 'about' },
3840
};
3941
},
4042
methods: {

raw-cms-app/src/modules/core/components/left-menu/left-menu.tpl.html

+16
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,20 @@
103103
</v-list-item>
104104
</template>
105105
</v-list>
106+
107+
<template v-slot:append>
108+
<v-list dense>
109+
<v-list-item
110+
:class="{ 'router-link-active': isActive(bottomItem)}"
111+
@click.stop="goTo(bottomItem)"
112+
>
113+
<v-list-item-action>
114+
<v-icon>{{ bottomItem.icon }}</v-icon>
115+
</v-list-item-action>
116+
<v-list-item-content>
117+
<v-list-item-title>{{ bottomItem.text }}</v-list-item-title>
118+
</v-list-item-content>
119+
</v-list-item>
120+
</v-list>
121+
</template>
106122
</v-navigation-drawer>

raw-cms-app/src/modules/core/components/top-bar/top-bar.tpl.html

+4
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,8 @@
55
</v-toolbar-title>
66

77
<v-spacer></v-spacer>
8+
<div class="flex-grow-1"></div>
9+
<v-avatar tile>
10+
<img src="/modules/core/assets/rawlogo_small.png" alt="RawCMS Logo" />
11+
</v-avatar>
812
</v-app-bar>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { sleep } from '../../../utils/time.utils.js';
2+
import { BaseApiService } from '../../shared/services/base-api-service.js';
3+
4+
class DashboardService extends BaseApiService {
5+
constructor() {
6+
super();
7+
}
8+
9+
async getDashboardInfo() {
10+
// FIXME: For now we use mock data
11+
12+
await sleep(5000);
13+
14+
const quota = {
15+
TEST: Math.floor(Math.random() * 100),
16+
Items1: Math.floor(Math.random() * 100),
17+
Items2: Math.floor(Math.random() * 100),
18+
Items3: Math.floor(Math.random() * 100),
19+
Items4: Math.floor(Math.random() * 100),
20+
Items5: Math.floor(Math.random() * 100),
21+
Items6: Math.floor(Math.random() * 100),
22+
};
23+
return {
24+
recordQuotas: quota,
25+
entitiesNum: Object.keys(quota).length,
26+
lastWeekCallsNum: Math.floor(Math.random() * 500),
27+
};
28+
}
29+
}
30+
31+
export const dashboardService = new DashboardService();
32+
export default dashboardService;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const _AboutView = async (res, rej) => {
2+
const tpl = await RawCMS.loadComponentTpl('/modules/core/views/about-view/about-view.tpl.html');
3+
4+
res({
5+
template: tpl,
6+
});
7+
};
8+
9+
export const AboutView = _AboutView;
10+
export default _AboutView;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<v-container class="fill-height" fluid>
2+
<v-row align="center" justify="center">
3+
<img src="/modules/core/assets/rawlogo.png" />
4+
</v-row>
5+
</v-container>

raw-cms-app/src/modules/core/views/home-view/home-view.js

+10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1+
import { vuexStore } from '../../../../config/vuex.js';
2+
import { DashboardDef } from '../../components/dashboard/dashboard.js';
3+
14
const _HomeView = async (res, rej) => {
25
const tpl = await RawCMS.loadComponentTpl('/modules/core/views/home-view/home-view.tpl.html');
6+
const dashboardDef = await DashboardDef();
37

48
res({
9+
components: {
10+
Dashboard: dashboardDef,
11+
},
12+
mounted() {
13+
vuexStore.dispatch('core/updateTopBarTitle', this.$t('core.home.title'));
14+
},
515
template: tpl,
616
});
717
};
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
<v-container class="fill-height" fluid>
2-
<v-row align="center" justify="center">
3-
<img src="/modules/core/assets/rawlogo.png" />
1+
<v-container fluid>
2+
<v-row align="start">
3+
<v-col>
4+
{{ $t('core.home.welcomeText') }}
5+
<dashboard></dashboard>
6+
</v-col>
47
</v-row>
58
</v-container>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { vuetifyColors } from '../../../../config/vuetify.js';
2+
import { optionalChain } from '../../../../utils/object.utils.js';
3+
4+
const _transparentize = function(color, opacity) {
5+
const alpha = opacity === undefined ? 0.5 : 1 - opacity;
6+
return Color(color)
7+
.alpha(alpha)
8+
.rgbString();
9+
};
10+
11+
const _colorize = function(value, { range = [0, 100], lowerIsBetter = false } = {}) {
12+
const min = optionalChain(() => range[0], 0);
13+
const max = optionalChain(() => range[1], 100);
14+
let colors = [vuetifyColors.red.darken4, vuetifyColors.orange.base, vuetifyColors.green.base];
15+
if (lowerIsBetter) {
16+
colors = colors.reverse();
17+
}
18+
const colorMap = d3.piecewise(d3.interpolate, colors);
19+
const interpolationValue = (value - min) / (max - min);
20+
21+
const c = colorMap(interpolationValue);
22+
return c;
23+
};
24+
25+
export const colorize = _colorize;
26+
export const transparentize = _transparentize;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Pie } from '../../../../../config/vue-chartjs.js';
2+
import { optionalChain } from '../../../../../utils/object.utils.js';
3+
import { colorize, transparentize } from '../charts.utils.js';
4+
5+
const _defaultChartOptions = {
6+
lowerIsBetter: false,
7+
};
8+
9+
const _SimplePieChart = {
10+
computed: {
11+
chartData: function() {
12+
const data = this.sortedData;
13+
const backColors = data.map(x => this.normalColorize(x));
14+
15+
const res = {
16+
datasets: [
17+
{
18+
data: data,
19+
backgroundColor: backColors,
20+
hoverColor: backColors.map(x => this.hoverColorize(x)),
21+
},
22+
],
23+
labels: this.context.labels,
24+
};
25+
26+
return res;
27+
},
28+
sortedData: function() {
29+
let data = optionalChain(() => [...this.context.data], { fallbackValue: [] }).sort(
30+
(a, b) => a - b
31+
);
32+
33+
if (this.options.lowerIsBetter) {
34+
data = data.reverse();
35+
}
36+
37+
return data;
38+
},
39+
},
40+
extends: Pie,
41+
methods: {
42+
normalColorize: function(value) {
43+
const data = this.sortedData;
44+
const min = Math.min(...data) || 0;
45+
const max = Math.max(...data) || 100;
46+
return colorize(value, { range: [min, max], lowerIsBetter: this.options.lowerIsBetter });
47+
},
48+
hoverColorize: function(color) {
49+
return transparentize(color);
50+
},
51+
refresh: function() {
52+
this.styles = { width: '100%', height: '100%', position: 'relative', ...this.styles };
53+
this.renderChart(this.chartData, {
54+
maintainAspectRatio: false,
55+
});
56+
},
57+
},
58+
mounted() {
59+
this.refresh();
60+
},
61+
props: {
62+
context: {
63+
type: Object,
64+
default: {
65+
data: [],
66+
labels: [],
67+
},
68+
},
69+
options: {
70+
type: Object,
71+
default: _defaultChartOptions,
72+
},
73+
},
74+
watch: {
75+
context: function() {
76+
this.refresh();
77+
},
78+
},
79+
};
80+
81+
export const SimplePieChart = _SimplePieChart;
82+
export default _SimplePieChart;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { apiClient } from '../../core/api/api-client.js';
2+
3+
export class BaseApiService {
4+
_apiClient;
5+
6+
constructor() {
7+
this._apiClient = apiClient;
8+
}
9+
10+
_checkGenericError(axiosRes) {
11+
if (axiosRes.status !== 200) {
12+
return false;
13+
}
14+
15+
return true;
16+
}
17+
}

0 commit comments

Comments
 (0)