From b61aca2692f04f9be376d001877e453cedf8423f Mon Sep 17 00:00:00 2001
From: zachmann <gabriel.zachmann@kit.edu>
Date: Tue, 27 Feb 2024 09:37:42 +0100
Subject: [PATCH] [web] add notifications info to tokeninfo pane

---
 internal/server/handlers.go                   | 21 +++---
 .../web/partials/calendar-listing.mustache    | 27 ++++++++
 .../web/partials/notifications-info.mustache  | 31 +++++++++
 internal/server/web/partials/scripts.mustache |  1 +
 .../settings-notifications-calendar.mustache  | 21 ------
 .../partials/settings-notifications.mustache  |  2 +-
 .../server/web/partials/tokeninfo.mustache    |  3 +
 .../server/web/static/js/notifications.js     |  5 +-
 .../web/static/js/settings-notifications.js   | 58 +++++++++-------
 internal/server/web/static/js/settings.js     |  2 +
 .../server/web/static/js/tokeninfo-status.js  | 69 +++++++++++++++++++
 internal/utils/templating/keys.go             |  1 +
 12 files changed, 183 insertions(+), 58 deletions(-)
 create mode 100644 internal/server/web/partials/calendar-listing.mustache
 create mode 100644 internal/server/web/partials/notifications-info.mustache
 delete mode 100644 internal/server/web/partials/settings-notifications-calendar.mustache

diff --git a/internal/server/handlers.go b/internal/server/handlers.go
index cacee0cd..a0bca10f 100644
--- a/internal/server/handlers.go
+++ b/internal/server/handlers.go
@@ -47,8 +47,9 @@ func homeBindingData() map[string]interface{} {
 			templating.MustacheKeyCollapse: templating.Collapsable{
 				CollapseRestr: true,
 			},
-			templating.MustacheKeyPrefix:   "tokeninfo-",
-			templating.MustacheKeyReadOnly: true,
+			templating.MustacheKeyPrefix:            "tokeninfo-",
+			templating.MustacheKeyReadOnly:          true,
+			templating.MustacheKeyCalendarsEditable: false,
 		},
 		templating.MustacheSubCreateMT: map[string]interface{}{
 			templating.MustacheKeyPrefix:             "createMT-",
@@ -196,13 +197,15 @@ func handleSettings(ctx *fiber.Ctx) error {
 		g.EmbedBody = embed.String()
 	}
 	binding := map[string]interface{}{
-		templating.MustacheKeyGrants:          grants,
-		templating.MustacheKeyLoggedIn:        true,
-		templating.MustacheKeySettings:        true,
-		templating.MustacheKeySettingsSSH:     true,
-		templating.MustacheKeyRestrictionsGUI: true,
-		templating.MustacheKeyRestrictions:    webentities.WebRestrictions{},
-		templating.MustacheKeyCapabilities:    webentities.AllWebCapabilities(),
+		templating.MustacheKeyGrants:            grants,
+		templating.MustacheKeyLoggedIn:          true,
+		templating.MustacheKeySettings:          true,
+		templating.MustacheKeySettingsSSH:       true,
+		templating.MustacheKeyRestrictionsGUI:   true,
+		templating.MustacheKeyCalendarsEditable: true,
+		templating.MustacheKeyRestrictions:      webentities.WebRestrictions{},
+		templating.MustacheKeyCapabilities:      webentities.AllWebCapabilities(),
+		templating.MustacheKeyPrefix:            "settings-",
 	}
 	return ctx.Render("sites/settings", binding, templating.LayoutMain)
 }
diff --git a/internal/server/web/partials/calendar-listing.mustache b/internal/server/web/partials/calendar-listing.mustache
new file mode 100644
index 00000000..ce158ba3
--- /dev/null
+++ b/internal/server/web/partials/calendar-listing.mustache
@@ -0,0 +1,27 @@
+<table class="table table-striped bg-my_grey">
+    <thead>
+    <tr>
+        <th>Name</th>
+        <th></th>
+        <th>ICS Link</th>
+        {{#calendar-editable}}
+            <th></th>
+        {{/calendar-editable}}
+    </tr>
+    </thead>
+    <tbody id="{{prefix}}calendars">
+    <tr id="{{prefix}}noCalendars">
+        <td colspan="{{#calendar-editable}}{{/calendar-editable}}4{{^calendar-editable}}3{{/calendar-editable}}"
+            class="text-muted text-center">No calendar created.
+        </td>
+    </tr>
+    {{#calendar-editable}}
+        <tr>
+            <td colspan="4" class="text-center">
+                <button class="btn btn-success new-calendar-btn" role="button" id="{{prefix}}new-calendar-btn"><i
+                        class="fas fa-plus-circle"> New Calendar</i></button>
+            </td>
+        </tr>
+    {{/calendar-editable}}
+    </tbody>
+</table>
diff --git a/internal/server/web/partials/notifications-info.mustache b/internal/server/web/partials/notifications-info.mustache
new file mode 100644
index 00000000..c3938d01
--- /dev/null
+++ b/internal/server/web/partials/notifications-info.mustache
@@ -0,0 +1,31 @@
+<h4>Notifications</h4>
+{{#collapse.Notifications}}
+    <button class="btn btn-info my-expand" type="button" data-toggle="collapse"
+            data-target="#{{prefix}}not-info-body"
+            aria-expanded="false" aria-controls="{{prefix}}not-info-body" id="{{prefix}}not-info-expand">Expand
+    </button>
+{{/collapse.Notifications}}
+
+
+<div id="{{prefix}}not-info-body" class="{{#collapse.Notifications}}collapse{{/collapse.Notifications}}">
+
+    <div>
+        <p id="{{prefix}}tokeninfo-notifications-no" class="d-none">
+            This mytoken does not have anay active notifications.
+        </p>
+        <div id="{{prefix}}tokeninfo-notifications-listing">
+            <p>The following notifications are defined for this mytoken:</p>
+            <div id="{{prefix}}tokeninfo-notifications-listing-table-container"></div>
+        </div>
+    </div>
+
+
+    <h5>Calendars</h5>
+    <div id="{{prefix}}tokeninfo-calendar-listing">
+        <p>This mytoken is included in the following calendars:</p>
+        {{>calendar-listing}}
+    </div>
+    <div id="{{prefix}}tokeninfo-calendar-no" class="d-none">
+        <p>This mytoken is not included in any calendars</p>
+    </div>
+</div>
diff --git a/internal/server/web/partials/scripts.mustache b/internal/server/web/partials/scripts.mustache
index ad12e1a2..04d0f2f8 100644
--- a/internal/server/web/partials/scripts.mustache
+++ b/internal/server/web/partials/scripts.mustache
@@ -33,6 +33,7 @@
         <script src="{{instance-url}}/static/js/create-mt.js"></script>
         <script src="{{instance-url}}/static/js/capabilities.js"></script>
         <script src="{{instance-url}}/static/js/notifications.js"></script>
+        <script src="{{instance-url}}/static/js/settings-notifications.js"></script> <!--        TODO adapt-->
     {{/home}}
     {{#settings}}
         <script src="{{instance-url}}/static/js/tokeninfo.js"></script>
diff --git a/internal/server/web/partials/settings-notifications-calendar.mustache b/internal/server/web/partials/settings-notifications-calendar.mustache
deleted file mode 100644
index c71fe4db..00000000
--- a/internal/server/web/partials/settings-notifications-calendar.mustache
+++ /dev/null
@@ -1,21 +0,0 @@
-<table class="table table-striped bg-my_grey">
-    <thead>
-    <tr>
-        <th>Name</th>
-        <th></th>
-        <th>ICS Link</th>
-        <th></th>
-    </tr>
-    </thead>
-    <tbody id="calendars">
-    <tr id="noCalendars">
-        <td colspan="4" class="text-muted text-center">No calendar created.</td>
-    </tr>
-    <tr>
-        <td colspan="4" class="text-center">
-            <button class="btn btn-success" role="button" id="new-calendar-btn"><i class="fas fa-plus-circle"> New
-                Calendar</i></button>
-        </td>
-    </tr>
-    </tbody>
-</table>
diff --git a/internal/server/web/partials/settings-notifications.mustache b/internal/server/web/partials/settings-notifications.mustache
index c1d730b9..3a47e7d0 100644
--- a/internal/server/web/partials/settings-notifications.mustache
+++ b/internal/server/web/partials/settings-notifications.mustache
@@ -34,7 +34,7 @@
         <tr class="collapse no-top-border" id="settings-notifications-calendar-body">
             <td class="no-top-border"></td>
             <td class="no-top-border" colspan="3">
-                <div>{{> settings-notifications-calendar }}</div>
+                <div>{{> calendar-listing }}</div>
             </td>
         </tr>
 
diff --git a/internal/server/web/partials/tokeninfo.mustache b/internal/server/web/partials/tokeninfo.mustache
index 4825440a..9cfb5a41 100644
--- a/internal/server/web/partials/tokeninfo.mustache
+++ b/internal/server/web/partials/tokeninfo.mustache
@@ -134,6 +134,9 @@
                     <div class="alert border">
                         {{>rotation}}
                     </div>
+                    <div id="tokeninfo-notifications-display" class="alert border d-none">
+                        {{>notifications-info}}
+                    </div>
 
                 </div>
             </div>
diff --git a/internal/server/web/static/js/notifications.js b/internal/server/web/static/js/notifications.js
index b50b1760..0dfe67a7 100644
--- a/internal/server/web/static/js/notifications.js
+++ b/internal/server/web/static/js/notifications.js
@@ -50,7 +50,7 @@ function getNotificationClassIcon(notificationInfo, class_name) {
     }
 }
 
-function notificationsToTable(notifications) {
+function notificationsToTable(notifications, details = true) {
     let tableEntries = "";
     const websocketIcon = `<i class="fas fa-rss"></i>`;
     const mailIcon = `<i class="fas fa-envelope"></i>`;
@@ -82,7 +82,8 @@ function notificationsToTable(notifications) {
         let notification_classes_html = `<span data-toggle="tooltip"
               data-placement="bottom" title=""
               data-original-title="${notification_classes_str}">${notification_classes_icons}</span>`
-        tableEntries += `<tr management-code="${management_code}" role="button" onclick="toggleSubscriptionDetails('${management_code}')"><td>${typeIcon}</td><td>${notification_classes_html}</td><td>${tokens}</td></tr>`;
+        let details_html = details ? `role="button" onclick="toggleSubscriptionDetails('${management_code}')"` : '';
+        tableEntries += `<tr management-code="${management_code}" ${details_html}><td>${typeIcon}</td><td>${notification_classes_html}</td><td>${tokens}</td></tr>`;
         tableEntries += `<tr><td colspan="4" management-code="${management_code}" class="notification-details-container d-none pl-5"></td></tr>`
     });
     if (tableEntries === "") {
diff --git a/internal/server/web/static/js/settings-notifications.js b/internal/server/web/static/js/settings-notifications.js
index 2e9f0653..a50528f9 100644
--- a/internal/server/web/static/js/settings-notifications.js
+++ b/internal/server/web/static/js/settings-notifications.js
@@ -6,9 +6,14 @@ const $preferredMimeTypeText = $('#preferred_mimetype_txt');
 const $editMailBtn = $('#edit-mail-btn');
 const $saveMailBtn = $('#save-mail-btn');
 
-const $calendarTable = $('#calendars');
-const $noCalendarsEntry = $('#noCalendars');
-const $addCalendarButton = $('#new-calendar-btn');
+
+function $calendarTable(prefix = "") {
+    return $(prefixId('calendars', prefix));
+}
+
+function $noCalendarsEntry(prefix = "") {
+    return $(prefixId('noCalendars', prefix));
+}
 
 let settingsStatus = {"email_data_obtained": false, "calendars_loaded": false};
 
@@ -108,22 +113,22 @@ $emailInput.on('keyup', function (e) {
 
 $('#calendar-trigger-btn').on('click', function () {
     if (!settingsStatus["calendars_loaded"]) {
-        loadCalendars();
+        loadCalendars(settingsPrefix);
     }
 })
 
-function loadCalendars() {
-    clearCalendarTable();
+function loadCalendars(prefix = "") {
+    clearCalendarTable(prefix);
     $.ajax({
         type: "GET",
         url: storageGet('notifications_endpoint') + "/calendars",
         success: function (res) {
             let cals = res["calendars"];
             if (cals === undefined || cals === null) {
-                $noCalendarsEntry.showB();
+                $noCalendarsEntry(prefix).showB();
             } else {
                 cals.forEach(function (cal) {
-                    addCalendarToTable(cal);
+                    addCalendarToTable(cal, prefix);
                 })
                 settingsStatus["calendars_loaded"] = true;
             }
@@ -135,22 +140,24 @@ function loadCalendars() {
     });
 }
 
-const deleteCalendarHtml = `<td><a href="#" role="button" onclick="deleteCalendar(this)"><i class="fas fa-trash-alt text-danger"></i></a></td>`;
+function deleteCalendarHtml(prefix = "") {
+    return `<td><a href="#" role="button" onclick="deleteCalendar(this, '${prefix}')"><i class="fas fa-trash-alt text-danger"></i></a></td>`;
+}
 
-function clearCalendarTable() {
-    $calendarTable.find('tr.calendar-entry').remove();
+function clearCalendarTable(prefix = "") {
+    $calendarTable(prefix).find('tr.calendar-entry').remove();
 }
 
-function addCalendarToTable(cal) {
-    $noCalendarsEntry.hideB();
+function addCalendarToTable(cal, prefix = "", with_delete = true) {
+    $noCalendarsEntry(prefix).hideB();
     let name = cal['name'];
     let ics_path = cal['ics_path'];
     let viewCalendarHtml = `<td><a href="${ics_path}/view"><i class="fas fa-calendar-alt"></i></a></td>`;
-    const html = `<tr class="calendar-entry"><td>${name}</td>${viewCalendarHtml}<td><a href="${ics_path}" target="_blank" rel="noopener noreferrer">${ics_path}</a></td>${deleteCalendarHtml}</tr>`;
-    $calendarTable.prepend(html);
+    const html = `<tr class="calendar-entry"><td>${name}</td>${viewCalendarHtml}<td><a href="${ics_path}" target="_blank" rel="noopener noreferrer">${ics_path}</a></td>${with_delete ? deleteCalendarHtml(prefix) : ""}</tr>`;
+    $calendarTable(prefix).prepend(html);
 }
 
-function deleteCalendar(el) {
+function deleteCalendar(el, prefix = "") {
     const name = $(el).parent().siblings()[0].innerHTML;
     $(`
         <div class="modal fade" tabindex="-1" role="dialog">
@@ -165,7 +172,7 @@ function deleteCalendar(el) {
                     <div class="modal-body">Confirm to delete the calendar '${name}'.</div>
                     <div class="modal-footer">
                         <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
-                        <button type="button" class="btn btn-danger" data-dismiss="modal" onclick="sendDeleteCalendarRequest('${name}')">Delete</button>
+                        <button type="button" class="btn btn-danger" data-dismiss="modal" onclick="sendDeleteCalendarRequest('${name}', '${prefix}')">Delete</button>
                     </div>
                 </div>
             </div>
@@ -173,12 +180,12 @@ function deleteCalendar(el) {
    `).modal();
 }
 
-function sendDeleteCalendarRequest(name) {
+function sendDeleteCalendarRequest(name, prefix = "") {
     $.ajax({
         type: "DELETE",
         url: storageGet('notifications_endpoint') + "/calendars/" + name,
         success: function () {
-            loadCalendars();
+            loadCalendars(prefix);
         },
         error: function (errRes) {
             $settingsErrorModalMsg.text(getErrorMessage(errRes));
@@ -187,9 +194,11 @@ function sendDeleteCalendarRequest(name) {
     });
 }
 
-$addCalendarButton.on('click', addCalendar);
+$('.new-calendar-btn').on('click', function () {
+    addCalendar(extractPrefix('new-calendar-btn', $(this).attr('id')));
+});
 
-function addCalendar() {
+function addCalendar(prefix = "") {
     $(`
         <div class="modal fade" tabindex="-1" role="dialog">
             <div class="modal-dialog modal-dialog-centered modal-lg" role="document">
@@ -206,7 +215,7 @@ function addCalendar() {
                     </div>
                     <div class="modal-footer">
                         <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
-                        <button type="button" class="btn btn-success" data-dismiss="modal" onclick="sendCreateCalendarRequest()">Create <i class="fas fa-calendar-alt"></i></button>
+                        <button type="button" class="btn btn-success" data-dismiss="modal" onclick="sendCreateCalendarRequest('${prefix}')">Create <i class="fas fa-calendar-alt"></i></button>
                     </div>
                 </div>
             </div>
@@ -214,9 +223,8 @@ function addCalendar() {
    `).modal();
 }
 
-function sendCreateCalendarRequest() {
+function sendCreateCalendarRequest(prefix = "") {
     let data = JSON.stringify({"name": $('#calendar-name-input').val()});
-    console.log(data);
     $.ajax({
         type: "POST",
         data: data,
@@ -224,7 +232,7 @@ function sendCreateCalendarRequest() {
         contentType: "application/json",
         url: storageGet('notifications_endpoint') + "/calendars",
         success: function () {
-            loadCalendars();
+            loadCalendars(prefix);
         },
         error: function (errRes) {
             $settingsErrorModalMsg.text(getErrorMessage(errRes));
diff --git a/internal/server/web/static/js/settings.js b/internal/server/web/static/js/settings.js
index 1e6904dd..5250c5bd 100644
--- a/internal/server/web/static/js/settings.js
+++ b/internal/server/web/static/js/settings.js
@@ -7,6 +7,8 @@ $(function () {
     openCorrectTab();
 })
 
+const settingsPrefix = "settings-";
+
 function initGrants(...next) {
     $.ajax({
         type: "GET",
diff --git a/internal/server/web/static/js/tokeninfo-status.js b/internal/server/web/static/js/tokeninfo-status.js
index a8dcc9b0..959c520f 100644
--- a/internal/server/web/static/js/tokeninfo-status.js
+++ b/internal/server/web/static/js/tokeninfo-status.js
@@ -16,6 +16,27 @@ const $tokeninfoBadgeExpDate = $('#tokeninfo-token-exp-date');
 const $tokeninfoTypeBadges = $('.tokeninfo-token-type');
 const $tokeninfoTokenGoneWarningMsg = $('#token-gone-warning');
 const $tokeninfoActionButtons = $('#token-action-buttons');
+const $tokeninfoNotificationsInfo = $('#tokeninfo-notifications-display');
+
+function $tokeninfoCalendarListing(prefix = "") {
+    return $(prefixId('tokeninfo-calendar-listing', prefix));
+}
+
+function $tokeninfoNoCalendars(prefix = "") {
+    return $(prefixId('tokeninfo-calendar-no', prefix));
+}
+
+function $tokeninfoNotificationsListing(prefix = "") {
+    return $(prefixId('tokeninfo-notifications-listing', prefix));
+}
+
+function $tokeninfoNoNotifications(prefix = "") {
+    return $(prefixId('tokeninfo-notifications-no', prefix));
+}
+
+function $tokeninfoNotificationsListingTableContainer(prefix = "") {
+    return $(prefixId('tokeninfo-notifications-listing-table-container', prefix));
+}
 
 $('#tokeninfo-token-copy').on('click', function () {
     if (!$tokeninfoTokenGoneWarningMsg.hasClass('d-none')) {
@@ -106,6 +127,7 @@ async function update_tokeninfo() {
                 if (res['valid']) {
                     $tokeninfoBadgeValid.showB();
                     $tokeninfoBadgeInvalid.hideB();
+                    notificationsInfo(token);
                 } else {
                     $tokeninfoBadgeValid.hideB();
                     $tokeninfoBadgeInvalid.showB();
@@ -149,6 +171,53 @@ async function update_tokeninfo() {
     $('#introspect-tab').tab('show');
 }
 
+function notificationsInfo(token) {
+    $.ajax({
+        type: "POST",
+        url: tokeninfoEndpointToUse,
+        data: JSON.stringify({
+            'action': 'notifications',
+            'mytoken': token,
+        }),
+        dataType: "json",
+        contentType: "application/json",
+        success: function (res) {
+            clearCalendarTable(tokeninfoPrefix);
+            let cals = res["calendars"];
+            let notifications = res["notifications"];
+            let calsSet = cals !== undefined && cals.length > 0;
+            let notificationsSet = notifications !== undefined && notifications.length > 0;
+            if (!calsSet && !notificationsSet) {
+                $tokeninfoNotificationsInfo.hideB();
+                return;
+            }
+            $tokeninfoNotificationsInfo.showB();
+            if (!calsSet) {
+                $tokeninfoCalendarListing(tokeninfoPrefix).hideB();
+                $tokeninfoNoCalendars(tokeninfoPrefix).showB();
+            } else {
+                $tokeninfoNoCalendars(tokeninfoPrefix).hideB();
+                $tokeninfoCalendarListing(tokeninfoPrefix).showB();
+                cals.forEach(function (cal) {
+                    addCalendarToTable(cal, tokeninfoPrefix, false);
+                })
+            }
+            if (!notificationsSet) {
+                $tokeninfoNotificationsListing(tokeninfoPrefix).hideB();
+                $tokeninfoNoNotifications(tokeninfoPrefix).showB();
+            } else {
+                $tokeninfoNoNotifications(tokeninfoPrefix).hideB();
+                $tokeninfoNotificationsListing(tokeninfoPrefix).showB();
+                $tokeninfoNotificationsListingTableContainer(tokeninfoPrefix).html(notificationsToTable(notifications, false));
+                $('[data-toggle="tooltip"]').tooltip();
+            }
+        },
+        error: function (errRes) {
+            console.error(getErrorMessage(errRes));
+        }
+    });
+}
+
 $tokenInput.on('change', update_tokeninfo);
 $('#info-reload').on('click', update_tokeninfo);
 $('#info-tab').on('shown.bs.tab', update_tokeninfo);
diff --git a/internal/utils/templating/keys.go b/internal/utils/templating/keys.go
index a5fb67a3..3ab92ad5 100644
--- a/internal/utils/templating/keys.go
+++ b/internal/utils/templating/keys.go
@@ -38,6 +38,7 @@ const (
 	MustacheKeyProfilesRestrictions = "restrictions"
 	MustacheKeyProfilesRotation     = "rotation"
 	MustacheKeyNotificationClasses  = "notification-classes"
+	MustacheKeyCalendarsEditable    = "calendar-editable"
 )
 
 // Keys for sub configs
-- 
GitLab