Notifications: all messages in chrono order; added timestamps and grouping (#1102)
This commit is contained in:
parent
e34877ff75
commit
eede2e8de9
10 changed files with 104 additions and 24 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
<link href="/css/bootstrap.min.css" rel="stylesheet" />
|
<link href="/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
<script src="/js/bootstrap.min.js"></script>
|
<script src="/js/bootstrap.min.js" async></script>
|
||||||
|
|
||||||
<link href="/css/app.css" rel="stylesheet" />
|
<link href="/css/app.css" rel="stylesheet" />
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,21 @@ window.app = new Vue({
|
||||||
methods: {
|
methods: {
|
||||||
raise: function (msg) {
|
raise: function (msg) {
|
||||||
console[msg.type](msg);
|
console[msg.type](msg);
|
||||||
const withoutThisMsg = this.notifications.filter((m) => m.message !== msg.message);
|
const now = new Date();
|
||||||
this.notifications = [msg, ...withoutThisMsg];
|
const latestMsg = this.notifications[0];
|
||||||
|
if (latestMsg && latestMsg.message === msg.message) {
|
||||||
|
latestMsg.count++;
|
||||||
|
latestMsg.time = now;
|
||||||
|
} else {
|
||||||
|
this.notifications = [
|
||||||
|
{
|
||||||
|
...msg,
|
||||||
|
count: 1,
|
||||||
|
time: now,
|
||||||
|
},
|
||||||
|
...this.notifications,
|
||||||
|
];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
clear: function () {
|
clear: function () {
|
||||||
this.notifications = [];
|
this.notifications = [];
|
||||||
|
|
|
||||||
|
|
@ -14,21 +14,53 @@ const Template = (args, { argTypes }) => ({
|
||||||
template: '<Notifications v-bind="$props"></Notifications>',
|
template: '<Notifications v-bind="$props"></Notifications>',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function timeAgo(hours = 0, minutes = 0, seconds = 0) {
|
||||||
|
const date = new Date();
|
||||||
|
date.setHours(date.getHours() - hours);
|
||||||
|
date.setMinutes(date.getMinutes() - minutes);
|
||||||
|
date.setSeconds(date.getSeconds() - seconds);
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
export const Base = Template.bind({});
|
export const Base = Template.bind({});
|
||||||
Base.args = {
|
Base.args = {
|
||||||
notifications: [
|
notifications: [
|
||||||
{
|
{
|
||||||
message: "Server unavailable",
|
message: "Server unavailable",
|
||||||
type: "error",
|
type: "error",
|
||||||
|
time: timeAgo(),
|
||||||
|
count: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: "charger out of sync: expected disabled, got enabled",
|
message: "charger out of sync: expected disabled, got enabled",
|
||||||
type: "warn",
|
type: "warn",
|
||||||
|
count: 4,
|
||||||
|
time: timeAgo(0, 0, 42),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Sponsortoken: x509: certificate has expired",
|
||||||
|
type: "error",
|
||||||
|
count: 1,
|
||||||
|
time: timeAgo(1, 12, 44),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "charger out of sync: expected disabled, got enabled",
|
||||||
|
type: "warn",
|
||||||
|
count: 4,
|
||||||
|
time: timeAgo(1, 22, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "vehicle remote charge start: invalid character '<' looking for beginning of value",
|
||||||
|
type: "warn",
|
||||||
|
count: 3,
|
||||||
|
time: timeAgo(4, 2, 0),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message:
|
message:
|
||||||
"Amet irure quis incididunt voluptate esse. Commodo ea sunt est ipsum tempor nisi laboris voluptate labore elit laborum. Ex irure commodo reprehenderit consequat consequat do ad tempor aliquip deserunt eu. Laboris minim nostrud quis nisi. Dolor occaecat reprehenderit velit dolore exercitation cupidatat et voluptate. Nulla pariatur deserunt esse minim nisi nisi nulla. Sit eiusmod do incididunt sint minim pariatur aute.",
|
"Amet irure quis incididunt voluptate esse. Commodo ea sunt est ipsum tempor nisi laboris voluptate labore elit laborum. Ex irure commodo reprehenderit consequat consequat do ad tempor aliquip deserunt eu. Laboris minim nostrud quis nisi. Dolor occaecat reprehenderit velit dolore exercitation cupidatat et voluptate. Nulla pariatur deserunt esse minim nisi nisi nulla. Sit eiusmod do incididunt sint minim pariatur aute.",
|
||||||
type: "warn",
|
type: "warn",
|
||||||
|
count: 1,
|
||||||
|
time: timeAgo(5, 2, 44),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -29,21 +29,30 @@
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p
|
<div v-for="(msg, index) in notifications" :key="index">
|
||||||
v-for="(notification, index) in notifications"
|
<small
|
||||||
:key="index"
|
class="d-flex justify-content-end mt-3"
|
||||||
class="d-flex align-items-baseline"
|
:title="fmtAbsoluteDate(msg.time)"
|
||||||
>
|
>
|
||||||
<fa-icon
|
{{ fmtTimeAgo(msg.time) }}
|
||||||
:class="{
|
</small>
|
||||||
'text-danger': notification.type === 'error',
|
<p class="d-flex align-items-baseline">
|
||||||
'text-warning': notification.type === 'warn',
|
<fa-icon
|
||||||
}"
|
:class="{
|
||||||
class="flex-grow-0 d-block"
|
'text-danger': msg.type === 'error',
|
||||||
icon="exclamation-triangle"
|
'text-warning': msg.type === 'warn',
|
||||||
></fa-icon>
|
}"
|
||||||
<span class="flex-grow-1 px-2 py-1">{{ notification.message }}</span>
|
class="flex-grow-0 d-block"
|
||||||
</p>
|
icon="exclamation-triangle"
|
||||||
|
></fa-icon>
|
||||||
|
<span class="flex-grow-1 px-2 py-1">
|
||||||
|
{{ msg.message }}
|
||||||
|
</span>
|
||||||
|
<span class="badge rounded-pill bg-secondary" v-if="msg.count > 1">
|
||||||
|
{{ msg.count }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button
|
<button
|
||||||
|
|
@ -83,9 +92,17 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
clear: function () {
|
clear: function () {
|
||||||
window.app.clear();
|
window.app && window.app.clear();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
created: function () {
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
this.$forceUpdate();
|
||||||
|
}, 10 * 1000);
|
||||||
|
},
|
||||||
|
destroyed: function () {
|
||||||
|
clearTimeout(this.interval);
|
||||||
|
},
|
||||||
mixins: [formatter],
|
mixins: [formatter],
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -76,5 +76,23 @@ export default {
|
||||||
minute: "numeric",
|
minute: "numeric",
|
||||||
}).format(date);
|
}).format(date);
|
||||||
},
|
},
|
||||||
|
fmtTimeAgo: function (date) {
|
||||||
|
const units = {
|
||||||
|
year: 24 * 60 * 60 * 1000 * 365,
|
||||||
|
month: (24 * 60 * 60 * 1000 * 365) / 12,
|
||||||
|
day: 24 * 60 * 60 * 1000,
|
||||||
|
hour: 60 * 60 * 1000,
|
||||||
|
minute: 60 * 1000,
|
||||||
|
second: 1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
const rtf = new Intl.RelativeTimeFormat("default", { numeric: "auto" });
|
||||||
|
const elapsed = date - new Date();
|
||||||
|
|
||||||
|
// "Math.abs" accounts for both "past" & "future" scenarios
|
||||||
|
for (var u in units)
|
||||||
|
if (Math.abs(elapsed) > units[u] || u == "second")
|
||||||
|
return rtf.format(Math.round(elapsed / units[u]), u);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
4
dist/index.html
vendored
4
dist/index.html
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
<!DOCTYPE html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="EV Charge Controller"><meta name="author" content="andig"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><link rel="apple-touch-icon" sizes="180x180" href="ico/apple-touch-icon.png?[[.Version]]"><link rel="icon" type="image/png" sizes="32x32" href="ico/favicon-32x32.png?[[.Version]]"><link rel="icon" type="image/png" sizes="16x16" href="ico/favicon-16x16.png?[[.Version]]"><link rel="manifest" href="ico/site.webmanifest"><link rel="mask-icon" href="ico/safari-pinned-tab.svg?[[.Version]]" color="#18191a"><link rel="shortcut icon" href="ico/favicon.ico?[[.Version]]"><meta name="apple-mobile-web-app-title" content="evcc"><meta name="application-name" content="evcc"><meta name="msapplication-TileColor" content="#18191a"><meta name="msapplication-config" content="ico/browserconfig.xml"><meta name="theme-color" content="#18191a"><title>evcc</title><link href="css/chunk-vendors.882a611f.css" rel="preload" as="style"><link href="css/index.f1264e7c.css" rel="preload" as="style"><link href="js/chunk-vendors.8d9f525d.js" rel="preload" as="script"><link href="js/index.11167e27.js" rel="preload" as="script"><link href="css/chunk-vendors.882a611f.css" rel="stylesheet"><link href="css/index.f1264e7c.css" rel="stylesheet"></head><body><script>window.evcc = {
|
<!DOCTYPE html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="EV Charge Controller"><meta name="author" content="andig"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><link rel="apple-touch-icon" sizes="180x180" href="ico/apple-touch-icon.png?[[.Version]]"><link rel="icon" type="image/png" sizes="32x32" href="ico/favicon-32x32.png?[[.Version]]"><link rel="icon" type="image/png" sizes="16x16" href="ico/favicon-16x16.png?[[.Version]]"><link rel="manifest" href="ico/site.webmanifest"><link rel="mask-icon" href="ico/safari-pinned-tab.svg?[[.Version]]" color="#18191a"><link rel="shortcut icon" href="ico/favicon.ico?[[.Version]]"><meta name="apple-mobile-web-app-title" content="evcc"><meta name="application-name" content="evcc"><meta name="msapplication-TileColor" content="#18191a"><meta name="msapplication-config" content="ico/browserconfig.xml"><meta name="theme-color" content="#18191a"><title>evcc</title><link href="css/chunk-vendors.882a611f.css" rel="preload" as="style"><link href="css/index.f1264e7c.css" rel="preload" as="style"><link href="js/chunk-vendors.8d9f525d.js" rel="preload" as="script"><link href="js/index.ce8c46e1.js" rel="preload" as="script"><link href="css/chunk-vendors.882a611f.css" rel="stylesheet"><link href="css/index.f1264e7c.css" rel="stylesheet"></head><body><script>window.evcc = {
|
||||||
version: "[[.Version]]",
|
version: "[[.Version]]",
|
||||||
configured: "[[.Configured]]",
|
configured: "[[.Configured]]",
|
||||||
};</script><div id="app"></div><script src="js/chunk-vendors.8d9f525d.js"></script><script src="js/index.11167e27.js"></script></body></html>
|
};</script><div id="app"></div><script src="js/chunk-vendors.8d9f525d.js"></script><script src="js/index.ce8c46e1.js"></script></body></html>
|
||||||
2
dist/js/index.11167e27.js
vendored
2
dist/js/index.11167e27.js
vendored
File diff suppressed because one or more lines are too long
1
dist/js/index.11167e27.js.map
vendored
1
dist/js/index.11167e27.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/js/index.ce8c46e1.js
vendored
Normal file
2
dist/js/index.ce8c46e1.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/js/index.ce8c46e1.js.map
vendored
Normal file
1
dist/js/index.ce8c46e1.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue