From 8c91853eeab7a52422651767bdb9404d9ce1f802 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 17:28:39 +0200 Subject: [PATCH 01/16] add icons --- peach-web/static/icons/clipboard.png | Bin 0 -> 8267 bytes peach-web/static/icons/dns.png | Bin 0 -> 24220 bytes peach-web/static/icons/hermies.svg | 57 ++++++++++++++++++ .../icons/{power-switch.svg => power.svg} | 0 4 files changed, 57 insertions(+) create mode 100644 peach-web/static/icons/clipboard.png create mode 100644 peach-web/static/icons/dns.png create mode 100644 peach-web/static/icons/hermies.svg rename peach-web/static/icons/{power-switch.svg => power.svg} (100%) diff --git a/peach-web/static/icons/clipboard.png b/peach-web/static/icons/clipboard.png new file mode 100644 index 0000000000000000000000000000000000000000..642db7bab02889e0289b2fec0152a0b3c01c00c3 GIT binary patch literal 8267 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 Xr!?2 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 CfIS@m literal 0 HcmV?d00001 diff --git a/peach-web/static/icons/hermies.svg b/peach-web/static/icons/hermies.svg new file mode 100644 index 0000000..490f67f --- /dev/null +++ b/peach-web/static/icons/hermies.svg @@ -0,0 +1,57 @@ + + + + + + + + + + diff --git a/peach-web/static/icons/power-switch.svg b/peach-web/static/icons/power.svg similarity index 100% rename from peach-web/static/icons/power-switch.svg rename to peach-web/static/icons/power.svg -- 2.40.1 From 53cbe0f41a5b4d39a160519bf7e6017fc33bddcb Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 17:29:25 +0200 Subject: [PATCH 02/16] add settings templates --- peach-web/templates/settings/menu.html.tera | 14 ++ .../settings/network/configure_dns.html.tera | 75 ++++++++ .../settings/network/network_add.html.tera | 23 +++ .../settings/network/network_card.html.tera | 164 ++++++++++++++++++ .../settings/network/network_detail.html.tera | 76 ++++++++ .../settings/network/network_list.html.tera | 38 ++++ .../settings/network/network_modify.html.tera | 21 +++ .../settings/network/network_usage.html.tera | 47 +++++ .../templates/settings/ssb_settings.html.tera | 20 +++ 9 files changed, 478 insertions(+) create mode 100644 peach-web/templates/settings/menu.html.tera create mode 100644 peach-web/templates/settings/network/configure_dns.html.tera create mode 100644 peach-web/templates/settings/network/network_add.html.tera create mode 100644 peach-web/templates/settings/network/network_card.html.tera create mode 100644 peach-web/templates/settings/network/network_detail.html.tera create mode 100644 peach-web/templates/settings/network/network_list.html.tera create mode 100644 peach-web/templates/settings/network/network_modify.html.tera create mode 100644 peach-web/templates/settings/network/network_usage.html.tera create mode 100644 peach-web/templates/settings/ssb_settings.html.tera diff --git a/peach-web/templates/settings/menu.html.tera b/peach-web/templates/settings/menu.html.tera new file mode 100644 index 0000000..43253c8 --- /dev/null +++ b/peach-web/templates/settings/menu.html.tera @@ -0,0 +1,14 @@ +{%- extends "nav" -%} +{%- block card %} + +
+ +
+{%- endblock card -%} diff --git a/peach-web/templates/settings/network/configure_dns.html.tera b/peach-web/templates/settings/network/configure_dns.html.tera new file mode 100644 index 0000000..b9ceb84 --- /dev/null +++ b/peach-web/templates/settings/network/configure_dns.html.tera @@ -0,0 +1,75 @@ +{%- extends "nav" -%} +{%- block card %} + +
+ +
+ + {% if enable_dyndns %} + +
+
+ {% if is_dyndns_online %} + + {% else %} + + {% endif %} +
+
+ {% endif %} + +
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +
+ +
+ + + + + {% if flash_msg and flash_name == "success" %} + +
{{ flash_msg }}.
+ {%- elif flash_msg and flash_name == "info" %} + +
{{ flash_msg }}.
+ {%- elif flash_msg and flash_name == "error" %} + +
{{ flash_msg }}.
+ {%- endif -%} + + + +
+ + +{%- endblock card -%} diff --git a/peach-web/templates/settings/network/network_add.html.tera b/peach-web/templates/settings/network/network_add.html.tera new file mode 100644 index 0000000..ecff007 --- /dev/null +++ b/peach-web/templates/settings/network/network_add.html.tera @@ -0,0 +1,23 @@ +{%- extends "nav" -%} +{%- block card %} + +
+
+
+ + + + +
+ + Cancel +
+
+ + {% include "snippets/flash_message" %} + + {% include "snippets/noscript" %} +
+
+ +{%- endblock card -%} diff --git a/peach-web/templates/settings/network/network_card.html.tera b/peach-web/templates/settings/network/network_card.html.tera new file mode 100644 index 0000000..e880a5e --- /dev/null +++ b/peach-web/templates/settings/network/network_card.html.tera @@ -0,0 +1,164 @@ +{%- extends "nav" -%} + +{%- block card %} + + {%- if ap_state == "up" %} + +
+ +
+ + + +
+ WiFi router + +
+ + +
+ +

Access Point

+ +

peach

+ +

{{ ap_ip }}

+
+
+ + + + {% include "snippets/flash_message" %} + +
+
+
+ Digital devices +
+ +
+ +
+
+ Download +
+ {%- if ap_traffic -%} + + + {%- else -%} + + + {%- endif -%} +
+ +
+
+ Upload +
+ {%- if ap_traffic -%} + + + {%- else -%} + + + {%- endif -%} +
+ +
+
+
+
+ {%- else %} + +
+ + {%- if wlan_state == "up" %} +
+ + + +
+ WiFi online + + {%- else %} +
+
+ WiFi offline + + {%- endif %} +
+
+ + + +

WiFi Client

+ +

{{ wlan_ssid }}

+ +

{{ wlan_ip }}

+
+
+ + + + {% include "snippets/flash_message" %} +
+ + +
+
+ Signal +
+ +
+ +
+
+ Download +
+ {%- if wlan_traffic %} + + + + {%- else %} + + + + {%- endif %} +
+ +
+
+ Upload +
+ {%- if wlan_traffic %} + + + + {%- else %} + + + + {%- endif %} +
+ +
+
+
+
+ + {%- endif -%} +{%- endblock card -%} diff --git a/peach-web/templates/settings/network/network_detail.html.tera b/peach-web/templates/settings/network/network_detail.html.tera new file mode 100644 index 0000000..1839687 --- /dev/null +++ b/peach-web/templates/settings/network/network_detail.html.tera @@ -0,0 +1,76 @@ +{%- extends "nav" -%} +{%- block card -%} + {%- if wlan_networks -%} + {%- for ssid, ap in wlan_networks -%} + {# select only the access point we are interested in #} + {%- if ssid == selected %} + +
+ +
+ + +
+ WiFi icon + +
+ + +
+ +

{{ ssid }}

+ +

{% if ap.detail %}{% if ap.detail.protocol != "" %}{{ ap.detail.protocol }}{% else %}None{% endif %}{% else %}Unknown{% endif %}

+ +

{% if ap.signal %}{{ ap.signal }}%{% else %}Unknown{% endif %}

+
+
+ +
+
+ {%- if wlan_ssid == selected -%} +
+ + + +
+ {%- endif -%} + {%- if saved_aps -%} + {# Loop through the list of AP's with saved credentials #} + {%- for ap in saved_aps -%} + {# If the selected access point appears in the list, #} + {# display the Modify and Forget buttons. #} + {%- if ap.ssid == selected -%} + {# Set 'in_list' to true to allow correct Add button display #} + {% set_global in_list = true %} + {%- if wlan_ssid != selected and ap.state == "Available" -%} +
+ + + +
+ {%- endif -%} + Modify +
+ + + +
+ {%- endif -%} + {%- endfor -%} + {%- endif -%} + {%- if in_list == false -%} + {# Display the Add button if AP creds not already in saved networks list #} + Add + {%- endif -%} + Cancel +
+ + {% include "snippets/flash_message" %} +
+
+ + {%- endif -%} + {%- endfor -%} + {%- endif -%} +{%- endblock card -%} diff --git a/peach-web/templates/settings/network/network_list.html.tera b/peach-web/templates/settings/network/network_list.html.tera new file mode 100644 index 0000000..852af77 --- /dev/null +++ b/peach-web/templates/settings/network/network_list.html.tera @@ -0,0 +1,38 @@ +{%- extends "nav" -%} +{%- block card %} +
+
+ +
+
+{%- endblock card -%} diff --git a/peach-web/templates/settings/network/network_modify.html.tera b/peach-web/templates/settings/network/network_modify.html.tera new file mode 100644 index 0000000..43cf19f --- /dev/null +++ b/peach-web/templates/settings/network/network_modify.html.tera @@ -0,0 +1,21 @@ +{%- extends "nav" -%} +{%- block card %} + +
+
+
+ + + + +
+ + Cancel +
+
+ + {% include "snippets/flash_message" %} +
+
+ +{%- endblock card -%} diff --git a/peach-web/templates/settings/network/network_usage.html.tera b/peach-web/templates/settings/network/network_usage.html.tera new file mode 100644 index 0000000..0d4dec1 --- /dev/null +++ b/peach-web/templates/settings/network/network_usage.html.tera @@ -0,0 +1,47 @@ +{%- extends "nav" -%} +{%- block card -%} + +
+
+
+ + +
+ +
+
+
+ Warning +
+
+ + + +
+
+ + +
+
+ Cutoff +
+
+ + + +
+
+ + +
+
+
+ + Reset + Cancel +
+ + {% include "snippets/flash_message" %} +
+ +{%- endblock card %} diff --git a/peach-web/templates/settings/ssb_settings.html.tera b/peach-web/templates/settings/ssb_settings.html.tera new file mode 100644 index 0000000..24587ae --- /dev/null +++ b/peach-web/templates/settings/ssb_settings.html.tera @@ -0,0 +1,20 @@ +{%- extends "nav" -%} +{%- block card %} + + +{%- endblock card -%} -- 2.40.1 From b10cfd31c082463a3dfe27d73dbbe8d12f34159d Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 17:29:42 +0200 Subject: [PATCH 03/16] add scuttlebutt templates --- .../templates/scuttlebutt/messages.html.tera | 12 +++++++ .../templates/scuttlebutt/peers.html.tera | 15 +++++++++ .../scuttlebutt/peers_list.html.tera | 16 +++++++++ .../templates/scuttlebutt/profile.html.tera | 33 +++++++++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 peach-web/templates/scuttlebutt/messages.html.tera create mode 100644 peach-web/templates/scuttlebutt/peers.html.tera create mode 100644 peach-web/templates/scuttlebutt/peers_list.html.tera create mode 100644 peach-web/templates/scuttlebutt/profile.html.tera diff --git a/peach-web/templates/scuttlebutt/messages.html.tera b/peach-web/templates/scuttlebutt/messages.html.tera new file mode 100644 index 0000000..394f0b0 --- /dev/null +++ b/peach-web/templates/scuttlebutt/messages.html.tera @@ -0,0 +1,12 @@ +{%- extends "nav" -%} +{%- block card %} + +
+
+ + {% include "snippets/flash_message" %} + + {% include "snippets/noscript" %} +
+
+{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/peers.html.tera b/peach-web/templates/scuttlebutt/peers.html.tera new file mode 100644 index 0000000..b78906f --- /dev/null +++ b/peach-web/templates/scuttlebutt/peers.html.tera @@ -0,0 +1,15 @@ +{%- extends "nav" -%} +{%- block card %} + +
+
+ + +
+
+{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/peers_list.html.tera b/peach-web/templates/scuttlebutt/peers_list.html.tera new file mode 100644 index 0000000..2b61d78 --- /dev/null +++ b/peach-web/templates/scuttlebutt/peers_list.html.tera @@ -0,0 +1,16 @@ +{%- extends "nav" -%} +{%- block card %} + +{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/profile.html.tera b/peach-web/templates/scuttlebutt/profile.html.tera new file mode 100644 index 0000000..a34a37a --- /dev/null +++ b/peach-web/templates/scuttlebutt/profile.html.tera @@ -0,0 +1,33 @@ +{%- extends "nav" -%} +{%- block card %} + +
+ +
+ + Profile picture + + + Profile picture + +

{ name }

+ +

{ description }

+
+ +
+ + + +
+ + + + + {% include "snippets/flash_message" %} +
+{%- endblock card -%} -- 2.40.1 From 8306239ee9b66ede6652bf688f064a5665065282 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 17:30:10 +0200 Subject: [PATCH 04/16] add admin templates --- peach-web/templates/admin/add_admin.html.tera | 2 +- .../templates/admin/change_password.html.tera | 50 +++++++++++++++++++ .../templates/admin/forgot_password.html.tera | 25 ++++++++++ .../templates/admin/reset_password.html.tera | 48 ++++++++++++++++++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 peach-web/templates/admin/change_password.html.tera create mode 100644 peach-web/templates/admin/forgot_password.html.tera create mode 100644 peach-web/templates/admin/reset_password.html.tera diff --git a/peach-web/templates/admin/add_admin.html.tera b/peach-web/templates/admin/add_admin.html.tera index a99badb..37b186a 100644 --- a/peach-web/templates/admin/add_admin.html.tera +++ b/peach-web/templates/admin/add_admin.html.tera @@ -7,7 +7,7 @@ diff --git a/peach-web/templates/admin/change_password.html.tera b/peach-web/templates/admin/change_password.html.tera new file mode 100644 index 0000000..f4414a6 --- /dev/null +++ b/peach-web/templates/admin/change_password.html.tera @@ -0,0 +1,50 @@ +{%- extends "nav" -%} +{%- block card %} + +
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ Cancel +
+ + + {% include "snippets/flash_message" %} + + + {% include "snippets/noscript" %} + +
+
+ + +{%- endblock card -%} diff --git a/peach-web/templates/admin/forgot_password.html.tera b/peach-web/templates/admin/forgot_password.html.tera new file mode 100644 index 0000000..4cec989 --- /dev/null +++ b/peach-web/templates/admin/forgot_password.html.tera @@ -0,0 +1,25 @@ +{%- extends "nav" -%} +{%- block card %} + +
+

+ Click the button below to send a new temporary password which can be used to change your device password. +

+ The temporary password will be sent in an SSB private message to the admin of this device. +

+ +
+
+ +
+
+ + + {% include "snippets/flash_message" %} + + + {% include "snippets/noscript" %} + +
+
+{%- endblock card -%} diff --git a/peach-web/templates/admin/reset_password.html.tera b/peach-web/templates/admin/reset_password.html.tera new file mode 100644 index 0000000..1e71905 --- /dev/null +++ b/peach-web/templates/admin/reset_password.html.tera @@ -0,0 +1,48 @@ +{%- extends "nav" -%} +{%- block card %} + +
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ + + {% include "snippets/flash_message" %} + + + {% include "snippets/noscript" %} + +
+
+ +{%- endblock card -%} -- 2.40.1 From fe30705178c1117210666a689ed8aa2d7d3ddb22 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 17:30:44 +0200 Subject: [PATCH 05/16] update js files --- peach-web/static/js/change_password.js | 2 +- peach-web/static/js/configure_dns.js | 2 +- peach-web/static/js/{shutdown_menu.js => power_menu.js} | 8 ++++---- peach-web/static/js/reset_password.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename peach-web/static/js/{shutdown_menu.js => power_menu.js} (95%) diff --git a/peach-web/static/js/change_password.js b/peach-web/static/js/change_password.js index 9361d56..85bc2b1 100644 --- a/peach-web/static/js/change_password.js +++ b/peach-web/static/js/change_password.js @@ -23,7 +23,7 @@ PEACH.add = function() { // write in-progress status message to ui PEACH.flashMsg("info", "Saving new password."); // send add_wifi POST request - fetch("/api/v1/settings/change_password", { + fetch("/api/v1/admin/change_password", { method: "post", headers: { 'Content-Type': 'application/json', diff --git a/peach-web/static/js/configure_dns.js b/peach-web/static/js/configure_dns.js index 835be63..8abef30 100644 --- a/peach-web/static/js/configure_dns.js +++ b/peach-web/static/js/configure_dns.js @@ -38,7 +38,7 @@ PEACH_DNS.add = function() { // write in-progress status message to ui PEACH_DNS.flashMsg("info", "Saving new DNS configurations"); // send add_wifi POST request - fetch("/api/v1/dns/configure", { + fetch("/api/v1/network/dns/configure", { method: "post", headers: { 'Content-Type': 'application/json', diff --git a/peach-web/static/js/shutdown_menu.js b/peach-web/static/js/power_menu.js similarity index 95% rename from peach-web/static/js/shutdown_menu.js rename to peach-web/static/js/power_menu.js index 0d40dff..91e22e0 100644 --- a/peach-web/static/js/shutdown_menu.js +++ b/peach-web/static/js/power_menu.js @@ -1,7 +1,7 @@ /* -behavioural layer for the `shutdown.html.tera` template, -corresponding to the web route `/shutdown` +behavioural layer for the `power.html.tera` template, +corresponding to the web route `/power` - intercept button clicks for reboot & shutdown - perform json api calls @@ -28,7 +28,7 @@ PEACH_DEVICE.reboot = function() { // write reboot flash message PEACH_DEVICE.flashMsg("success", "Rebooting the device..."); // send reboot_device POST request - fetch("/api/v1/device/reboot", { + fetch("/api/v1/admin/reboot", { method: "post", headers: { 'Accept': 'application/json', @@ -59,7 +59,7 @@ PEACH_DEVICE.shutdown = function() { // write shutdown flash message PEACH_DEVICE.flashMsg("success", "Shutting down the device..."); // send shutdown_device POST request - fetch("/api/v1/device/shutdown", { + fetch("/api/v1/shutdown", { method: "post", headers: { 'Accept': 'application/json', diff --git a/peach-web/static/js/reset_password.js b/peach-web/static/js/reset_password.js index 1b4eb09..bf1c2ec 100644 --- a/peach-web/static/js/reset_password.js +++ b/peach-web/static/js/reset_password.js @@ -23,7 +23,7 @@ PEACH.add = function() { // write in-progress status message to ui PEACH.flashMsg("info", "Saving new password."); // send add_wifi POST request - fetch("/public/api/v1/reset_password", { + fetch("/api/v1/admin/reset_password", { method: "post", headers: { 'Content-Type': 'application/json', -- 2.40.1 From c73287cc2772b7fccad9401de5c73fa1f1e4e7f3 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 17:31:15 +0200 Subject: [PATCH 06/16] modify templates --- .../{ => catchers}/internal_error.html.tera | 0 .../{ => catchers}/not_found.html.tera | 0 peach-web/templates/configure_dns.html.tera | 75 -------- peach-web/templates/device.html.tera | 12 +- .../{index.html.tera => home.html.tera} | 16 +- peach-web/templates/messages.html.tera | 12 -- peach-web/templates/nav.html.tera | 4 +- peach-web/templates/network_add.html.tera | 23 --- peach-web/templates/network_card.html.tera | 164 ------------------ peach-web/templates/network_detail.html.tera | 76 -------- peach-web/templates/network_list.html.tera | 38 ---- peach-web/templates/network_modify.html.tera | 21 --- peach-web/templates/network_usage.html.tera | 47 ----- .../password/change_password.html.tera | 50 ------ .../password/forgot_password.html.tera | 25 --- .../password/reset_password.html.tera | 48 ----- peach-web/templates/peers.html.tera | 15 -- peach-web/templates/peers_list.html.tera | 16 -- .../{shutdown.html.tera => power.html.tera} | 6 +- peach-web/templates/profile.html.tera | 33 ---- peach-web/templates/settings.html.tera | 14 -- peach-web/templates/ssb_settings.html.tera | 20 --- 22 files changed, 19 insertions(+), 696 deletions(-) rename peach-web/templates/{ => catchers}/internal_error.html.tera (100%) rename peach-web/templates/{ => catchers}/not_found.html.tera (100%) delete mode 100644 peach-web/templates/configure_dns.html.tera rename peach-web/templates/{index.html.tera => home.html.tera} (73%) delete mode 100644 peach-web/templates/messages.html.tera delete mode 100644 peach-web/templates/network_add.html.tera delete mode 100644 peach-web/templates/network_card.html.tera delete mode 100644 peach-web/templates/network_detail.html.tera delete mode 100644 peach-web/templates/network_list.html.tera delete mode 100644 peach-web/templates/network_modify.html.tera delete mode 100644 peach-web/templates/network_usage.html.tera delete mode 100644 peach-web/templates/password/change_password.html.tera delete mode 100644 peach-web/templates/password/forgot_password.html.tera delete mode 100644 peach-web/templates/password/reset_password.html.tera delete mode 100644 peach-web/templates/peers.html.tera delete mode 100644 peach-web/templates/peers_list.html.tera rename peach-web/templates/{shutdown.html.tera => power.html.tera} (77%) delete mode 100644 peach-web/templates/profile.html.tera delete mode 100644 peach-web/templates/settings.html.tera delete mode 100644 peach-web/templates/ssb_settings.html.tera diff --git a/peach-web/templates/internal_error.html.tera b/peach-web/templates/catchers/internal_error.html.tera similarity index 100% rename from peach-web/templates/internal_error.html.tera rename to peach-web/templates/catchers/internal_error.html.tera diff --git a/peach-web/templates/not_found.html.tera b/peach-web/templates/catchers/not_found.html.tera similarity index 100% rename from peach-web/templates/not_found.html.tera rename to peach-web/templates/catchers/not_found.html.tera diff --git a/peach-web/templates/configure_dns.html.tera b/peach-web/templates/configure_dns.html.tera deleted file mode 100644 index 019bc5b..0000000 --- a/peach-web/templates/configure_dns.html.tera +++ /dev/null @@ -1,75 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
- -
- - {% if enable_dyndns %} - -
-
- {% if is_dyndns_online %} - - {% else %} - - {% endif %} -
-
- {% endif %} - -
-
- - - -
-
-
- - - -
-
-
- - - -
-
- -
- -
- - - - - {% if flash_msg and flash_name == "success" %} - -
{{ flash_msg }}.
- {%- elif flash_msg and flash_name == "info" %} - -
{{ flash_msg }}.
- {%- elif flash_msg and flash_name == "error" %} - -
{{ flash_msg }}.
- {%- endif -%} - - - -
-
- -{%- endblock card -%} diff --git a/peach-web/templates/device.html.tera b/peach-web/templates/device.html.tera index 9b31333..96d1cc7 100644 --- a/peach-web/templates/device.html.tera +++ b/peach-web/templates/device.html.tera @@ -25,7 +25,7 @@
- Network + Network
@@ -33,7 +33,7 @@
- Display + Display
@@ -41,7 +41,7 @@
- Stats + Stats
@@ -51,7 +51,7 @@
- Dyndns + Dyndns
@@ -59,7 +59,7 @@
- Config + Config
@@ -67,7 +67,7 @@
- Sbot + Sbot
diff --git a/peach-web/templates/index.html.tera b/peach-web/templates/home.html.tera similarity index 73% rename from peach-web/templates/index.html.tera rename to peach-web/templates/home.html.tera index b7142ae..14edce6 100644 --- a/peach-web/templates/index.html.tera +++ b/peach-web/templates/home.html.tera @@ -6,21 +6,21 @@
- +
- +
- +
@@ -30,23 +30,23 @@ - +
- +
- +
- +
- +
diff --git a/peach-web/templates/messages.html.tera b/peach-web/templates/messages.html.tera deleted file mode 100644 index 394f0b0..0000000 --- a/peach-web/templates/messages.html.tera +++ /dev/null @@ -1,12 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
- - {% include "snippets/flash_message" %} - - {% include "snippets/noscript" %} -
-
-{%- endblock card -%} diff --git a/peach-web/templates/nav.html.tera b/peach-web/templates/nav.html.tera index 33c50e7..e89e50a 100644 --- a/peach-web/templates/nav.html.tera +++ b/peach-web/templates/nav.html.tera @@ -22,8 +22,8 @@ PeachCloud - - Question mark + + Power switch {%- endblock nav -%} diff --git a/peach-web/templates/network_add.html.tera b/peach-web/templates/network_add.html.tera deleted file mode 100644 index aaa47ad..0000000 --- a/peach-web/templates/network_add.html.tera +++ /dev/null @@ -1,23 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-
- - - - -
- - Cancel -
-
- - {% include "snippets/flash_message" %} - - {% include "snippets/noscript" %} -
-
- -{%- endblock card -%} diff --git a/peach-web/templates/network_card.html.tera b/peach-web/templates/network_card.html.tera deleted file mode 100644 index aac40c4..0000000 --- a/peach-web/templates/network_card.html.tera +++ /dev/null @@ -1,164 +0,0 @@ -{%- extends "nav" -%} - -{%- block card %} - - {%- if ap_state == "up" %} - -
- -
- - - -
- WiFi router - -
- - -
- -

Access Point

- -

peach

- -

{{ ap_ip }}

-
-
- - - - {% include "snippets/flash_message" %} - -
-
-
- Digital devices -
- -
- -
-
- Download -
- {%- if ap_traffic -%} - - - {%- else -%} - - - {%- endif -%} -
- -
-
- Upload -
- {%- if ap_traffic -%} - - - {%- else -%} - - - {%- endif -%} -
- -
-
-
-
- {%- else %} - -
- - {%- if wlan_state == "up" %} -
- - - -
- WiFi online - - {%- else %} -
-
- WiFi offline - - {%- endif %} -
-
- - - -

WiFi Client

- -

{{ wlan_ssid }}

- -

{{ wlan_ip }}

-
-
- - - - {% include "snippets/flash_message" %} -
- - -
-
- Signal -
- -
- -
-
- Download -
- {%- if wlan_traffic %} - - - - {%- else %} - - - - {%- endif %} -
- -
-
- Upload -
- {%- if wlan_traffic %} - - - - {%- else %} - - - - {%- endif %} -
- -
-
-
-
- - {%- endif -%} -{%- endblock card -%} diff --git a/peach-web/templates/network_detail.html.tera b/peach-web/templates/network_detail.html.tera deleted file mode 100644 index c58b5de..0000000 --- a/peach-web/templates/network_detail.html.tera +++ /dev/null @@ -1,76 +0,0 @@ -{%- extends "nav" -%} -{%- block card -%} - {%- if wlan_networks -%} - {%- for ssid, ap in wlan_networks -%} - {# select only the access point we are interested in #} - {%- if ssid == selected %} - -
- -
- - -
- WiFi icon - -
- - -
- -

{{ ssid }}

- -

{% if ap.detail %}{% if ap.detail.protocol != "" %}{{ ap.detail.protocol }}{% else %}None{% endif %}{% else %}Unknown{% endif %}

- -

{% if ap.signal %}{{ ap.signal }}%{% else %}Unknown{% endif %}

-
-
- -
-
- {%- if wlan_ssid == selected -%} -
- - - -
- {%- endif -%} - {%- if saved_aps -%} - {# Loop through the list of AP's with saved credentials #} - {%- for ap in saved_aps -%} - {# If the selected access point appears in the list, #} - {# display the Modify and Forget buttons. #} - {%- if ap.ssid == selected -%} - {# Set 'in_list' to true to allow correct Add button display #} - {% set_global in_list = true %} - {%- if wlan_ssid != selected and ap.state == "Available" -%} -
- - - -
- {%- endif -%} - Modify -
- - - -
- {%- endif -%} - {%- endfor -%} - {%- endif -%} - {%- if in_list == false -%} - {# Display the Add button if AP creds not already in saved networks list #} - Add - {%- endif -%} - Cancel -
- - {% include "snippets/flash_message" %} -
-
- - {%- endif -%} - {%- endfor -%} - {%- endif -%} -{%- endblock card -%} diff --git a/peach-web/templates/network_list.html.tera b/peach-web/templates/network_list.html.tera deleted file mode 100644 index 45d8227..0000000 --- a/peach-web/templates/network_list.html.tera +++ /dev/null @@ -1,38 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} -
-
- -
-
-{%- endblock card -%} diff --git a/peach-web/templates/network_modify.html.tera b/peach-web/templates/network_modify.html.tera deleted file mode 100644 index 3c9b18c..0000000 --- a/peach-web/templates/network_modify.html.tera +++ /dev/null @@ -1,21 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-
- - - - -
- - Cancel -
-
- - {% include "snippets/flash_message" %} -
-
- -{%- endblock card -%} diff --git a/peach-web/templates/network_usage.html.tera b/peach-web/templates/network_usage.html.tera deleted file mode 100644 index 2c0ac65..0000000 --- a/peach-web/templates/network_usage.html.tera +++ /dev/null @@ -1,47 +0,0 @@ -{%- extends "nav" -%} -{%- block card -%} - -
-
-
- - -
- -
-
-
- Warning -
-
- - - -
-
- - -
-
- Cutoff -
-
- - - -
-
- - -
-
-
- - Reset - Cancel -
- - {% include "snippets/flash_message" %} -
- -{%- endblock card %} diff --git a/peach-web/templates/password/change_password.html.tera b/peach-web/templates/password/change_password.html.tera deleted file mode 100644 index f4414a6..0000000 --- a/peach-web/templates/password/change_password.html.tera +++ /dev/null @@ -1,50 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-
-
- - -
- -
- - -
- -
- - -
- -
- -
- Cancel -
- - - {% include "snippets/flash_message" %} - - - {% include "snippets/noscript" %} - -
-
- - -{%- endblock card -%} diff --git a/peach-web/templates/password/forgot_password.html.tera b/peach-web/templates/password/forgot_password.html.tera deleted file mode 100644 index 4cec989..0000000 --- a/peach-web/templates/password/forgot_password.html.tera +++ /dev/null @@ -1,25 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-

- Click the button below to send a new temporary password which can be used to change your device password. -

- The temporary password will be sent in an SSB private message to the admin of this device. -

- -
-
- -
-
- - - {% include "snippets/flash_message" %} - - - {% include "snippets/noscript" %} - -
-
-{%- endblock card -%} diff --git a/peach-web/templates/password/reset_password.html.tera b/peach-web/templates/password/reset_password.html.tera deleted file mode 100644 index 1e71905..0000000 --- a/peach-web/templates/password/reset_password.html.tera +++ /dev/null @@ -1,48 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-
-
- - -
- -
- - -
- -
- - -
- -
- -
-
- - - {% include "snippets/flash_message" %} - - - {% include "snippets/noscript" %} - -
-
- -{%- endblock card -%} diff --git a/peach-web/templates/peers.html.tera b/peach-web/templates/peers.html.tera deleted file mode 100644 index b78906f..0000000 --- a/peach-web/templates/peers.html.tera +++ /dev/null @@ -1,15 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
- - -
-
-{%- endblock card -%} diff --git a/peach-web/templates/peers_list.html.tera b/peach-web/templates/peers_list.html.tera deleted file mode 100644 index 2b61d78..0000000 --- a/peach-web/templates/peers_list.html.tera +++ /dev/null @@ -1,16 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -{%- endblock card -%} diff --git a/peach-web/templates/shutdown.html.tera b/peach-web/templates/power.html.tera similarity index 77% rename from peach-web/templates/shutdown.html.tera rename to peach-web/templates/power.html.tera index 12b450c..ffd0a1e 100644 --- a/peach-web/templates/shutdown.html.tera +++ b/peach-web/templates/power.html.tera @@ -5,8 +5,8 @@
@@ -15,5 +15,5 @@ {% include "snippets/noscript" %}
- + {%- endblock card -%} diff --git a/peach-web/templates/profile.html.tera b/peach-web/templates/profile.html.tera deleted file mode 100644 index a34a37a..0000000 --- a/peach-web/templates/profile.html.tera +++ /dev/null @@ -1,33 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
- -
- - Profile picture - - - Profile picture - -

{ name }

- -

{ description }

-
- -
- - - -
- - - - - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/settings.html.tera b/peach-web/templates/settings.html.tera deleted file mode 100644 index 43253c8..0000000 --- a/peach-web/templates/settings.html.tera +++ /dev/null @@ -1,14 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
- -
-{%- endblock card -%} diff --git a/peach-web/templates/ssb_settings.html.tera b/peach-web/templates/ssb_settings.html.tera deleted file mode 100644 index 24587ae..0000000 --- a/peach-web/templates/ssb_settings.html.tera +++ /dev/null @@ -1,20 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - - -{%- endblock card -%} -- 2.40.1 From 8baf68715de42cde4a63219dbab9d6a96dd55ac8 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 17:32:00 +0200 Subject: [PATCH 07/16] reorganise routes --- peach-web/src/routes/catchers.rs | 10 +- peach-web/src/routes/index.rs | 4 +- peach-web/src/routes/mod.rs | 5 +- peach-web/src/routes/scuttlebutt.rs | 14 +- peach-web/src/routes/settings/admin.rs | 27 ++-- peach-web/src/routes/settings/dns.rs | 27 ++-- peach-web/src/routes/settings/menu.rs | 41 +++++ peach-web/src/routes/settings/mod.rs | 3 +- peach-web/src/routes/settings/network.rs | 160 ++++++++++---------- peach-web/src/routes/{ => status}/device.rs | 58 +++---- peach-web/src/routes/status/mod.rs | 2 + peach-web/src/routes/{ => status}/ping.rs | 14 +- 12 files changed, 210 insertions(+), 155 deletions(-) create mode 100644 peach-web/src/routes/settings/menu.rs rename peach-web/src/routes/{ => status}/device.rs (86%) create mode 100644 peach-web/src/routes/status/mod.rs rename peach-web/src/routes/{ => status}/ping.rs (93%) diff --git a/peach-web/src/routes/catchers.rs b/peach-web/src/routes/catchers.rs index a096cac..0c3523e 100644 --- a/peach-web/src/routes/catchers.rs +++ b/peach-web/src/routes/catchers.rs @@ -1,7 +1,7 @@ use log::debug; -use rocket::{catch}; -use rocket_dyn_templates::Template; +use rocket::catch; use rocket::response::Redirect; +use rocket_dyn_templates::Template; use serde::Serialize; // HELPERS AND ROUTES FOR 404 ERROR @@ -34,7 +34,7 @@ pub fn not_found() -> Template { context.flash_name = Some("error".to_string()); context.flash_msg = Some("No resource found for given URL".to_string()); - Template::render("not_found", context) + Template::render("catchers/not_found", context) } // HELPERS AND ROUTES FOR 500 ERROR @@ -48,7 +48,7 @@ pub fn internal_error() -> Template { context.flash_name = Some("error".to_string()); context.flash_msg = Some("Internal server error".to_string()); - Template::render("internal_error", context) + Template::render("catchers/internal_error", context) } // HELPERS AND ROUTES FOR 403 FORBIDDEN @@ -57,4 +57,4 @@ pub fn internal_error() -> Template { pub fn forbidden() -> Redirect { debug!("403 Forbidden"); Redirect::to("/login") -} \ No newline at end of file +} diff --git a/peach-web/src/routes/index.rs b/peach-web/src/routes/index.rs index 1ca4443..aa1113e 100644 --- a/peach-web/src/routes/index.rs +++ b/peach-web/src/routes/index.rs @@ -24,13 +24,13 @@ impl HomeContext { } #[get("/")] -pub fn index(_auth: Authenticated) -> Template { +pub fn home(_auth: Authenticated) -> Template { let context = HomeContext { flash_name: None, flash_msg: None, title: None, }; - Template::render("index", &context) + Template::render("home", &context) } // HELPERS AND ROUTES FOR /help diff --git a/peach-web/src/routes/mod.rs b/peach-web/src/routes/mod.rs index be18d09..830e23b 100644 --- a/peach-web/src/routes/mod.rs +++ b/peach-web/src/routes/mod.rs @@ -1,7 +1,6 @@ pub mod authentication; -pub mod device; pub mod catchers; pub mod index; -pub mod ping; pub mod scuttlebutt; -pub mod settings; \ No newline at end of file +pub mod settings; +pub mod status; diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index ec8d826..ede8699 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -45,7 +45,7 @@ pub fn private(flash: Option, _auth: Authenticated) -> Template { context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - Template::render("messages", &context) + Template::render("scuttlebutt/messages", &context) } // HELPERS AND ROUTES FOR /peers @@ -81,7 +81,7 @@ pub fn peers(flash: Option, _auth: Authenticated) -> Template { context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - Template::render("peers", &context) + Template::render("scuttlebutt/peers", &context) } // HELPERS AND ROUTES FOR /post/publish @@ -209,7 +209,7 @@ pub fn profile( context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - Template::render("profile", &context) + Template::render("scuttlebutt/profile", &context) } // HELPERS AND ROUTES FOR /friends @@ -247,7 +247,7 @@ pub fn friends(flash: Option, _auth: Authenticated) -> Template { context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - Template::render("peers_list", &context) + Template::render("scuttlebutt/peers_list", &context) } // HELPERS AND ROUTES FOR /follows @@ -285,7 +285,7 @@ pub fn follows(flash: Option, _auth: Authenticated) -> Template { context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - Template::render("peers_list", &context) + Template::render("scuttlebutt/peers_list", &context) } // HELPERS AND ROUTES FOR /followers @@ -323,7 +323,7 @@ pub fn followers(flash: Option, _auth: Authenticated) -> Template context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - Template::render("peers_list", &context) + Template::render("scuttlebutt/peers_list", &context) } // HELPERS AND ROUTES FOR /blocks @@ -361,5 +361,5 @@ pub fn blocks(flash: Option, _auth: Authenticated) -> Template { context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - Template::render("peers_list", &context) + Template::render("scuttlebutt/peers_list", &context) } diff --git a/peach-web/src/routes/settings/admin.rs b/peach-web/src/routes/settings/admin.rs index 4f8980a..5148a17 100644 --- a/peach-web/src/routes/settings/admin.rs +++ b/peach-web/src/routes/settings/admin.rs @@ -1,12 +1,12 @@ +use rocket::serde::{Deserialize, Serialize}; use rocket::{ + form::{Form, FromForm}, get, post, request::FlashMessage, - form::{Form, FromForm}, response::{Flash, Redirect}, uri, }; use rocket_dyn_templates::Template; -use rocket::serde::{Deserialize, Serialize}; use peach_lib::config_manager; use peach_lib::config_manager::load_peach_config; @@ -39,12 +39,12 @@ impl ConfigureAdminContext { } } -/// View and delete currently configured admin. -#[get("/settings/configure_admin")] +/// Administrator settings menu. View and delete currently configured admin. +#[get("/")] pub fn configure_admin(flash: Option, _auth: Authenticated) -> Template { let mut context = ConfigureAdminContext::build(); - // set back icon link to network route - context.back = Some("/network".to_string()); + // set back icon link to settings route + context.back = Some("/settings".to_string()); context.title = Some("Configure Admin".to_string()); // check to see if there is a flash message to display if let Some(flash) = flash { @@ -83,14 +83,14 @@ impl AddAdminContext { pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError> { let _result = config_manager::add_ssb_admin_id(&admin_form.ssb_id)?; - // if the previous line didn't throw an error then it was a success + // if the previous line didn't throw an error then it was a success Ok(()) } -#[get("/settings/admin/add")] +#[get("/add")] pub fn add_admin(flash: Option, _auth: Authenticated) -> Template { let mut context = AddAdminContext::build(); - context.back = Some("/settings/configure_admin".to_string()); + context.back = Some("/settings/admin".to_string()); context.title = Some("Add Admin".to_string()); // check to see if there is a flash message to display if let Some(flash) = flash { @@ -102,7 +102,7 @@ pub fn add_admin(flash: Option, _auth: Authenticated) -> Template Template::render("admin/add_admin", &context) } -#[post("/settings/admin/add", data = "")] +#[post("/add", data = "")] pub fn add_admin_post(add_admin_form: Form, _auth: Authenticated) -> Flash { let result = save_add_admin_form(add_admin_form.into_inner()); let url = uri!(configure_admin); @@ -119,8 +119,11 @@ pub struct DeleteAdminForm { pub ssb_id: String, } -#[post("/settings/admin/delete", data = "")] -pub fn delete_admin_post(delete_admin_form: Form, _auth: Authenticated) -> Flash { +#[post("/delete", data = "")] +pub fn delete_admin_post( + delete_admin_form: Form, + _auth: Authenticated, +) -> Flash { let result = config_manager::delete_ssb_admin_id(&delete_admin_form.ssb_id); let url = uri!(configure_admin); match result { diff --git a/peach-web/src/routes/settings/dns.rs b/peach-web/src/routes/settings/dns.rs index 7bca89c..8ef3e21 100644 --- a/peach-web/src/routes/settings/dns.rs +++ b/peach-web/src/routes/settings/dns.rs @@ -1,12 +1,14 @@ use log::info; use rocket::{ + form::{Form, FromForm}, get, post, request::FlashMessage, - form::{Form, FromForm} + serde::{ + json::{Json, Value}, + Deserialize, Serialize, + }, }; -use rocket::serde::json::Json; use rocket_dyn_templates::Template; -use rocket::serde::{Deserialize, Serialize}; use peach_lib::config_manager; use peach_lib::config_manager::load_peach_config; @@ -22,7 +24,6 @@ use peach_lib::jsonrpc_core::types::error::ErrorCode; use crate::error::PeachWebError; use crate::routes::authentication::Authenticated; use crate::utils::build_json_response; -use rocket::serde::json::Value; #[derive(Debug, Deserialize, FromForm)] pub struct DnsForm { @@ -113,11 +114,11 @@ impl ConfigureDNSContext { } } -#[get("/network/dns")] +#[get("/dns")] pub fn configure_dns(flash: Option, _auth: Authenticated) -> Template { let mut context = ConfigureDNSContext::build(); // set back icon link to network route - context.back = Some("/network".to_string()); + context.back = Some("/settings/network".to_string()); context.title = Some("Configure DNS".to_string()); // check to see if there is a flash message to display if let Some(flash) = flash { @@ -125,35 +126,35 @@ pub fn configure_dns(flash: Option, _auth: Authenticated) -> Templ context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - Template::render("configure_dns", &context) + Template::render("settings/network/configure_dns", &context) } -#[post("/network/dns", data = "")] +#[post("/dns", data = "")] pub fn configure_dns_post(dns: Form, _auth: Authenticated) -> Template { let result = save_dns_configuration(dns.into_inner()); match result { Ok(_) => { let mut context = ConfigureDNSContext::build(); // set back icon link to network route - context.back = Some("/network".to_string()); + context.back = Some("/settings/network".to_string()); context.title = Some("Configure DNS".to_string()); context.flash_name = Some("success".to_string()); context.flash_msg = Some("New dynamic dns configuration is now enabled".to_string()); - Template::render("configure_dns", &context) + Template::render("settings/network/configure_dns", &context) } Err(err) => { let mut context = ConfigureDNSContext::build(); // set back icon link to network route - context.back = Some("/network".to_string()); + context.back = Some("/settings/network".to_string()); context.title = Some("Configure DNS".to_string()); context.flash_name = Some("error".to_string()); context.flash_msg = Some(format!("Failed to save dns configurations: {}", err)); - Template::render("configure_dns", &context) + Template::render("settings/network/configure_dns", &context) } } } -#[post("/api/v1/dns/configure", data = "")] +#[post("/dns/configure", data = "")] pub fn save_dns_configuration_endpoint(dns_form: Json, _auth: Authenticated) -> Value { let result = save_dns_configuration(dns_form.into_inner()); match result { diff --git a/peach-web/src/routes/settings/menu.rs b/peach-web/src/routes/settings/menu.rs new file mode 100644 index 0000000..e5abe1c --- /dev/null +++ b/peach-web/src/routes/settings/menu.rs @@ -0,0 +1,41 @@ +use rocket::{get, request::FlashMessage, serde::Serialize}; +use rocket_dyn_templates::Template; + +use crate::routes::authentication::Authenticated; + +// HELPERS AND ROUTES FOR /settings + +#[derive(Debug, Serialize)] +pub struct SettingsMenuContext { + pub back: Option, + pub title: Option, + pub flash_name: Option, + pub flash_msg: Option, +} + +impl SettingsMenuContext { + pub fn build() -> SettingsMenuContext { + SettingsMenuContext { + back: None, + title: None, + flash_name: None, + flash_msg: None, + } + } +} + +/// View and delete currently configured admin. +#[get("/settings")] +pub fn settings_menu(flash: Option, _auth: Authenticated) -> Template { + let mut context = SettingsMenuContext::build(); + // set back icon link to network route + context.back = Some("/".to_string()); + context.title = Some("Settings".to_string()); + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + Template::render("settings/menu", &context) +} diff --git a/peach-web/src/routes/settings/mod.rs b/peach-web/src/routes/settings/mod.rs index 8fee4d7..530c15b 100644 --- a/peach-web/src/routes/settings/mod.rs +++ b/peach-web/src/routes/settings/mod.rs @@ -1,3 +1,4 @@ pub mod admin; pub mod dns; -pub mod network; \ No newline at end of file +pub mod menu; +pub mod network; diff --git a/peach-web/src/routes/settings/network.rs b/peach-web/src/routes/settings/network.rs index 9b80a37..61ae7be 100644 --- a/peach-web/src/routes/settings/network.rs +++ b/peach-web/src/routes/settings/network.rs @@ -1,27 +1,27 @@ use log::{debug, warn}; use rocket::{ - get, - post, - request::FlashMessage, form::{Form, FromForm}, + get, post, + request::FlashMessage, response::{Flash, Redirect}, + serde::{ + json::{json, Json, Value}, + Deserialize, Serialize, + }, uri, UriDisplayQuery, }; -use rocket::serde::json::{json, Json}; use rocket_dyn_templates::Template; -use rocket::serde::{Deserialize, Serialize}; use std::collections::HashMap; use peach_lib::network_client; use peach_lib::network_client::{AccessPoint, Networks, Scan}; use peach_lib::stats_client::Traffic; +use crate::routes::authentication::Authenticated; +use crate::utils::build_json_response; use crate::utils::monitor; use crate::utils::monitor::{Alert, Data, Threshold}; -use crate::utils::build_json_response; -use crate::routes::authentication::Authenticated; -use rocket::serde::json::Value; // STRUCTS USED BY NETWORK ROUTES @@ -36,9 +36,9 @@ pub struct WiFi { pub pass: String, } -// HELPERS AND ROUTES FOR /network/wifi/usage/reset +// HELPERS AND ROUTES FOR /settings/network/wifi/usage/reset -#[get("/network/wifi/usage/reset")] +#[get("/wifi/usage/reset")] pub fn wifi_usage_reset(_auth: Authenticated) -> Flash { let url = uri!(wifi_usage); match monitor::reset_data() { @@ -50,7 +50,7 @@ pub fn wifi_usage_reset(_auth: Authenticated) -> Flash { } } -#[post("/network/wifi/connect", data = "")] +#[post("/wifi/connect", data = "")] pub fn connect_wifi(network: Form, _auth: Authenticated) -> Flash { let ssid = &network.ssid; let url = uri!(network_detail(ssid = ssid)); @@ -63,7 +63,7 @@ pub fn connect_wifi(network: Form, _auth: Authenticated) -> Flash, _auth: Authenticated) -> Flash { let ssid = &network.ssid; let url = uri!(network_home); @@ -73,7 +73,7 @@ pub fn disconnect_wifi(network: Form, _auth: Authenticated) -> Flash, _auth: Authenticated) -> Flash { let ssid = &network.ssid; let url = uri!(network_home); @@ -86,10 +86,10 @@ pub fn forget_wifi(network: Form, _auth: Authenticated) -> Flash } } -#[get("/network/wifi/modify?")] +#[get("/wifi/modify?")] pub fn wifi_password(ssid: &str, flash: Option, _auth: Authenticated) -> Template { let mut context = NetworkAddContext { - back: Some("/network/wifi".to_string()), + back: Some("/settings/network/wifi".to_string()), flash_name: None, flash_msg: None, selected: Some(ssid.to_string()), @@ -102,10 +102,10 @@ pub fn wifi_password(ssid: &str, flash: Option, _auth: Authenticat context.flash_msg = Some(flash.message().to_string()); }; // template_dir is set in Rocket.toml - Template::render("network_modify", &context) + Template::render("settings/network/network_modify", &context) } -#[post("/network/wifi/modify", data = "")] +#[post("/wifi/modify", data = "")] pub fn wifi_set_password(wifi: Form, _auth: Authenticated) -> Flash { let ssid = &wifi.ssid; let pass = &wifi.pass; @@ -119,7 +119,7 @@ pub fn wifi_set_password(wifi: Form, _auth: Authenticated) -> Flash, _auth: Authenticated) -> Template { // assign context through context_builder call let mut context = NetworkContext::build(); // set back button (nav) url - context.back = Some("/".to_string()); + context.back = Some("/settings".to_string()); // set page title context.title = Some("Network Configuration".to_string()); // check to see if there is a flash message to display @@ -288,25 +288,28 @@ pub fn network_home(flash: Option, _auth: Authenticated) -> Templa context.flash_msg = Some(flash.message().to_string()); }; // template_dir is set in Rocket.toml - Template::render("network_card", &context) + Template::render("settings/network/network_card", &context) } -// HELPERS AND ROUTES FOR /network/ap/activate +// HELPERS AND ROUTES FOR /settings/network/ap/activate -#[get("/network/ap/activate")] +#[get("/ap/activate")] pub fn deploy_ap(_auth: Authenticated) -> Flash { // activate the wireless access point debug!("Activating WiFi access point."); match network_client::activate_ap() { - Ok(_) => Flash::success(Redirect::to("/network"), "Activated WiFi access point"), + Ok(_) => Flash::success( + Redirect::to("/settings/network"), + "Activated WiFi access point", + ), Err(_) => Flash::error( - Redirect::to("/network"), + Redirect::to("/settings/network"), "Failed to activate WiFi access point", ), } } -// HELPERS AND ROUTES FOR /network/wifi +// HELPERS AND ROUTES FOR /settings/network/wifi #[derive(Debug, Serialize)] pub struct NetworkListContext { @@ -375,11 +378,11 @@ impl NetworkListContext { } } -#[get("/network/wifi")] +#[get("/wifi")] pub fn wifi_list(flash: Option, _auth: Authenticated) -> Template { // assign context through context_builder call let mut context = NetworkListContext::build(); - context.back = Some("/network".to_string()); + context.back = Some("/settings/network".to_string()); context.title = Some("WiFi Networks".to_string()); // check to see if there is a flash message to display if let Some(flash) = flash { @@ -388,10 +391,10 @@ pub fn wifi_list(flash: Option, _auth: Authenticated) -> Template context.flash_msg = Some(flash.message().to_string()); }; // template_dir is set in Rocket.toml - Template::render("network_list", &context) + Template::render("settings/network/network_list", &context) } -// HELPERS AND ROUTES FOR /network/wifi +// HELPERS AND ROUTES FOR /settings/network/wifi #[derive(Debug, Serialize)] pub struct NetworkDetailContext { @@ -540,11 +543,11 @@ impl NetworkDetailContext { } } -#[get("/network/wifi?")] +#[get("/wifi?")] pub fn network_detail(ssid: &str, flash: Option, _auth: Authenticated) -> Template { // assign context through context_builder call let mut context = NetworkDetailContext::build(); - context.back = Some("/network/wifi".to_string()); + context.back = Some("/settings/network/wifi".to_string()); context.title = Some("WiFi Network".to_string()); context.selected = Some(ssid.to_string()); // check to see if there is a flash message to display @@ -554,28 +557,31 @@ pub fn network_detail(ssid: &str, flash: Option, _auth: Authentica context.flash_msg = Some(flash.message().to_string()); }; // template_dir is set in Rocket.toml - Template::render("network_detail", &context) + Template::render("settings/network/network_detail", &context) } -// HELPERS AND ROUTES FOR /network/wifi/activate +// HELPERS AND ROUTES FOR /settings/network/wifi/activate -#[get("/network/wifi/activate")] +#[get("/wifi/activate")] pub fn deploy_client(_auth: Authenticated) -> Flash { // activate the wireless client debug!("Activating WiFi client mode."); match network_client::activate_client() { - Ok(_) => Flash::success(Redirect::to("/network"), "Activated WiFi client"), - Err(_) => Flash::error(Redirect::to("/network"), "Failed to activate WiFi client"), + Ok(_) => Flash::success(Redirect::to("/settings/network"), "Activated WiFi client"), + Err(_) => Flash::error( + Redirect::to("/settings/network"), + "Failed to activate WiFi client", + ), } } -// HELPERS AND ROUTES FOR /network/wifi/add +// HELPERS AND ROUTES FOR /settings/network/wifi/add -#[get("/network/wifi/add")] -pub fn network_add_wifi(flash: Option, _auth: Authenticated) -> Template { +#[get("/wifi/add")] +pub fn add_wifi(flash: Option, _auth: Authenticated) -> Template { let mut context = NetworkContext::build(); // set back icon link to network route - context.back = Some("/network".to_string()); + context.back = Some("/settings/network".to_string()); context.title = Some("Add WiFi Network".to_string()); // check to see if there is a flash message to display if let Some(flash) = flash { @@ -584,10 +590,10 @@ pub fn network_add_wifi(flash: Option, _auth: Authenticated) -> Te context.flash_msg = Some(flash.message().to_string()); }; // template_dir is set in Rocket.toml - Template::render("network_add", &context) + Template::render("settings/network/network_add", &context) } -// used in /network/wifi/add? +// used in /settings/network/wifi/add? #[derive(Debug, Serialize)] pub struct NetworkAddContext { pub back: Option, @@ -609,10 +615,10 @@ impl NetworkAddContext { } } -#[get("/network/wifi/add?")] -pub fn network_add_ssid(ssid: &str, flash: Option, _auth: Authenticated) -> Template { +#[get("/wifi/add?")] +pub fn add_ssid(ssid: &str, flash: Option, _auth: Authenticated) -> Template { let mut context = NetworkAddContext::build(); - context.back = Some("/network/wifi".to_string()); + context.back = Some("/settings/network/wifi".to_string()); context.selected = Some(ssid.to_string()); context.title = Some("Add WiFi Network".to_string()); // check to see if there is a flash message to display @@ -622,10 +628,10 @@ pub fn network_add_ssid(ssid: &str, flash: Option, _auth: Authenti context.flash_msg = Some(flash.message().to_string()); }; // template_dir is set in Rocket.toml - Template::render("network_add", &context) + Template::render("settings/network/network_add", &context) } -#[post("/network/wifi/add", data = "")] +#[post("/wifi/add", data = "")] pub fn add_credentials(wifi: Form, _auth: Authenticated) -> Template { // check if the credentials already exist for this access point // note: this is nicer but it's an unstable feature: @@ -634,13 +640,13 @@ pub fn add_credentials(wifi: Form, _auth: Authenticated) -> Template { let creds_exist = network_client::saved_ap(&wifi.ssid).unwrap_or(false); if creds_exist { let mut context = NetworkAddContext::build(); - context.back = Some("/network".to_string()); + context.back = Some("/settings/network".to_string()); context.flash_name = Some("error".to_string()); context.flash_msg = Some("Network credentials already exist for this access point".to_string()); context.title = Some("Add WiFi Network".to_string()); // return early from handler with "creds already exist" message - return Template::render("network_add", &context); + return Template::render("settings/network/network_add", &context); }; // if credentials not found, generate and write wifi config to wpa_supplicant @@ -653,20 +659,20 @@ pub fn add_credentials(wifi: Form, _auth: Authenticated) -> Template { Err(_) => warn!("Failed to reconfigure wpa_supplicant"), } let mut context = NetworkAddContext::build(); - context.back = Some("/network".to_string()); + context.back = Some("/settings/network".to_string()); context.flash_name = Some("success".to_string()); context.flash_msg = Some("Added WiFi credentials".to_string()); context.title = Some("Add WiFi Network".to_string()); - Template::render("network_add", &context) + Template::render("settings/network/network_add", &context) } Err(_) => { debug!("Failed to add WiFi credentials."); let mut context = NetworkAddContext::build(); - context.back = Some("/network".to_string()); + context.back = Some("/settings/network".to_string()); context.flash_name = Some("error".to_string()); context.flash_msg = Some("Failed to add WiFi credentials".to_string()); context.title = Some("Add WiFi Network".to_string()); - Template::render("network_add", &context) + Template::render("settings/network/network_add", &context) } } } @@ -719,11 +725,11 @@ impl NetworkAlertContext { } } -#[get("/network/wifi/usage")] +#[get("/wifi/usage")] pub fn wifi_usage(flash: Option, _auth: Authenticated) -> Template { let mut context = NetworkAlertContext::build(); // set back icon link to network route - context.back = Some("/network".to_string()); + context.back = Some("/settings/network".to_string()); context.title = Some("Network Data Usage".to_string()); // check to see if there is a flash message to display if let Some(flash) = flash { @@ -732,30 +738,32 @@ pub fn wifi_usage(flash: Option, _auth: Authenticated) -> Template context.flash_msg = Some(flash.message().to_string()); }; // template_dir is set in Rocket.toml - Template::render("network_usage", &context) + Template::render("settings/network/network_usage", &context) } -#[post("/network/wifi/usage", data = "")] +#[post("/wifi/usage", data = "")] pub fn wifi_usage_alerts(thresholds: Form, _auth: Authenticated) -> Flash { match monitor::update_store(thresholds.into_inner()) { Ok(_) => { debug!("WiFi data usage thresholds updated."); Flash::success( - Redirect::to("/network/wifi/usage"), + Redirect::to("/settings/network/wifi/usage"), "Updated alert thresholds and flags", ) } Err(_) => { warn!("Failed to update WiFi data usage thresholds."); Flash::error( - Redirect::to("/network/wifi/usage"), + Redirect::to("/settings/network/wifi/usage"), "Failed to update alert thresholds and flags", ) } } } -#[post("/api/v1/network/wifi/usage", data = "")] +// JSON ROUTES FOR NETWORK SETTINGS + +#[post("/wifi/usage", data = "")] pub fn update_wifi_alerts(thresholds: Json, _auth: Authenticated) -> Value { match monitor::update_store(thresholds.into_inner()) { Ok(_) => { @@ -773,7 +781,7 @@ pub fn update_wifi_alerts(thresholds: Json, _auth: Authenticated) -> } } -#[post("/api/v1/network/wifi/usage/reset")] +#[post("/wifi/usage/reset")] pub fn reset_data_total(_auth: Authenticated) -> Value { match monitor::reset_data() { Ok(_) => { @@ -805,7 +813,7 @@ pub fn reset_data_total(_auth: Authenticated) -> Value { // HELPERS AND ROUTES FOR ACCESS POINT ACTIVATION -#[post("/api/v1/network/activate_ap")] +#[post("/activate_ap")] pub fn activate_ap(_auth: Authenticated) -> Value { // activate the wireless access point debug!("Activating WiFi access point."); @@ -824,7 +832,7 @@ pub fn activate_ap(_auth: Authenticated) -> Value { // HELPERS AND ROUTES FOR WIFI CLIENT MANAGEMENT -#[post("/api/v1/network/activate_client")] +#[post("/activate_client")] pub fn activate_client(_auth: Authenticated) -> Value { // activate the wireless client debug!("Activating WiFi client mode."); @@ -841,8 +849,8 @@ pub fn activate_client(_auth: Authenticated) -> Value { } } -#[post("/api/v1/network/wifi", data = "")] -pub fn add_wifi(wifi: Json, _auth: Authenticated) -> Value { +#[post("/wifi", data = "")] +pub fn add_wifi_credentials(wifi: Json, _auth: Authenticated) -> Value { // generate and write wifi config to wpa_supplicant match network_client::add(&wifi.ssid, &wifi.pass) { Ok(_) => { @@ -867,7 +875,7 @@ pub fn add_wifi(wifi: Json, _auth: Authenticated) -> Value { } } -#[post("/api/v1/network/wifi/connect", data = "")] +#[post("/wifi/connect", data = "")] pub fn connect_ap(ssid: Json, _auth: Authenticated) -> Value { // retrieve the id for the given network ssid match network_client::id("wlan0", &ssid.ssid) { @@ -892,7 +900,7 @@ pub fn connect_ap(ssid: Json, _auth: Authenticated) -> Value { } } -#[post("/api/v1/network/wifi/disconnect", data = "")] +#[post("/wifi/disconnect", data = "")] pub fn disconnect_ap(ssid: Json, _auth: Authenticated) -> Value { // attempt to disable the current network for wlan0 interface match network_client::disable("wlan0", &ssid.ssid) { @@ -909,7 +917,7 @@ pub fn disconnect_ap(ssid: Json, _auth: Authenticated) -> Value { } } -#[post("/api/v1/network/wifi/forget", data = "")] +#[post("/wifi/forget", data = "")] pub fn forget_ap(network: Json, _auth: Authenticated) -> Value { let ssid = &network.ssid; match network_client::forget("wlan0", ssid) { @@ -928,7 +936,7 @@ pub fn forget_ap(network: Json, _auth: Authenticated) -> Value { } } -#[post("/api/v1/network/wifi/modify", data = "")] +#[post("/wifi/modify", data = "")] pub fn modify_password(wifi: Json, _auth: Authenticated) -> Value { let ssid = &wifi.ssid; let pass = &wifi.pass; @@ -953,7 +961,7 @@ pub fn modify_password(wifi: Json, _auth: Authenticated) -> Value { // HELPERS AND ROUTES FOR NETWORK STATE QUERIES -#[get("/api/v1/network/ip")] +#[get("/ip")] pub fn return_ip(_auth: Authenticated) -> Value { // retrieve ip for wlan0 or set to x.x.x.x if not found let wlan_ip = match network_client::ip("wlan0") { @@ -973,7 +981,7 @@ pub fn return_ip(_auth: Authenticated) -> Value { build_json_response(status, Some(data), None) } -#[get("/api/v1/network/rssi")] +#[get("/rssi")] pub fn return_rssi(_auth: Authenticated) -> Value { // retrieve rssi for connected network match network_client::rssi("wlan0") { @@ -990,7 +998,7 @@ pub fn return_rssi(_auth: Authenticated) -> Value { } } -#[get("/api/v1/network/ssid")] +#[get("/ssid")] pub fn return_ssid(_auth: Authenticated) -> Value { // retrieve ssid for connected network match network_client::ssid("wlan0") { @@ -1007,7 +1015,7 @@ pub fn return_ssid(_auth: Authenticated) -> Value { } } -#[get("/api/v1/network/state")] +#[get("/state")] pub fn return_state(_auth: Authenticated) -> Value { // retrieve state of wlan0 or set to x.x.x.x if not found let wlan_state = match network_client::state("wlan0") { @@ -1027,7 +1035,7 @@ pub fn return_state(_auth: Authenticated) -> Value { build_json_response(status, Some(data), None) } -#[get("/api/v1/network/status")] +#[get("/status")] pub fn return_status(_auth: Authenticated) -> Value { // retrieve status info for wlan0 interface match network_client::status("wlan0") { @@ -1044,7 +1052,7 @@ pub fn return_status(_auth: Authenticated) -> Value { } } -#[get("/api/v1/network/wifi")] +#[get("/wifi")] pub fn scan_networks(_auth: Authenticated) -> Value { // retrieve scan results for access-points within range of wlan0 match network_client::available_networks("wlan0") { diff --git a/peach-web/src/routes/device.rs b/peach-web/src/routes/status/device.rs similarity index 86% rename from peach-web/src/routes/device.rs rename to peach-web/src/routes/status/device.rs index d06983b..f67c9aa 100644 --- a/peach-web/src/routes/device.rs +++ b/peach-web/src/routes/status/device.rs @@ -16,15 +16,15 @@ use peach_lib::config_manager::load_peach_config; use peach_lib::stats_client::{CpuStatPercentages, DiskUsage, LoadAverage, MemStat}; use peach_lib::{dyndns_client, network_client, oled_client, sbot_client, stats_client}; -use crate::utils::build_json_response; use crate::routes::authentication::Authenticated; +use crate::utils::build_json_response; use rocket::serde::json::Value; -// HELPERS AND ROUTES FOR /device +// HELPERS AND ROUTES FOR /status /// System statistics data. #[derive(Debug, Serialize)] -pub struct DeviceContext { +pub struct StatusContext { pub back: Option, pub cpu_stat_percent: Option, pub disk_stats: Vec, @@ -43,8 +43,8 @@ pub struct DeviceContext { pub uptime: Option, } -impl DeviceContext { - pub fn build() -> DeviceContext { +impl StatusContext { + pub fn build() -> StatusContext { // convert result to Option, discard any error let cpu_stat_percent = stats_client::cpu_stats_percent().ok(); let load_average = stats_client::load_average().ok(); @@ -129,7 +129,7 @@ impl DeviceContext { } } - DeviceContext { + StatusContext { back: None, cpu_stat_percent, disk_stats, @@ -150,10 +150,10 @@ impl DeviceContext { } } -#[get("/device")] -pub fn device_stats(flash: Option, _auth: Authenticated) -> Template { +#[get("/status")] +pub fn device_status(flash: Option, _auth: Authenticated) -> Template { // assign context through context_builder call - let mut context = DeviceContext::build(); + let mut context = StatusContext::build(); context.back = Some("/".to_string()); context.title = Some("Device Status".to_string()); // check to see if there is a flash message to display @@ -166,7 +166,7 @@ pub fn device_stats(flash: Option, _auth: Authenticated) -> Templa Template::render("device", &context) } -// HELPERS AND ROUTES FOR /device/reboot +// HELPERS AND ROUTES FOR /power/reboot /// Executes a system command to reboot the device immediately. pub fn reboot() -> io::Result { @@ -181,16 +181,16 @@ pub fn reboot() -> io::Result { .output() } -#[get("/device/reboot")] +#[get("/power/reboot")] pub fn reboot_cmd(_auth: Authenticated) -> Flash { match reboot() { - Ok(_) => Flash::success(Redirect::to("/shutdown"), "Rebooting the device"), - Err(_) => Flash::error(Redirect::to("/shutdown"), "Failed to reboot the device"), + Ok(_) => Flash::success(Redirect::to("/power"), "Rebooting the device"), + Err(_) => Flash::error(Redirect::to("/power"), "Failed to reboot the device"), } } /// JSON request handler for device reboot. -#[post("/api/v1/device/reboot")] +#[post("/api/v1/admin/reboot")] pub fn reboot_device(_auth: Authenticated) -> Value { match reboot() { Ok(_) => { @@ -208,7 +208,7 @@ pub fn reboot_device(_auth: Authenticated) -> Value { } } -// HELPERS AND ROUTES FOR /device/shutdown +// HELPERS AND ROUTES FOR /power/shutdown /// Executes a system command to shutdown the device immediately. pub fn shutdown() -> io::Result { @@ -219,16 +219,16 @@ pub fn shutdown() -> io::Result { Command::new("sudo").arg("shutdown").arg("now").output() } -#[get("/device/shutdown")] +#[get("/power/shutdown")] pub fn shutdown_cmd(_auth: Authenticated) -> Flash { match shutdown() { - Ok(_) => Flash::success(Redirect::to("/shutdown"), "Shutting down the device"), - Err(_) => Flash::error(Redirect::to("/shutdown"), "Failed to shutdown the device"), + Ok(_) => Flash::success(Redirect::to("/power"), "Shutting down the device"), + Err(_) => Flash::error(Redirect::to("/power"), "Failed to shutdown the device"), } } // shutdown the device -#[post("/api/v1/device/shutdown")] +#[post("/power/shutdown")] pub fn shutdown_device(_auth: Authenticated) -> Value { match shutdown() { Ok(_) => { @@ -246,19 +246,19 @@ pub fn shutdown_device(_auth: Authenticated) -> Value { } } -// HELPERS AND ROUTES FOR /shutdown +// HELPERS AND ROUTES FOR /power #[derive(Debug, Serialize)] -pub struct ShutdownContext { +pub struct PowerContext { pub back: Option, pub flash_name: Option, pub flash_msg: Option, pub title: Option, } -impl ShutdownContext { - pub fn build() -> ShutdownContext { - ShutdownContext { +impl PowerContext { + pub fn build() -> PowerContext { + PowerContext { back: None, flash_name: None, flash_msg: None, @@ -267,16 +267,16 @@ impl ShutdownContext { } } -#[get("/shutdown")] -pub fn shutdown_menu(flash: Option, _auth: Authenticated) -> Template { - let mut context = ShutdownContext::build(); +#[get("/power")] +pub fn power_menu(flash: Option, _auth: Authenticated) -> Template { + let mut context = PowerContext::build(); context.back = Some("/".to_string()); - context.title = Some("Shutdown Device".to_string()); + context.title = Some("Power Menu".to_string()); // check to see if there is a flash message to display if let Some(flash) = flash { // add flash message contents to the context object context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - Template::render("shutdown", &context) + Template::render("power", &context) } diff --git a/peach-web/src/routes/status/mod.rs b/peach-web/src/routes/status/mod.rs new file mode 100644 index 0000000..cd4d59c --- /dev/null +++ b/peach-web/src/routes/status/mod.rs @@ -0,0 +1,2 @@ +pub mod device; +pub mod ping; diff --git a/peach-web/src/routes/ping.rs b/peach-web/src/routes/status/ping.rs similarity index 93% rename from peach-web/src/routes/ping.rs rename to peach-web/src/routes/status/ping.rs index aed39ec..3f83002 100644 --- a/peach-web/src/routes/ping.rs +++ b/peach-web/src/routes/status/ping.rs @@ -1,19 +1,19 @@ //! Helper routes for pinging services to check that they are active use log::{debug, warn}; use rocket::get; -use rocket::serde::json::{Value}; +use rocket::serde::json::Value; use peach_lib::network_client; use peach_lib::oled_client; use peach_lib::stats_client; -use crate::utils::build_json_response; use crate::routes::authentication::Authenticated; +use crate::utils::build_json_response; /// Status route: useful for checking connectivity from web client. -#[get("/api/v1/ping")] +#[get("/ping")] pub fn ping_pong(_auth: Authenticated) -> Value { -//pub fn ping_pong() -> Value { + //pub fn ping_pong() -> Value { // ping pong let status = "success".to_string(); let msg = "pong!".to_string(); @@ -21,7 +21,7 @@ pub fn ping_pong(_auth: Authenticated) -> Value { } /// Status route: check availability of `peach-network` microservice. -#[get("/api/v1/ping/network")] +#[get("/ping/network")] pub fn ping_network(_auth: Authenticated) -> Value { match network_client::ping() { Ok(_) => { @@ -40,7 +40,7 @@ pub fn ping_network(_auth: Authenticated) -> Value { } /// Status route: check availability of `peach-oled` microservice. -#[get("/api/v1/ping/oled")] +#[get("/ping/oled")] pub fn ping_oled(_auth: Authenticated) -> Value { match oled_client::ping() { Ok(_) => { @@ -59,7 +59,7 @@ pub fn ping_oled(_auth: Authenticated) -> Value { } /// Status route: check availability of `peach-stats` microservice. -#[get("/api/v1/ping/stats")] +#[get("/ping/stats")] pub fn ping_stats(_auth: Authenticated) -> Value { match stats_client::ping() { Ok(_) => { -- 2.40.1 From 0cb66c23ba74a48072d6c4b2dcb7ec20a397b37f Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 17:32:21 +0200 Subject: [PATCH 08/16] set route mountpoints --- peach-web/src/main.rs | 178 +++++++++++++++++++++++------------------- 1 file changed, 99 insertions(+), 79 deletions(-) diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index ab1941f..5362010 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -38,13 +38,14 @@ use rocket_dyn_templates::Template; use crate::routes::authentication::*; use crate::routes::catchers::*; -use crate::routes::device::*; use crate::routes::index::*; -use crate::routes::ping::*; use crate::routes::scuttlebutt::*; +use crate::routes::status::device::*; +use crate::routes::status::ping::*; use crate::routes::settings::admin::*; use crate::routes::settings::dns::*; +use crate::routes::settings::menu::*; use crate::routes::settings::network::*; pub type BoxError = Box; @@ -52,86 +53,105 @@ pub type BoxError = Box; /// Create rocket instance & mount all routes. fn init_rocket() -> Rocket { rocket::build() - .mount( - "/scuttlebutt", - routes![ - peers, // WEB ROUTE - friends, // WEB ROUTE - follows, // WEB ROUTE - followers, // WEB ROUTE - blocks, // WEB ROUTE - profile, // WEB ROUTE - private, // WEB ROUTE - follow, // WEB ROUTE - unfollow, // WEB ROUTE - block, // WEB ROUTE - publish, // WEB ROUTE - ], - ) + // GENERAL HTML ROUTES .mount( "/", routes![ - add_credentials, // WEB ROUTE - connect_wifi, // WEB ROUTE - disconnect_wifi, // WEB ROUTE - deploy_ap, // WEB ROUTE - deploy_client, // WEB ROUTE - device_stats, // WEB ROUTE - forget_wifi, // WEB ROUTE - help, // WEB ROUTE - index, // WEB ROUTE - login, // WEB ROUTE - login_post, // WEB ROUTE - logout, // WEB ROUTE - network_home, // WEB ROUTE - network_add_ssid, // WEB ROUTE - network_add_wifi, // WEB ROUTE - network_detail, // WEB ROUTE - reboot_cmd, // WEB ROUTE - shutdown_cmd, // WEB ROUTE - shutdown_menu, // WEB ROUTE - wifi_list, // WEB ROUTE - wifi_password, // WEB ROUTE - wifi_set_password, // WEB ROUTE - wifi_usage, // WEB ROUTE - wifi_usage_alerts, // WEB ROUTE - wifi_usage_reset, // WEB ROUTE - configure_dns, // WEB ROUTE - configure_dns_post, // WEB ROUTE - change_password, // WEB ROUTE - change_password_post, // WEB ROUTE - reset_password, // WEB ROUTE - reset_password_post, // WEB ROUTE - forgot_password_page, // WEB ROUTE - send_password_reset_post, // WEB ROUTE - configure_admin, // WEB ROUTE - add_admin, // WEB ROUTE - add_admin_post, // WEB ROUTE - delete_admin_post, // WEB ROUTE - activate_ap, // JSON API - activate_client, // JSON API - add_wifi, // JSON API - connect_ap, // JSON API - disconnect_ap, // JSON API - forget_ap, // JSON API - modify_password, // JSON API - ping_pong, // JSON API - ping_network, // JSON API - ping_oled, // JSON API - ping_stats, // JSON API - reset_data_total, // JSON API - return_ip, // JSON API - return_rssi, // JSON API - return_ssid, // JSON API - return_state, // JSON API - return_status, // JSON API - reboot_device, // JSON API - scan_networks, // JSON API - shutdown_device, // JSON API - update_wifi_alerts, // JSON API - save_dns_configuration_endpoint, // JSON API - save_password_form_endpoint, // JSON API - reset_password_form_endpoint, // JSON API + device_status, + help, + home, + login, + login_post, + logout, + reboot_cmd, + shutdown_cmd, + power_menu, + settings_menu, + ], + ) + // ADMIN SETTINGS HTML ROUTES + .mount( + "/settings/admin", + routes![ + configure_admin, + add_admin, + add_admin_post, + delete_admin_post, + change_password, + change_password_post, + reset_password, + reset_password_post, + forgot_password_page, + send_password_reset_post, + ], + ) + // NETWORK SETTINGS HTML ROUTES + .mount( + "/settings/network", + routes![ + add_credentials, + connect_wifi, + configure_dns, + configure_dns_post, + disconnect_wifi, + deploy_ap, + deploy_client, + forget_wifi, + network_home, + add_ssid, + add_wifi, + network_detail, + wifi_list, + wifi_password, + wifi_set_password, + wifi_usage, + wifi_usage_alerts, + wifi_usage_reset, + ], + ) + // SCUTTLEBUTT HTML ROUTES + .mount( + "/scuttlebutt", + routes![ + peers, friends, follows, followers, blocks, profile, private, follow, unfollow, + block, publish, + ], + ) + // GENERAL JSON API ROUTES + .mount( + "/api/v1", + routes![ping_pong, ping_network, ping_oled, ping_stats,], + ) + // ADMIN JSON API ROUTES + .mount( + "/api/v1/admin", + routes![ + save_password_form_endpoint, + reset_password_form_endpoint, + reboot_device, + shutdown_device, + ], + ) + // NETWORK JSON API ROUTES + .mount( + "/api/v1/network", + routes![ + activate_ap, + activate_client, + add_wifi_credentials, + connect_ap, + disconnect_ap, + forget_ap, + modify_password, + reset_data_total, + return_ip, + return_rssi, + return_ssid, + return_state, + return_status, + scan_networks, + update_wifi_alerts, + save_dns_configuration_endpoint, ], ) .mount("/", FileServer::from("static")) -- 2.40.1 From 03a746431e4f39c7168ba110845b004682a15bf1 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 17:32:30 +0200 Subject: [PATCH 09/16] update tests --- peach-web/src/tests.rs | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/peach-web/src/tests.rs b/peach-web/src/tests.rs index 931af3e..251de09 100644 --- a/peach-web/src/tests.rs +++ b/peach-web/src/tests.rs @@ -47,15 +47,15 @@ fn index_html() { assert!(body.contains("/peers")); assert!(body.contains("/profile")); assert!(body.contains("/private")); - assert!(body.contains("/device")); + assert!(body.contains("/status")); assert!(body.contains("/help")); - assert!(body.contains("/network")); + assert!(body.contains("/settings")); } #[test] fn network_card_html() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); - let response = client.get("/network").dispatch(); + let response = client.get("/settings/network").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); let body = response.into_string().unwrap(); @@ -73,7 +73,7 @@ fn network_card_html() { #[test] fn network_list_html() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); - let response = client.get("/network/wifi").dispatch(); + let response = client.get("/settings/network/wifi").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); let body = response.into_string().unwrap(); @@ -85,7 +85,7 @@ fn network_list_html() { #[test] fn network_detail_html() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); - let response = client.get("/network/wifi?ssid=Home").dispatch(); + let response = client.get("/settings/network/wifi?ssid=Home").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); //let body = response.into_string().unwrap(); @@ -95,7 +95,7 @@ fn network_detail_html() { #[test] fn network_add_html() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); - let response = client.get("/network/wifi/add").dispatch(); + let response = client.get("/settings/network/wifi/add").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); let body = response.into_string().unwrap(); @@ -109,7 +109,9 @@ fn network_add_html() { #[test] fn network_add_ssid_html() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); - let response = client.get("/network/wifi/add?ssid=Home").dispatch(); + let response = client + .get("/settings/network/wifi/add?ssid=Home") + .dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); let body = response.into_string().unwrap(); @@ -121,9 +123,9 @@ fn network_add_ssid_html() { } #[test] -fn device_html() { +fn status_html() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); - let response = client.get("/device").dispatch(); + let response = client.get("/status").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); let body = response.into_string().unwrap(); @@ -269,9 +271,9 @@ fn profile_html() { } #[test] -fn shutdown_html() { +fn power_html() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); - let response = client.get("/shutdown").dispatch(); + let response = client.get("/power").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); let body = response.into_string().unwrap(); @@ -281,7 +283,7 @@ fn shutdown_html() { #[test] fn network_usage_html() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); - let response = client.get("/network/wifi/usage").dispatch(); + let response = client.get("/settings/network/wifi/usage").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); let body = response.into_string().unwrap(); @@ -295,7 +297,7 @@ fn network_usage_html() { fn add_credentials() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let response = client - .post("/network/wifi/add") + .post("/settings/network/wifi/add") .header(ContentType::Form) .body("ssid=Home&pass=Password") .dispatch(); @@ -307,7 +309,7 @@ fn add_credentials() { fn forget_wifi() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let response = client - .post("/network/wifi/forget") + .post("/settings/network/wifi/forget") .header(ContentType::Form) .body("ssid=Home") .dispatch(); @@ -319,7 +321,7 @@ fn forget_wifi() { fn modify_password() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); let response = client - .post("/network/wifi/modify") + .post("/settings/network/wifi/modify") .header(ContentType::Form) .body("ssid=Home&pass=Password") .dispatch(); @@ -330,7 +332,7 @@ fn modify_password() { #[test] fn deploy_ap() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); - let response = client.get("/network/ap/activate").dispatch(); + let response = client.get("/settings/network/ap/activate").dispatch(); // check for 303 status (redirect) assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.content_type(), None); @@ -339,7 +341,7 @@ fn deploy_ap() { #[test] fn deploy_client() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); - let response = client.get("/network/wifi/activate").dispatch(); + let response = client.get("/settings/network/wifi/activate").dispatch(); // check for 303 status (redirect) assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.content_type(), None); -- 2.40.1 From 1506e89c5ee64325728765f20a5d43f17a3d62d1 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 17:53:51 +0200 Subject: [PATCH 10/16] move admin templates to settings --- peach-web/src/routes/settings/admin.rs | 4 ++-- peach-web/templates/{ => settings}/admin/add_admin.html.tera | 0 .../templates/{ => settings}/admin/change_password.html.tera | 0 .../templates/{ => settings}/admin/configure_admin.html.tera | 0 .../templates/{ => settings}/admin/forgot_password.html.tera | 0 .../templates/{ => settings}/admin/reset_password.html.tera | 0 6 files changed, 2 insertions(+), 2 deletions(-) rename peach-web/templates/{ => settings}/admin/add_admin.html.tera (100%) rename peach-web/templates/{ => settings}/admin/change_password.html.tera (100%) rename peach-web/templates/{ => settings}/admin/configure_admin.html.tera (100%) rename peach-web/templates/{ => settings}/admin/forgot_password.html.tera (100%) rename peach-web/templates/{ => settings}/admin/reset_password.html.tera (100%) diff --git a/peach-web/src/routes/settings/admin.rs b/peach-web/src/routes/settings/admin.rs index 5148a17..0171f07 100644 --- a/peach-web/src/routes/settings/admin.rs +++ b/peach-web/src/routes/settings/admin.rs @@ -52,7 +52,7 @@ pub fn configure_admin(flash: Option, _auth: Authenticated) -> Tem context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - Template::render("admin/configure_admin", &context) + Template::render("settings/admin/configure_admin", &context) } // HELPERS AND ROUTES FOR /settings/admin/add @@ -99,7 +99,7 @@ pub fn add_admin(flash: Option, _auth: Authenticated) -> Template context.flash_msg = Some(flash.message().to_string()); }; // template_dir is set in Rocket.toml - Template::render("admin/add_admin", &context) + Template::render("settings/admin/add_admin", &context) } #[post("/add", data = "")] diff --git a/peach-web/templates/admin/add_admin.html.tera b/peach-web/templates/settings/admin/add_admin.html.tera similarity index 100% rename from peach-web/templates/admin/add_admin.html.tera rename to peach-web/templates/settings/admin/add_admin.html.tera diff --git a/peach-web/templates/admin/change_password.html.tera b/peach-web/templates/settings/admin/change_password.html.tera similarity index 100% rename from peach-web/templates/admin/change_password.html.tera rename to peach-web/templates/settings/admin/change_password.html.tera diff --git a/peach-web/templates/admin/configure_admin.html.tera b/peach-web/templates/settings/admin/configure_admin.html.tera similarity index 100% rename from peach-web/templates/admin/configure_admin.html.tera rename to peach-web/templates/settings/admin/configure_admin.html.tera diff --git a/peach-web/templates/admin/forgot_password.html.tera b/peach-web/templates/settings/admin/forgot_password.html.tera similarity index 100% rename from peach-web/templates/admin/forgot_password.html.tera rename to peach-web/templates/settings/admin/forgot_password.html.tera diff --git a/peach-web/templates/admin/reset_password.html.tera b/peach-web/templates/settings/admin/reset_password.html.tera similarity index 100% rename from peach-web/templates/admin/reset_password.html.tera rename to peach-web/templates/settings/admin/reset_password.html.tera -- 2.40.1 From 61828082fda50d40ec928798e0282cb19a780844 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 20:55:46 +0200 Subject: [PATCH 11/16] add scuttlebutt settings routes and template --- peach-web/src/routes/settings/scuttlebutt.rs | 41 +++++++++++++++++++ ...ttings.html.tera => scuttlebutt.html.tera} | 0 2 files changed, 41 insertions(+) create mode 100644 peach-web/src/routes/settings/scuttlebutt.rs rename peach-web/templates/settings/{ssb_settings.html.tera => scuttlebutt.html.tera} (100%) diff --git a/peach-web/src/routes/settings/scuttlebutt.rs b/peach-web/src/routes/settings/scuttlebutt.rs new file mode 100644 index 0000000..bd1a129 --- /dev/null +++ b/peach-web/src/routes/settings/scuttlebutt.rs @@ -0,0 +1,41 @@ +use rocket::{get, request::FlashMessage, serde::Serialize}; +use rocket_dyn_templates::Template; + +use crate::routes::authentication::Authenticated; + +// HELPERS AND ROUTES FOR /settings/scuttlebutt + +#[derive(Debug, Serialize)] +pub struct ScuttlebuttSettingsContext { + pub back: Option, + pub title: Option, + pub flash_name: Option, + pub flash_msg: Option, +} + +impl ScuttlebuttSettingsContext { + pub fn build() -> ScuttlebuttSettingsContext { + ScuttlebuttSettingsContext { + back: None, + title: None, + flash_name: None, + flash_msg: None, + } + } +} + +/// Scuttlebutt settings menu. +#[get("/")] +pub fn ssb_settings_menu(flash: Option, _auth: Authenticated) -> Template { + let mut context = ScuttlebuttSettingsContext::build(); + // set back icon link to network route + context.back = Some("/settings".to_string()); + context.title = Some("Scuttlebutt Settings".to_string()); + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + Template::render("settings/scuttlebutt", &context) +} diff --git a/peach-web/templates/settings/ssb_settings.html.tera b/peach-web/templates/settings/scuttlebutt.html.tera similarity index 100% rename from peach-web/templates/settings/ssb_settings.html.tera rename to peach-web/templates/settings/scuttlebutt.html.tera -- 2.40.1 From d9a7cf36226204b9592ad37e7cb6a92f83b0d00d Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 20:56:22 +0200 Subject: [PATCH 12/16] update auth and admin routes --- peach-web/src/routes/authentication.rs | 76 +++++++++++--------------- peach-web/src/routes/settings/admin.rs | 47 ++++++++++++++-- peach-web/src/routes/settings/mod.rs | 1 + 3 files changed, 76 insertions(+), 48 deletions(-) diff --git a/peach-web/src/routes/authentication.rs b/peach-web/src/routes/authentication.rs index 7c71799..1d1a9b6 100644 --- a/peach-web/src/routes/authentication.rs +++ b/peach-web/src/routes/authentication.rs @@ -1,23 +1,20 @@ -use log::{info}; -use rocket::request::{FlashMessage}; +use log::info; use rocket::form::{Form, FromForm}; +use rocket::request::FlashMessage; use rocket::response::{Flash, Redirect}; -use rocket::{get, post}; use rocket::serde::json::Json; -use rocket_dyn_templates::Template; use rocket::serde::{Deserialize, Serialize}; +use rocket::{get, post}; +use rocket_dyn_templates::Template; -use peach_lib::password_utils; use peach_lib::error::PeachError; +use peach_lib::password_utils; use crate::error::PeachWebError; use crate::utils::{build_json_response, TemplateOrRedirect}; -use rocket::serde::json::Value; +use rocket::http::{Cookie, CookieJar, Status}; use rocket::request::{self, FromRequest, Request}; -use rocket::http::{Cookie, CookieJar, Status}; - - - +use rocket::serde::json::Value; // HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES @@ -32,7 +29,7 @@ pub struct Authenticated; #[derive(Debug)] pub enum LoginError { - UserNotLoggedIn + UserNotLoggedIn, } /// Request guard which returns an empty Authenticated struct from the request @@ -49,14 +46,10 @@ impl<'r> FromRequest<'r> for Authenticated { .cookies() .get_private(AUTH_COOKIE_KEY) .and_then(|cookie| cookie.value().parse().ok()) - .map(|_value: String| { Authenticated { } }); + .map(|_value: String| Authenticated {}); match authenticated { - Some(auth) => { - request::Outcome::Success(auth) - }, - None => { - request::Outcome::Failure((Status::Forbidden, LoginError::UserNotLoggedIn)) - } + Some(auth) => request::Outcome::Success(auth), + None => request::Outcome::Failure((Status::Forbidden, LoginError::UserNotLoggedIn)), } } } @@ -96,7 +89,6 @@ pub fn login(flash: Option) -> Template { Template::render("login", &context) } - #[derive(Debug, Deserialize, FromForm)] pub struct LoginForm { pub username: String, @@ -112,7 +104,7 @@ pub fn verify_login_form(login_form: LoginForm) -> Result<(), PeachError> { password_utils::verify_password(&login_form.password) } -#[post("/login", data="")] +#[post("/login", data = "")] pub fn login_post(login_form: Form, cookies: &CookieJar<'_>) -> TemplateOrRedirect { let result = verify_login_form(login_form.into_inner()); match result { @@ -138,7 +130,6 @@ pub fn login_post(login_form: Form, cookies: &CookieJar<'_>) -> Templ } } - // HELPERS AND ROUTES FOR /logout #[get("/logout")] @@ -149,7 +140,6 @@ pub fn logout(cookies: &CookieJar<'_>) -> Flash { Flash::success(Redirect::to("/login"), "Logged out") } - // HELPERS AND ROUTES FOR /reset_password #[derive(Debug, Deserialize, FromForm)] @@ -228,7 +218,7 @@ pub fn reset_password(flash: Option) -> Template { context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - Template::render("password/reset_password", &context) + Template::render("settings/admin/reset_password", &context) } /// Password reset form request handler. This route is used by a user who is not logged in @@ -245,7 +235,7 @@ pub fn reset_password_post(reset_password_form: Form) -> Temp context.flash_name = Some("success".to_string()); let flash_msg = "New password is now saved. Return home to login".to_string(); context.flash_msg = Some(flash_msg); - Template::render("password/reset_password", &context) + Template::render("settings/admin/reset_password", &context) } Err(err) => { let mut context = ChangePasswordContext::build(); @@ -254,18 +244,15 @@ pub fn reset_password_post(reset_password_form: Form) -> Temp context.title = Some("Reset Password".to_string()); context.flash_name = Some("error".to_string()); context.flash_msg = Some(format!("Failed to reset password: {}", err)); - Template::render("password/reset_password", &context) + Template::render("settings/admin/reset_password", &context) } } } /// JSON password reset form request handler. This route is used by a user who is not logged in /// and is specifically for users who have forgotten their password. -/// All routes under /public/* are excluded from nginx basic auth via the nginx config. -#[post("/public/api/v1/reset_password", data = "")] -pub fn reset_password_form_endpoint( - reset_password_form: Json, -) -> Value { +#[post("/reset_password", data = "")] +pub fn reset_password_form_endpoint(reset_password_form: Json) -> Value { let result = save_reset_password_form(reset_password_form.into_inner()); match result { Ok(_) => { @@ -316,7 +303,7 @@ pub fn forgot_password_page(flash: Option) -> Template { context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - Template::render("password/forgot_password", &context) + Template::render("settings/admin/forgot_password", &context) } /// Send password reset request handler. This route is used by a user who is not logged in @@ -335,7 +322,7 @@ pub fn send_password_reset_post() -> Template { let flash_msg = "A password reset link has been sent to the admin of this device".to_string(); context.flash_msg = Some(flash_msg); - Template::render("password/forgot_password", &context) + Template::render("settings/admin/forgot_password", &context) } Err(err) => { let mut context = ChangePasswordContext::build(); @@ -343,7 +330,7 @@ pub fn send_password_reset_post() -> Template { context.title = Some("Send Password Reset".to_string()); context.flash_name = Some("error".to_string()); context.flash_msg = Some(format!("Failed to send password reset link: {}", err)); - Template::render("password/forgot_password", &context) + Template::render("settings/admin/forgot_password", &context) } } } @@ -375,11 +362,11 @@ pub fn save_password_form(password_form: PasswordForm) -> Result<(), PeachWebErr } /// Change password request handler. This is used by a user who is already logged in. -#[get("/settings/change_password")] +#[get("/change_password")] pub fn change_password(flash: Option, _auth: Authenticated) -> Template { let mut context = ChangePasswordContext::build(); // set back icon link to network route - context.back = Some("/network".to_string()); + context.back = Some("/settings/admin".to_string()); context.title = Some("Change Password".to_string()); // check to see if there is a flash message to display if let Some(flash) = flash { @@ -387,39 +374,42 @@ pub fn change_password(flash: Option, _auth: Authenticated) -> Tem context.flash_name = Some(flash.kind().to_string()); context.flash_msg = Some(flash.message().to_string()); }; - Template::render("password/change_password", &context) + Template::render("settings/admin/change_password", &context) } /// Change password form request handler. This route is used by a user who is already logged in. -#[post("/settings/change_password", data = "")] +#[post("/change_password", data = "")] pub fn change_password_post(password_form: Form, _auth: Authenticated) -> Template { let result = save_password_form(password_form.into_inner()); match result { Ok(_) => { let mut context = ChangePasswordContext::build(); // set back icon link to network route - context.back = Some("/network".to_string()); + context.back = Some("/settings/admin".to_string()); context.title = Some("Change Password".to_string()); context.flash_name = Some("success".to_string()); context.flash_msg = Some("New password is now saved".to_string()); // template_dir is set in Rocket.toml - Template::render("password/change_password", &context) + Template::render("settings/admin/change_password", &context) } Err(err) => { let mut context = ChangePasswordContext::build(); // set back icon link to network route - context.back = Some("/network".to_string()); + context.back = Some("/settings/admin".to_string()); context.title = Some("Configure DNS".to_string()); context.flash_name = Some("error".to_string()); context.flash_msg = Some(format!("Failed to save new password: {}", err)); - Template::render("password/change_password", &context) + Template::render("settings/admin/change_password", &context) } } } /// JSON change password form request handler. -#[post("/api/v1/settings/change_password", data = "")] -pub fn save_password_form_endpoint(password_form: Json, _auth: Authenticated) -> Value { +#[post("/change_password", data = "")] +pub fn save_password_form_endpoint( + password_form: Json, + _auth: Authenticated, +) -> Value { let result = save_password_form(password_form.into_inner()); match result { Ok(_) => { diff --git a/peach-web/src/routes/settings/admin.rs b/peach-web/src/routes/settings/admin.rs index 0171f07..118fa0a 100644 --- a/peach-web/src/routes/settings/admin.rs +++ b/peach-web/src/routes/settings/admin.rs @@ -14,7 +14,44 @@ use peach_lib::config_manager::load_peach_config; use crate::error::PeachWebError; use crate::routes::authentication::Authenticated; -// HELPERS AND ROUTES FOR /settings/configure_admin +// HELPERS AND ROUTES FOR /settings/admin + +#[derive(Debug, Serialize)] +pub struct AdminMenuContext { + pub back: Option, + pub title: Option, + pub flash_name: Option, + pub flash_msg: Option, +} + +impl AdminMenuContext { + pub fn build() -> AdminMenuContext { + AdminMenuContext { + back: None, + title: None, + flash_name: None, + flash_msg: None, + } + } +} + +/// Administrator settings menu. +#[get("/")] +pub fn admin_menu(flash: Option, _auth: Authenticated) -> Template { + let mut context = AdminMenuContext::build(); + // set back icon link to settings route + context.back = Some("/settings".to_string()); + context.title = Some("Administrator Settings".to_string()); + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + Template::render("settings/admin/menu", &context) +} + +// HELPERS AND ROUTES FOR /settings/admin/configure #[derive(Debug, Serialize)] pub struct ConfigureAdminContext { @@ -39,12 +76,12 @@ impl ConfigureAdminContext { } } -/// Administrator settings menu. View and delete currently configured admin. -#[get("/")] +/// View and delete currently configured admin. +#[get("/configure")] pub fn configure_admin(flash: Option, _auth: Authenticated) -> Template { let mut context = ConfigureAdminContext::build(); // set back icon link to settings route - context.back = Some("/settings".to_string()); + context.back = Some("/settings/admin".to_string()); context.title = Some("Configure Admin".to_string()); // check to see if there is a flash message to display if let Some(flash) = flash { @@ -90,7 +127,7 @@ pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError #[get("/add")] pub fn add_admin(flash: Option, _auth: Authenticated) -> Template { let mut context = AddAdminContext::build(); - context.back = Some("/settings/admin".to_string()); + context.back = Some("/settings/admin/configure".to_string()); context.title = Some("Add Admin".to_string()); // check to see if there is a flash message to display if let Some(flash) = flash { diff --git a/peach-web/src/routes/settings/mod.rs b/peach-web/src/routes/settings/mod.rs index 530c15b..1096c8d 100644 --- a/peach-web/src/routes/settings/mod.rs +++ b/peach-web/src/routes/settings/mod.rs @@ -2,3 +2,4 @@ pub mod admin; pub mod dns; pub mod menu; pub mod network; +pub mod scuttlebutt; -- 2.40.1 From 40890b0e586cb2e486b4fb89a3d589c018736627 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 20:56:50 +0200 Subject: [PATCH 13/16] update login template --- peach-web/templates/login.html.tera | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peach-web/templates/login.html.tera b/peach-web/templates/login.html.tera index 072e5fb..504b242 100644 --- a/peach-web/templates/login.html.tera +++ b/peach-web/templates/login.html.tera @@ -17,7 +17,7 @@ {% include "snippets/flash_message" %}
-- 2.40.1 From 2590314b6c6e54ece64a5f9a5b530fdf35d0e5a7 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 20:57:49 +0200 Subject: [PATCH 14/16] update admin settings templates --- .../templates/settings/admin/add_admin.html.tera | 2 +- peach-web/templates/settings/admin/menu.html.tera | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 peach-web/templates/settings/admin/menu.html.tera diff --git a/peach-web/templates/settings/admin/add_admin.html.tera b/peach-web/templates/settings/admin/add_admin.html.tera index 37b186a..5c04908 100644 --- a/peach-web/templates/settings/admin/add_admin.html.tera +++ b/peach-web/templates/settings/admin/add_admin.html.tera @@ -7,7 +7,7 @@ diff --git a/peach-web/templates/settings/admin/menu.html.tera b/peach-web/templates/settings/admin/menu.html.tera new file mode 100644 index 0000000..98716a3 --- /dev/null +++ b/peach-web/templates/settings/admin/menu.html.tera @@ -0,0 +1,13 @@ +{%- extends "nav" -%} +{%- block card %} + + +{%- endblock card -%} -- 2.40.1 From f3b522c65d0bc1614e3a932440bb569367e01a38 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 20:58:13 +0200 Subject: [PATCH 15/16] mount scuttlebutt and admin menu routes --- peach-web/src/main.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 5362010..f950665 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -47,6 +47,7 @@ use crate::routes::settings::admin::*; use crate::routes::settings::dns::*; use crate::routes::settings::menu::*; use crate::routes::settings::network::*; +use crate::routes::settings::scuttlebutt::*; pub type BoxError = Box; @@ -73,6 +74,7 @@ fn init_rocket() -> Rocket { .mount( "/settings/admin", routes![ + admin_menu, configure_admin, add_admin, add_admin_post, @@ -109,7 +111,9 @@ fn init_rocket() -> Rocket { wifi_usage_reset, ], ) - // SCUTTLEBUTT HTML ROUTES + // SCUTTLEBUTT SETTINGS HTML ROUTES + .mount("/settings/scuttlebutt", routes![ssb_settings_menu]) + // SCUTTLEBUTT SOCIAL HTML ROUTES .mount( "/scuttlebutt", routes![ -- 2.40.1 From 308aaa11f5d540a4c11e9651631ff925bcf505c9 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 16 Nov 2021 08:43:04 +0200 Subject: [PATCH 16/16] remove route tables and update readme --- peach-web/README.md | 103 ++++++++++---------------------------------- 1 file changed, 22 insertions(+), 81 deletions(-) diff --git a/peach-web/README.md b/peach-web/README.md index e8956cf..f8185cd 100644 --- a/peach-web/README.md +++ b/peach-web/README.md @@ -12,62 +12,30 @@ The peach-web stack currently consists of [Rocket](https://rocket.rs/) (Rust web _Note: This is a work-in-progress._ -### WEB ROUTES (`src/routes.rs`) +### Setup -| Endpoint | Method | Parameters | Description | -| --- | --- | --- | --- | -| `/` | GET | | Home | -| `/device` | GET | | Device status overview | -| `/device/reboot` | GET | | Reboot device | -| `/device/shutdown` | GET | | Shutdown device | -| `/login` | GET | | Login form | -| `/network` | GET | | Network status overview | -| `/network/ap/activate` | GET | | Activate WiFi access point mode | -| `/network/wifi` | GET | | List of networks | -| `/network/wifi?` | GET | `ssid` | Details of a single network | -| `/network/wifi/activate` | GET | | Activate WiFi client mode | -| `/network/wifi/add` | GET | `ssid` (optional - prepopulation value of SSID in form) | Add a WiFi network | -| `/network/wifi/add` | POST | `ssid` & `pass` | Submit form to add a WiFi network | -| `/network/wifi/connect` | POST | `ssid` | Connect to the given WiFi network | -| `/network/wifi/disconnect` | POST | `ssid` | Disconnect from currently associated WiFi network | -| `/network/wifi/forget` | POST | `ssid` | Submit form to forget a saved WiFi network | -| `/network/wifi/modify?` | GET | `ssid` | Form for updating a WiFi network password | -| `/network/wifi/modify` | POST | `ssid` & `pass` | Submit form to update a WiFi network password | -| `/network/wifi/usage` | GET | | Network data usage values and a form to update alert thresholds | -| `/network/wifi/usage` | POST | `rx_warn`, `rx_cut`, `tx_warn`, `tx_cut`, `rx_warn_flag`, `rx_cut_flag`, `tx_warn_flag`, `tx_cut_flag` | Submit form to update alert thresholds & set flags | -| `/network/wifi/usage/reset` | GET | | Reset the stored network data usage total to zero | -| `/network/dns` | GET | | View current DNS configurations | -| `/network/dns` | POST | | Modify DNS configurations | -| `/shutdown` | GET | | Shutdown menu | +Clone the `peach-workspace` repo: -### JSON API (`src/json_api.rs`) +`git clone https://git.coopcloud.tech/PeachCloud/peach-workspace` -All JSON API calls are prefixed by `/api/v1/`. This has been excluded from the table below to keep the table compact. +Move into the repo and compile: -| Endpoint | Method | Parameters | Description | -| --- | --- | --- | --- | -| `device/reboot` | POST | | Reboot device | -| `device/shutdown` | POST | | Shutdown device | -| `network/activate_ap` | POST | | Activate WiFi access point mode | -| `network/activate_client` | POST | | Activate WiFi client mode | -| `network/ip` | GET | | Returns IP address values for wlan0 & ap0 interfaces | -| `network/rssi` | GET | | Returns RSSI for connected WiFi network | -| `network/ssid` | GET | | Returns SSID for connected WiFi network | -| `network/state` | GET | | Returns state of wlan0 & ap0 interfaces | -| `network/status` | GET | | Returns status object for connected WiFi network | -| `network/wifi` | GET | | Returns scan results for in-range access-points | -| `network/wifi` | POST | `ssid` & `pass` | Submit SSID & password to create new WiFi connection | -| `network/wifi/connect` | POST | `ssid` | Submit SSID to connect to a given WiFi network | -| `network/wifi/disconnect` | POST | `ssid` | Disconnect from the currently associated WiFi network | -| `network/wifi/forget` | POST | `ssid` | Submit SSID to delete credentials for given WiFi network | -| `network/wifi/modify` | POST | `ssid` & `pass` | Submit SSID & password to update the credentials for given WiFi network | -| `/network/wifi/usage` | POST | `rx_warn`, `rx_cut`, `tx_warn`, `tx_cut`, `rx_warn_flag`, `rx_cut_flag`, `tx_warn_flag`, `tx_cut_flag` | Submit form to update alert thresholds & set flags | -| `/network/wifi/usage/reset` | POST | | Reset network data usage total | -| `ping` | GET | | Returns `pong!` if `peach-web` is running | -| `ping/network` | GET | | Returns `pong!` if `peach-network` microservice is running | -| `ping/oled` | GET | | Returns `pong!` if `peach-oled` microservice is running | -| `ping/stats` | GET | | Returns `pong!` if `peach-stats` microservice is running | -| `dns/configure` | POST | | Modify dns configurations | +`cd peach-workspace/peach-web` +`cargo build --release` + +Run the tests: + +`cargo test` + +Move back to the `peach-workspace` directory: + +`cd ..` + +Run the binary: + +`./target/release/peach-web` + +_Note: Networking functionality requires peach-network microservice to be running._ ### Environment @@ -75,13 +43,7 @@ The web application deployment mode is configured with the `ROCKET_ENV` environm `export ROCKET_ENV=stage` -Other deployment modes are `dev` and `prod`. Read the [Rocket Environment Configurations docs](https://rocket.rs/v0.4/guide/configuration/#environment) for further information. - -The WebSocket server port can be configured with `PEACH_WEB_WS` environment variable: - -`export PEACH_WEB_WS=2333` - -When not set, the value defaults to `5115`. +Other deployment modes are `dev` and `prod`. Read the [Rocket Environment Configurations docs](https://rocket.rs/v0.5-rc/guide/configuration/#environment-variables) for further information. Logging is made available with `env_logger`: @@ -89,27 +51,6 @@ Logging is made available with `env_logger`: Other logging levels include `debug`, `warn` and `error`. -### Setup - -Clone this repo: - -`git clone https://github.com/peachcloud/peach-web.git` - -Move into the repo and compile: - -`cd peach-web` -`cargo build --release` - -Run the tests: - -`cargo test` - -Run the binary: - -`./target/release/peach-web` - -_Note: Networking functionality requires peach-network microservice to be running._ - ### Debian Packaging A `systemd` service file and Debian maintainer scripts are included in the `debian` directory, allowing `peach-web` to be easily bundled as a Debian package (`.deb`). The `cargo-deb` [crate](https://crates.io/crates/cargo-deb) can be used to achieve this. @@ -144,7 +85,7 @@ Remove configuration files (not removed with `apt-get remove`): ### Design -`peach-web` is built on the Rocket webserver and Tera templating engine. It presents a web interface for interacting with the device. HTML is rendered server-side. Request handlers call JSON-RPC microservices and serve HTML and assets. A JSON API is exposed for remote calls and dynamic client-side content updates (via vanilla JavaScript following unobstructive design principles). Each Tera template is passed a context object. In the case of Rust, this object is a `struct` and must implement `Serialize`. The fields of the context object are available in the context of the template to be rendered. +`peach-web` is built on the Rocket webserver and Tera templating engine. It presents a web interface for interacting with the device. HTML is rendered server-side. Request handlers call JSON-RPC microservices and serve HTML and assets. A JSON API is exposed for remote calls and dynamic client-side content updates (via plain JavaScript following unobstructive design principles). Each Tera template is passed a context object. In the case of Rust, this object is a `struct` and must implement `Serialize`. The fields of the context object are available in the context of the template to be rendered. ### Licensing -- 2.40.1