From 6e3a904aaac1a76a675dcb302747656ba8a1de37 Mon Sep 17 00:00:00 2001 From: Nelson Chan Date: Thu, 26 Aug 2021 18:55:19 +0800 Subject: [PATCH] WIP: Add tags functionality WIP: add color column, show tags WIP: Improve TagsManager styling & workflow WIP: Improve styling & validation, use translation WIP: Complete TagsManager functionality WIP: Add tags display in monitorList & Details Fix: update tags list after edit Fix: slightly improve tags styling Fix: Improve mobile UI Fix: Fix tags not showing on create monitor Fix: bring existingTags inside tagsManager Fix: remove unused tags prop Fix: Fix formatting, bump db version --- db/patch10.sql | 19 ++ server/database.js | 2 +- server/model/monitor.js | 3 + server/model/tag.js | 13 ++ server/server.js | 169 ++++++++++++++++++ src/components/MonitorList.vue | 13 ++ src/components/Tag.vue | 68 +++++++ src/components/TagsManager.vue | 313 +++++++++++++++++++++++++++++++++ src/icon.js | 37 +++- src/mixins/socket.js | 4 + src/pages/Details.vue | 13 ++ src/pages/EditMonitor.vue | 36 +++- 12 files changed, 681 insertions(+), 9 deletions(-) create mode 100644 db/patch10.sql create mode 100644 server/model/tag.js create mode 100644 src/components/Tag.vue create mode 100644 src/components/TagsManager.vue diff --git a/db/patch10.sql b/db/patch10.sql new file mode 100644 index 000000000..488db1169 --- /dev/null +++ b/db/patch10.sql @@ -0,0 +1,19 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +CREATE TABLE tag ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name VARCHAR(255) NOT NULL, + color VARCHAR(255) NOT NULL, + created_date DATETIME DEFAULT (DATETIME('now')) NOT NULL +); + +CREATE TABLE monitor_tag ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + monitor_id INTEGER NOT NULL, + tag_id INTEGER NOT NULL, + value TEXT, + CONSTRAINT FK_tag FOREIGN KEY (tag_id) REFERENCES tag(id) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT FK_monitor FOREIGN KEY (monitor_id) REFERENCES monitor(id) ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE INDEX monitor_tag_monitor_id_index ON monitor_tag (monitor_id); +CREATE INDEX monitor_tag_tag_id_index ON monitor_tag (tag_id); diff --git a/server/database.js b/server/database.js index 4b3ad443e..7af75423a 100644 --- a/server/database.js +++ b/server/database.js @@ -37,7 +37,7 @@ class Database { * The finally version should be 10 after merged tag feature * @deprecated Use patchList for any new feature */ - static latestVersion = 9; + static latestVersion = 10; static noReject = true; diff --git a/server/model/monitor.js b/server/model/monitor.js index 89208a3fd..6c4e17eeb 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -32,6 +32,8 @@ class Monitor extends BeanModel { notificationIDList[bean.notification_id] = true; } + const tags = await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [this.id]); + return { id: this.id, name: this.name, @@ -52,6 +54,7 @@ class Monitor extends BeanModel { dns_resolve_server: this.dns_resolve_server, dns_last_result: this.dns_last_result, notificationIDList, + tags: tags, }; } diff --git a/server/model/tag.js b/server/model/tag.js new file mode 100644 index 000000000..748280a70 --- /dev/null +++ b/server/model/tag.js @@ -0,0 +1,13 @@ +const { BeanModel } = require("redbean-node/dist/bean-model"); + +class Tag extends BeanModel { + toJSON() { + return { + id: this._id, + name: this._name, + color: this._color, + }; + } +} + +module.exports = Tag; diff --git a/server/server.js b/server/server.js index a0b9a2fbb..003d25ae6 100644 --- a/server/server.js +++ b/server/server.js @@ -514,6 +514,22 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString(); } }); + socket.on("getMonitorList", async (callback) => { + try { + checkLogin(socket) + await sendMonitorList(socket); + callback({ + ok: true, + }); + } catch (e) { + console.error(e) + callback({ + ok: false, + msg: e.message, + }); + } + }); + socket.on("getMonitor", async (monitorID, callback) => { try { checkLogin(socket) @@ -608,6 +624,159 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString(); } }); + socket.on("getTags", async (callback) => { + try { + checkLogin(socket) + + const list = await R.findAll("tag") + + callback({ + ok: true, + tags: list.map(bean => bean.toJSON()), + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("addTag", async (tag, callback) => { + try { + checkLogin(socket) + + let bean = R.dispense("tag") + bean.name = tag.name + bean.color = tag.color + await R.store(bean) + + callback({ + ok: true, + tag: await bean.toJSON(), + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("editTag", async (tag, callback) => { + try { + checkLogin(socket) + + let bean = await R.findOne("monitor", " id = ? ", [ tag.id ]) + bean.name = tag.name + bean.color = tag.color + await R.store(bean) + + callback({ + ok: true, + tag: await bean.toJSON(), + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("deleteTag", async (tagID, callback) => { + try { + checkLogin(socket) + + await R.exec("DELETE FROM tag WHERE id = ? ", [ tagID ]) + + callback({ + ok: true, + msg: "Deleted Successfully.", + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("addMonitorTag", async (tagID, monitorID, value, callback) => { + try { + checkLogin(socket) + + await R.exec("INSERT INTO monitor_tag (tag_id, monitor_id, value) VALUES (?, ?, ?)", [ + tagID, + monitorID, + value, + ]) + + callback({ + ok: true, + msg: "Added Successfully.", + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("editMonitorTag", async (tagID, monitorID, value, callback) => { + try { + checkLogin(socket) + + await R.exec("UPDATE monitor_tag SET value = ? WHERE tag_id = ? AND monitor_id = ?", [ + value, + tagID, + monitorID, + ]) + + callback({ + ok: true, + msg: "Edited Successfully.", + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("deleteMonitorTag", async (tagID, monitorID, callback) => { + try { + checkLogin(socket) + + await R.exec("DELETE FROM monitor_tag WHERE tag_id = ? AND monitor_id = ?", [ + tagID, + monitorID, + ]) + + // Cleanup unused Tags + await R.exec("delete from tag where ( select count(*) from monitor_tag mt where tag.id = mt.tag_id ) = 0"); + + callback({ + ok: true, + msg: "Deleted Successfully.", + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + socket.on("changePassword", async (password, callback) => { try { checkLogin(socket) diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index e72155891..77097dc12 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -11,6 +11,9 @@ {{ item.name }} +
+ +
@@ -29,10 +32,13 @@ + + diff --git a/src/components/TagsManager.vue b/src/components/TagsManager.vue new file mode 100644 index 000000000..4913eb091 --- /dev/null +++ b/src/components/TagsManager.vue @@ -0,0 +1,313 @@ + + + diff --git a/src/icon.js b/src/icon.js index 58583f0f8..56674f741 100644 --- a/src/icon.js +++ b/src/icon.js @@ -1,10 +1,37 @@ -import { library } from "@fortawesome/fontawesome-svg-core" -import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons" +import { library } from "@fortawesome/fontawesome-svg-core"; +import { + faArrowAltCircleUp, + faCog, + faEdit, + faEye, + faEyeSlash, + faList, + faPause, + faPlay, + faPlus, + faTachometerAlt, + faTimes, + faTrash +} from "@fortawesome/free-solid-svg-icons"; //import { fa } from '@fortawesome/free-regular-svg-icons' -import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome" +import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; // Add Free Font Awesome Icons here // https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free -library.add(faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash); +library.add( + faArrowAltCircleUp, + faCog, + faEdit, + faEye, + faEyeSlash, + faList, + faPause, + faPlay, + faPlus, + faTachometerAlt, + faTimes, + faTrash, +); + +export { FontAwesomeIcon }; -export { FontAwesomeIcon } diff --git a/src/mixins/socket.js b/src/mixins/socket.js index ea55f5a88..da6361486 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -266,6 +266,10 @@ export default { socket.emit("twoFAStatus", callback) }, + getMonitorList(callback) { + socket.emit("getMonitorList", callback) + }, + add(monitor, callback) { socket.emit("add", monitor, callback) }, diff --git a/src/pages/Details.vue b/src/pages/Details.vue index 9092b1792..c992d240b 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -2,6 +2,9 @@

{{ monitor.name }}

+
+ +

{{ monitor.url }} TCP Ping {{ monitor.hostname }}:{{ monitor.port }} @@ -213,6 +216,7 @@ import CountUp from "../components/CountUp.vue"; import Uptime from "../components/Uptime.vue"; import Pagination from "v-pagination-3"; const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue")); +import Tag from "../components/Tag.vue"; export default { components: { @@ -224,6 +228,7 @@ export default { Status, Pagination, PingChart, + Tag, }, data() { return { @@ -503,4 +508,12 @@ table { } } +.tags { + margin-bottom: 0.5rem; +} + +.tags > div:first-child { + margin-left: 0 !important; +} + diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index f98bb7560..d87bb4dd4 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -158,6 +158,10 @@

+
+ +
+
@@ -197,6 +201,7 @@