James Read 1 mēnesi atpakaļ
vecāks
revīzija
5c3d34228e
100 mainītis faili ar 1241 papildinājumiem un 37 dzēšanām
  1. 11 30
      .github/ISSUE_TEMPLATE/support_request.md
  2. 3 3
      .gitignore
  3. 7 3
      .goreleaser.yml
  4. BIN
      docs/modules/ROOT/images/action-confirmation.png
  5. 2 0
      docs/modules/ROOT/images/action_customization/max-rate/.gitignore
  6. 2 0
      docs/modules/ROOT/images/action_customization/max-rate/Makefile
  7. 14 0
      docs/modules/ROOT/images/action_customization/max-rate/config.yaml
  8. BIN
      docs/modules/ROOT/images/action_customization/max-rate/maxRate.png
  9. 11 0
      docs/modules/ROOT/images/action_customization/max-rate/screenshots.ini
  10. 92 0
      docs/modules/ROOT/images/action_customization/max-rate/setup_max_rate.py
  11. 2 0
      docs/modules/ROOT/images/action_customization/popup-output-only/.gitignore
  12. 2 0
      docs/modules/ROOT/images/action_customization/popup-output-only/Makefile
  13. 16 0
      docs/modules/ROOT/images/action_customization/popup-output-only/config.yaml
  14. BIN
      docs/modules/ROOT/images/action_customization/popup-output-only/popupOutputOnly.png
  15. 11 0
      docs/modules/ROOT/images/action_customization/popup-output-only/screenshots.ini
  16. 47 0
      docs/modules/ROOT/images/action_customization/popup-output-only/setup_popup_output_only.py
  17. 2 0
      docs/modules/ROOT/images/advanced_configuration/diagnostics/.gitignore
  18. 2 0
      docs/modules/ROOT/images/advanced_configuration/diagnostics/Makefile
  19. 11 0
      docs/modules/ROOT/images/advanced_configuration/diagnostics/config.yaml
  20. BIN
      docs/modules/ROOT/images/advanced_configuration/diagnostics/diagnostics.png
  21. 11 0
      docs/modules/ROOT/images/advanced_configuration/diagnostics/screenshots.ini
  22. 54 0
      docs/modules/ROOT/images/advanced_configuration/diagnostics/setup_diagnostics.py
  23. 2 0
      docs/modules/ROOT/images/advanced_configuration/webui/hide-navigation/.gitignore
  24. 2 0
      docs/modules/ROOT/images/advanced_configuration/webui/hide-navigation/Makefile
  25. 48 0
      docs/modules/ROOT/images/advanced_configuration/webui/hide-navigation/config.yaml
  26. BIN
      docs/modules/ROOT/images/advanced_configuration/webui/hide-navigation/hide-navigation.png
  27. 11 0
      docs/modules/ROOT/images/advanced_configuration/webui/hide-navigation/screenshots.ini
  28. 20 0
      docs/modules/ROOT/images/advanced_configuration/webui/hide-navigation/setup_hide_navigation.py
  29. 2 0
      docs/modules/ROOT/images/advanced_configuration/webui/page-title/.gitignore
  30. 2 0
      docs/modules/ROOT/images/advanced_configuration/webui/page-title/Makefile
  31. 19 0
      docs/modules/ROOT/images/advanced_configuration/webui/page-title/config.yaml
  32. BIN
      docs/modules/ROOT/images/advanced_configuration/webui/page-title/page-title.png
  33. 11 0
      docs/modules/ROOT/images/advanced_configuration/webui/page-title/screenshots.ini
  34. 20 0
      docs/modules/ROOT/images/advanced_configuration/webui/page-title/setup_page_title.py
  35. 2 0
      docs/modules/ROOT/images/advanced_configuration/webui/sidebar/.gitignore
  36. 2 0
      docs/modules/ROOT/images/advanced_configuration/webui/sidebar/Makefile
  37. 30 0
      docs/modules/ROOT/images/advanced_configuration/webui/sidebar/config.yaml
  38. 11 0
      docs/modules/ROOT/images/advanced_configuration/webui/sidebar/screenshots.ini
  39. 44 0
      docs/modules/ROOT/images/advanced_configuration/webui/sidebar/setup_sidebar.py
  40. BIN
      docs/modules/ROOT/images/advanced_configuration/webui/sidebar/sidebar.png
  41. 2 0
      docs/modules/ROOT/images/advanced_configuration/webui/topbar/.gitignore
  42. 2 0
      docs/modules/ROOT/images/advanced_configuration/webui/topbar/Makefile
  43. 33 0
      docs/modules/ROOT/images/advanced_configuration/webui/topbar/config.yaml
  44. 11 0
      docs/modules/ROOT/images/advanced_configuration/webui/topbar/screenshots.ini
  45. 58 0
      docs/modules/ROOT/images/advanced_configuration/webui/topbar/setup_topbar.py
  46. BIN
      docs/modules/ROOT/images/advanced_configuration/webui/topbar/topbar.png
  47. 2 0
      docs/modules/ROOT/images/advanced_configuration/webui/with-navigation/.gitignore
  48. 2 0
      docs/modules/ROOT/images/advanced_configuration/webui/with-navigation/Makefile
  49. 47 0
      docs/modules/ROOT/images/advanced_configuration/webui/with-navigation/config.yaml
  50. 11 0
      docs/modules/ROOT/images/advanced_configuration/webui/with-navigation/screenshots.ini
  51. 23 0
      docs/modules/ROOT/images/advanced_configuration/webui/with-navigation/setup_with_navigation.py
  52. BIN
      docs/modules/ROOT/images/advanced_configuration/webui/with-navigation/with-navigation.png
  53. BIN
      docs/modules/ROOT/images/args-choices-entities.png
  54. BIN
      docs/modules/ROOT/images/args-choices-exec.png
  55. BIN
      docs/modules/ROOT/images/args-multiline-text.png
  56. 2 0
      docs/modules/ROOT/images/args/checklist/.gitignore
  57. 2 0
      docs/modules/ROOT/images/args/checklist/Makefile
  58. BIN
      docs/modules/ROOT/images/args/checklist/checklist.png
  59. 22 0
      docs/modules/ROOT/images/args/checklist/config.yaml
  60. 11 0
      docs/modules/ROOT/images/args/checklist/screenshots.ini
  61. 31 0
      docs/modules/ROOT/images/args/checklist/setup_checklist.py
  62. 2 0
      docs/modules/ROOT/images/args/confirmation/.gitignore
  63. 2 0
      docs/modules/ROOT/images/args/confirmation/Makefile
  64. 14 0
      docs/modules/ROOT/images/args/confirmation/config.yaml
  65. BIN
      docs/modules/ROOT/images/args/confirmation/confirmation.png
  66. 11 0
      docs/modules/ROOT/images/args/confirmation/screenshots.ini
  67. 25 0
      docs/modules/ROOT/images/args/confirmation/setup_confirmation.py
  68. 2 0
      docs/modules/ROOT/images/args/dropdown/.gitignore
  69. 2 0
      docs/modules/ROOT/images/args/dropdown/Makefile
  70. 32 0
      docs/modules/ROOT/images/args/dropdown/config.yaml
  71. BIN
      docs/modules/ROOT/images/args/dropdown/dropdown-entities.png
  72. BIN
      docs/modules/ROOT/images/args/dropdown/dropdown-logs-detail.png
  73. BIN
      docs/modules/ROOT/images/args/dropdown/dropdown-logs-list.png
  74. BIN
      docs/modules/ROOT/images/args/dropdown/dropdown.png
  75. 2 0
      docs/modules/ROOT/images/args/dropdown/entities/containers.json
  76. 29 0
      docs/modules/ROOT/images/args/dropdown/screenshots.ini
  77. 37 0
      docs/modules/ROOT/images/args/dropdown/setup_dropdown.py
  78. 30 0
      docs/modules/ROOT/images/args/dropdown/setup_dropdown_entities.py
  79. 73 0
      docs/modules/ROOT/images/args/dropdown/setup_dropdown_logs_detail.py
  80. 45 0
      docs/modules/ROOT/images/args/dropdown/setup_dropdown_logs_list.py
  81. BIN
      docs/modules/ROOT/images/args/input/args1.png
  82. BIN
      docs/modules/ROOT/images/args/input/args2.png
  83. BIN
      docs/modules/ROOT/images/args/input/args3.png
  84. 1 0
      docs/modules/ROOT/images/args/input/screenshots.ini
  85. 11 1
      docs/modules/ROOT/images/args/input/setup_args3.py
  86. 2 0
      docs/modules/ROOT/images/args/textarea/.gitignore
  87. 2 0
      docs/modules/ROOT/images/args/textarea/Makefile
  88. 17 0
      docs/modules/ROOT/images/args/textarea/config.yaml
  89. BIN
      docs/modules/ROOT/images/args/textarea/multiline-text.png
  90. 11 0
      docs/modules/ROOT/images/args/textarea/screenshots.ini
  91. 27 0
      docs/modules/ROOT/images/args/textarea/setup_textarea.py
  92. BIN
      docs/modules/ROOT/images/args4.png
  93. 2 0
      docs/modules/ROOT/images/dashboards/fieldsets/.gitignore
  94. 2 0
      docs/modules/ROOT/images/dashboards/fieldsets/Makefile
  95. 25 0
      docs/modules/ROOT/images/dashboards/fieldsets/config.yaml
  96. BIN
      docs/modules/ROOT/images/dashboards/fieldsets/fieldset.png
  97. 11 0
      docs/modules/ROOT/images/dashboards/fieldsets/screenshots.ini
  98. 35 0
      docs/modules/ROOT/images/dashboards/fieldsets/setup_fieldset.py
  99. 2 0
      docs/modules/ROOT/images/dashboards/folders/.gitignore
  100. 2 0
      docs/modules/ROOT/images/dashboards/folders/Makefile

+ 11 - 30
.github/ISSUE_TEMPLATE/support_request.md

@@ -1,48 +1,29 @@
 ---
 name: Support request
-about: Need some help? Got an error message?
-title: ""
-type: support
-labels:
-  - "waiting-on-developer"
+about: Ask for help with OliveTin
+title: ''
+labels: ''
 assignees: ''
 
 ---
 
-**What seems to be the problem?!**
+**What is the problem you are having?**
 
-If you are getting an error message, then please copy/paste, or better, provide
-a screenshot to show us exactly what is wrong.
 
-**Can you provide a sosreport?**
+**What OliveTin version are you running?**
 
-A sosreport really helps us to help you, by providing critical information about your install. If you can generate a sosreport, please copy and paste the output here.
 
-How to generate a sosreport: https://docs.olivetin.app/sosreport.html
+**Can you provide Server Diagnostics?**
 
-**What package/file/container did you use to install OliveTin?**
+Server Diagnostics really helps us to help you, by providing critical information about your install. If you can generate Server Diagnostics, please copy and paste the output here.
 
-eg: OliveTin-1234-x86_64.rpm
-eg: Container from Dockerhub - please include the version number
+How to generate Server Diagnostics: https://docs.olivetin.app/troubleshooting/server-diagnostics.html
 
-**Your config.yaml**
 
-```
-Please copy-paste your config.yaml here
+**What is your config.yaml?**
 
-You don't need to include anything under "actions:" unless that is where your
-error probably is.
-```
 
-**OliveTin logs**
+**What are the OliveTin service logs showing?**
 
-If possible, please copy and paste your OliveTin logs from when the error happened.
 
-**Screenshot of WebDeveloper console logs**
-
-If you know how, and if you think it's relevant, a screenshot of the
-WebDeveloper console from when you clicked a button is often really helpful.
-
-**Anything else?**
-
-Add any other context about the problem here.
+**Screenshots (if appropriate)**

+ 3 - 3
.gitignore

@@ -18,12 +18,12 @@ integration-tests/flakey-test-runs.jsonl
 service/flakey-test-runs.log
 service/flakey-test-runs.jsonl
 .vscode/
-webui/
+/webui/
 server.log
 OliveTin
 integration-tests/configs/authRequireGuestsToLogin/sessions.yaml
-webui
-webui.dev
+/webui
+/webui.dev
 sessions.yaml
 docs/build/
 build/

+ 7 - 3
.goreleaser.yml

@@ -163,9 +163,13 @@ nfpms:
       - src: var/openrc/OliveTin
         dst: /etc/init.d/OliveTin
 
-      - src: webui/
-        dst: /var/www/olivetin/
-        type: tree
+      # Avoid type: tree for apk — nfpm writes GNU base-256 modes for walked
+      # dirs (ModeDir), which apk-tools rejects (#761 / goreleaser/nfpm#1112).
+      - src: webui/index.html
+        dst: /var/www/olivetin/index.html
+
+      - src: webui/assets/*
+        dst: /var/www/olivetin/assets/
 
       - src: config.yaml
         dst: /etc/OliveTin/config.yaml

BIN
docs/modules/ROOT/images/action-confirmation.png


+ 2 - 0
docs/modules/ROOT/images/action_customization/max-rate/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/action_customization/max-rate/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../screenshots.mk

+ 14 - 0
docs/modules/ROOT/images/action_customization/max-rate/config.yaml

@@ -0,0 +1,14 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:11337
+
+logLevel: "WARN"
+checkForUpdates: false
+showFooter: false
+
+actions:
+  - title: date
+    shell: date
+    icon: clock
+    maxRate:
+      - limit: 3
+        duration: 5m

BIN
docs/modules/ROOT/images/action_customization/max-rate/maxRate.png


+ 11 - 0
docs/modules/ROOT/images/action_customization/max-rate/screenshots.ini

@@ -0,0 +1,11 @@
+[DEFAULT]
+base_url = http://localhost:11337/
+dir = .
+width = 980
+height = 620
+post_script_sleep = 0.5
+
+[max-rate]
+url = .
+name = maxRate
+script = setup_max_rate.py

+ 92 - 0
docs/modules/ROOT/images/action_customization/max-rate/setup_max_rate.py

@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+"""Show blocked rate-limit log entries for the date action."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+_START_ACTION_REPEATEDLY_JS = """
+const done = arguments[arguments.length - 1];
+const title = arguments[0];
+const count = arguments[1];
+
+function bindingIdForTitle(actionTitle) {
+  const button = document.querySelector('[title="' + actionTitle + '"]');
+  if (!button) {
+    throw new Error('Action button not found: ' + actionTitle);
+  }
+  return button.closest('.action-button').id.replace('actionButton-', '');
+}
+
+function uniqueTrackingId() {
+  if (window.isSecureContext && window.crypto?.randomUUID) {
+    return window.crypto.randomUUID();
+  }
+  return 'doc-screenshot-' + Date.now() + '-' + Math.random();
+}
+
+async function startAndWait(actionTitle) {
+  const bindingId = bindingIdForTitle(actionTitle);
+  const trackingId = uniqueTrackingId();
+  const response = await window.client.startAction({
+    bindingId,
+    arguments: [],
+    uniqueTrackingId: trackingId,
+  });
+  const executionTrackingId = response.executionTrackingId || trackingId;
+
+  while (true) {
+    const result = await window.client.executionStatus({
+      executionTrackingId,
+    });
+    if (result.logEntry?.executionFinished) {
+      return result.logEntry;
+    }
+    await new Promise((resolve) => setTimeout(resolve, 100));
+  }
+}
+
+async function startRepeatedly(actionTitle, runs) {
+  for (let i = 0; i < runs; i++) {
+    await startAndWait(actionTitle);
+  }
+}
+
+startRepeatedly(title, count).then(() => done(true)).catch((err) => done(String(err)));
+"""
+
+
+def _wait_for_dashboard(driver, timeout=15):
+    WebDriverWait(driver, timeout).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute("loaded-dashboard"))
+    )
+    WebDriverWait(driver, timeout).until(
+        lambda d: d.execute_script("return !!window.client")
+    )
+
+
+def _wait_for_rate_limited_logs(driver, timeout=20):
+    def ready(d):
+        try:
+            rows = d.find_elements(By.CSS_SELECTOR, ".logs-table tbody tr")
+            blocked = d.find_elements(By.CSS_SELECTOR, ".logs-table .status-blocked")
+            completed = d.find_elements(By.CSS_SELECTOR, ".logs-table .status-success")
+        except Exception:
+            return False
+        return len(rows) >= 5 and len(blocked) >= 2 and len(completed) >= 3
+
+    WebDriverWait(driver, timeout).until(ready)
+
+
+def run(driver):
+    _wait_for_dashboard(driver)
+
+    driver.execute_async_script(_START_ACTION_REPEATEDLY_JS, "date", 5)
+
+    time.sleep(1)
+
+    driver.execute_script("window.location.href = '/logs'")
+    _wait_for_rate_limited_logs(driver)
+
+    time.sleep(0.2)

+ 2 - 0
docs/modules/ROOT/images/action_customization/popup-output-only/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/action_customization/popup-output-only/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../screenshots.mk

+ 16 - 0
docs/modules/ROOT/images/action_customization/popup-output-only/config.yaml

@@ -0,0 +1,16 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:11337
+
+logLevel: "WARN"
+checkForUpdates: false
+showFooter: false
+
+actions:
+  - title: Check disk space
+    icon: disk
+    onclick: execution-dialog
+    shell: |
+      echo "Filesystem      Size  Used Avail Use% Mounted on"
+      echo "/dev/mapper/fedora_mindstorm-root   99G   82G   12G  88% /"
+      echo "Filesystem      Size  Used Avail Use% Mounted on"
+      echo "/dev/mapper/fedora_mindstorm-root   99G   82G   12G  88% /"

BIN
docs/modules/ROOT/images/action_customization/popup-output-only/popupOutputOnly.png


+ 11 - 0
docs/modules/ROOT/images/action_customization/popup-output-only/screenshots.ini

@@ -0,0 +1,11 @@
+[DEFAULT]
+base_url = http://localhost:11337/
+dir = .
+width = 900
+height = 620
+post_script_sleep = 0.5
+
+[popup-output-only]
+url = .
+name = popupOutputOnly
+script = setup_popup_output_only.py

+ 47 - 0
docs/modules/ROOT/images/action_customization/popup-output-only/setup_popup_output_only.py

@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+"""Open the execution dialog for Check disk space."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def _wait_for_body_attr(driver, attr, timeout=15):
+    WebDriverWait(driver, timeout).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute(attr))
+    )
+
+
+def _wait_for_logs_page(driver, timeout=15):
+    WebDriverWait(driver, timeout).until(
+        lambda d: "/logs/" in d.current_url and not d.current_url.rstrip("/").endswith("/logs")
+    )
+
+
+def _wait_for_execution_complete(driver, timeout=15):
+    def finished(d):
+        try:
+            status = d.find_element(By.CSS_SELECTOR, ".execution-dialog-status").text
+        except Exception:
+            return False
+        return "Still running" not in status and "Queued" not in status
+
+    WebDriverWait(driver, timeout).until(finished)
+
+
+def run(driver):
+    _wait_for_body_attr(driver, "loaded-dashboard")
+
+    driver.find_element(By.CSS_SELECTOR, '[title="Check disk space"]').click()
+
+    _wait_for_logs_page(driver)
+    _wait_for_execution_complete(driver)
+
+    WebDriverWait(driver, 15).until(
+        lambda d: "fedora_mindstorm-root" in d.find_element(
+            By.CSS_SELECTOR, "#execution-results-popup .xterm-rows"
+        ).text
+    )
+
+    time.sleep(0.2)

+ 2 - 0
docs/modules/ROOT/images/advanced_configuration/diagnostics/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/advanced_configuration/diagnostics/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../screenshots.mk

+ 11 - 0
docs/modules/ROOT/images/advanced_configuration/diagnostics/config.yaml

@@ -0,0 +1,11 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:11337
+
+logLevel: "WARN"
+checkForUpdates: false
+showFooter: false
+
+actions:
+  - title: date
+    icon: clock
+    shell: date

BIN
docs/modules/ROOT/images/advanced_configuration/diagnostics/diagnostics.png


+ 11 - 0
docs/modules/ROOT/images/advanced_configuration/diagnostics/screenshots.ini

@@ -0,0 +1,11 @@
+[DEFAULT]
+base_url = http://localhost:11337/
+dir = .
+width = 980
+height = 640
+post_script_sleep = 0.5
+
+[diagnostics]
+url = /diagnostics
+name = diagnostics
+script = setup_diagnostics.py

+ 54 - 0
docs/modules/ROOT/images/advanced_configuration/diagnostics/setup_diagnostics.py

@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+"""Open the diagnostics page from advanced_configuration/diagnostics.adoc."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def _wait_for_diagnostics_page(driver, timeout=30):
+    WebDriverWait(driver, timeout).until(
+        lambda d: d.execute_script("return !!window.client")
+    )
+
+    def ready(d):
+        try:
+            ssh_heading = d.find_element(
+                By.XPATH,
+                '//*[self::h2 or self::h3][contains(normalize-space(), "SSH")]',
+            )
+            server_diagnostics_heading = d.find_element(
+                By.XPATH,
+                '//*[self::h2 or self::h3][contains(normalize-space(), "Server Diagnostics")]',
+            )
+            key_value = d.find_element(
+                By.XPATH,
+                '//dt[contains(normalize-space(), "Found Key")]/following-sibling::dd[1]',
+            )
+            config_value = d.find_element(
+                By.XPATH,
+                '//dt[contains(normalize-space(), "Found Config")]/following-sibling::dd[1]',
+            )
+        except Exception:
+            return False
+
+        return all(
+            element.is_displayed()
+            for element in (
+                ssh_heading,
+                server_diagnostics_heading,
+                key_value,
+                config_value,
+            )
+        ) and all(
+            element.text.strip() not in ("", "?")
+            for element in (key_value, config_value)
+        )
+
+    WebDriverWait(driver, timeout).until(ready)
+
+
+def run(driver):
+    _wait_for_diagnostics_page(driver)
+    time.sleep(0.2)

+ 2 - 0
docs/modules/ROOT/images/advanced_configuration/webui/hide-navigation/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/advanced_configuration/webui/hide-navigation/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../../screenshots.mk

+ 48 - 0
docs/modules/ROOT/images/advanced_configuration/webui/hide-navigation/config.yaml

@@ -0,0 +1,48 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:11337
+
+logLevel: "WARN"
+checkForUpdates: false
+showFooter: false
+showNavigation: false
+
+actions:
+  - title: Ping the Internet
+    icon: ping
+    shell: ping -c 1 127.0.0.1
+  - title: Check disk space
+    icon: disk
+    shell: df -h
+  - title: check dmesg logs
+    icon: logs
+    shell: dmesg | tail -n 1
+  - title: date
+    icon: clock
+    shell: date
+  - title: Run backup script
+    icon: backup
+    shell: echo backup
+  - title: Ping host
+    icon: ping
+    shell: ping -c 1 127.0.0.1
+  - title: Restart Docker Container
+    icon: restart
+    shell: echo restart
+  - title: Delete old backups
+    icon: ashtonished
+    shell: echo delete
+  - title: Get OliveTin Theme
+    icon: theme
+    shell: echo theme
+  - title: Setup easy SSH
+    icon: ssh
+    shell: echo ssh
+  - title: Restart httpd on server1
+    icon: restart
+    shell: echo restart httpd
+  - title: Toggle GPIO light
+    icon: light
+    shell: echo toggle
+  - title: Run Automation Playbook
+    icon: robot
+    shell: echo ansible

BIN
docs/modules/ROOT/images/advanced_configuration/webui/hide-navigation/hide-navigation.png


+ 11 - 0
docs/modules/ROOT/images/advanced_configuration/webui/hide-navigation/screenshots.ini

@@ -0,0 +1,11 @@
+[DEFAULT]
+base_url = http://localhost:11337/
+dir = .
+width = 1180
+height = 760
+post_script_sleep = 0.5
+
+[hide-navigation]
+url = .
+name = hide-navigation
+script = setup_hide_navigation.py

+ 20 - 0
docs/modules/ROOT/images/advanced_configuration/webui/hide-navigation/setup_hide_navigation.py

@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+"""Capture the Actions view with navigation hidden."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def run(driver):
+    WebDriverWait(driver, 15).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute("loaded-dashboard"))
+    )
+    WebDriverWait(driver, 15).until(
+        lambda d: len(d.find_elements(By.ID, "mainnav")) == 0
+    )
+    WebDriverWait(driver, 15).until(
+        lambda d: len(d.find_elements(By.CSS_SELECTOR, ".action-button button")) >= 8
+    )
+    time.sleep(0.2)

+ 2 - 0
docs/modules/ROOT/images/advanced_configuration/webui/page-title/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/advanced_configuration/webui/page-title/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../../screenshots.mk

+ 19 - 0
docs/modules/ROOT/images/advanced_configuration/webui/page-title/config.yaml

@@ -0,0 +1,19 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:11337
+
+logLevel: "WARN"
+checkForUpdates: false
+showFooter: false
+
+pageTitle: My OliveTin Instance
+
+actions:
+  - title: Ping the Internet
+    icon: ping
+    shell: ping -c 1 127.0.0.1
+  - title: Check disk space
+    icon: disk
+    shell: df -h /
+  - title: check dmesg logs
+    icon: logs
+    shell: dmesg | tail -n 1

BIN
docs/modules/ROOT/images/advanced_configuration/webui/page-title/page-title.png


+ 11 - 0
docs/modules/ROOT/images/advanced_configuration/webui/page-title/screenshots.ini

@@ -0,0 +1,11 @@
+[DEFAULT]
+base_url = http://localhost:11337/
+dir = .
+width = 980
+height = 220
+post_script_sleep = 0.5
+
+[page-title]
+url = .
+name = page-title
+script = setup_page_title.py

+ 20 - 0
docs/modules/ROOT/images/advanced_configuration/webui/page-title/setup_page_title.py

@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+"""Capture the custom page title from advanced_configuration/webui.adoc."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def run(driver):
+    WebDriverWait(driver, 15).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute("loaded-dashboard"))
+    )
+    WebDriverWait(driver, 15).until(
+        lambda d: "My OliveTin Instance" in d.find_element(By.CSS_SELECTOR, "header h1").text
+    )
+    WebDriverWait(driver, 15).until(
+        lambda d: d.find_element(By.CSS_SELECTOR, '[title="Ping the Internet"]').is_displayed()
+    )
+    time.sleep(0.2)

+ 2 - 0
docs/modules/ROOT/images/advanced_configuration/webui/sidebar/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/advanced_configuration/webui/sidebar/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../../screenshots.mk

+ 30 - 0
docs/modules/ROOT/images/advanced_configuration/webui/sidebar/config.yaml

@@ -0,0 +1,30 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:11337
+
+logLevel: "WARN"
+checkForUpdates: false
+showFooter: false
+
+sectionNavigationStyle: sidebar
+
+actions:
+  - title: Check disk space
+    icon: disk
+    shell: df -h /
+  - title: Setup easy SSH
+    icon: ssh
+    shell: echo ssh
+  - title: My Servers ping
+    icon: ping
+    shell: echo ping
+  - title: My Containers list
+    icon: disk
+    shell: echo list
+
+dashboards:
+  - title: My Servers
+    contents:
+      - title: My Servers ping
+  - title: My Containers
+    contents:
+      - title: My Containers list

+ 11 - 0
docs/modules/ROOT/images/advanced_configuration/webui/sidebar/screenshots.ini

@@ -0,0 +1,11 @@
+[DEFAULT]
+base_url = http://localhost:11337/
+dir = .
+width = 980
+height = 520
+post_script_sleep = 0.5
+
+[sidebar]
+url = .
+name = sidebar
+script = setup_sidebar.py

+ 44 - 0
docs/modules/ROOT/images/advanced_configuration/webui/sidebar/setup_sidebar.py

@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+"""Capture the default sidebar navigation style."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def _wait_for_sidebar_navigation(driver, timeout=30):
+    WebDriverWait(driver, timeout).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute("loaded-dashboard"))
+    )
+
+    driver.find_element(By.ID, "sidebar-toggler-button").click()
+
+    WebDriverWait(driver, timeout).until(
+        lambda d: d.find_element(By.ID, "mainnav").is_displayed()
+    )
+
+    driver.find_element(By.CSS_SELECTOR, "#mainnav .stick-toggle").click()
+
+    def ready(d):
+        try:
+            my_servers = d.find_element(
+                By.XPATH,
+                '//*[@id="mainnav"]//a[contains(normalize-space(), "My Servers")]',
+            )
+            my_containers = d.find_element(
+                By.XPATH,
+                '//*[@id="mainnav"]//a[contains(normalize-space(), "My Containers")]',
+            )
+            disk_space = d.find_element(By.CSS_SELECTOR, '[title="Check disk space"]')
+            ssh = d.find_element(By.CSS_SELECTOR, '[title="Setup easy SSH"]')
+        except Exception:
+            return False
+        return all(element.is_displayed() for element in (my_servers, my_containers, disk_space, ssh))
+
+    WebDriverWait(driver, timeout).until(ready)
+
+
+def run(driver):
+    _wait_for_sidebar_navigation(driver)
+    time.sleep(0.2)

BIN
docs/modules/ROOT/images/advanced_configuration/webui/sidebar/sidebar.png


+ 2 - 0
docs/modules/ROOT/images/advanced_configuration/webui/topbar/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/advanced_configuration/webui/topbar/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../../screenshots.mk

+ 33 - 0
docs/modules/ROOT/images/advanced_configuration/webui/topbar/config.yaml

@@ -0,0 +1,33 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:11337
+
+logLevel: "WARN"
+checkForUpdates: false
+showFooter: false
+
+sectionNavigationStyle: topbar
+
+actions:
+  - title: Ping host
+    icon: ping
+    shell: ping -c 1 127.0.0.1
+  - title: Restart Docker Container
+    icon: restart
+    shell: echo restart
+  - title: Delete old backups
+    icon: ashtonished
+    shell: echo delete
+  - title: Server ping
+    icon: ping
+    shell: echo ping
+  - title: Container list
+    icon: disk
+    shell: echo list
+
+dashboards:
+  - title: My Servers
+    contents:
+      - title: Server ping
+  - title: My Containers
+    contents:
+      - title: Container list

+ 11 - 0
docs/modules/ROOT/images/advanced_configuration/webui/topbar/screenshots.ini

@@ -0,0 +1,11 @@
+[DEFAULT]
+base_url = http://localhost:11337/
+dir = .
+width = 980
+height = 420
+post_script_sleep = 0.5
+
+[topbar]
+url = .
+name = topbar
+script = setup_topbar.py

+ 58 - 0
docs/modules/ROOT/images/advanced_configuration/webui/topbar/setup_topbar.py

@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+"""Capture the topbar section navigation style."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def _wait_for_topbar_navigation(driver, timeout=30):
+    WebDriverWait(driver, timeout).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute("loaded-dashboard"))
+    )
+
+    def ready(d):
+        try:
+            topbar = d.find_element(By.CSS_SELECTOR, "nav.topbar")
+            my_servers = d.find_element(
+                By.XPATH,
+                '//nav[contains(@class, "topbar")]//a[contains(normalize-space(), "My Servers")]',
+            )
+            my_containers = d.find_element(
+                By.XPATH,
+                '//nav[contains(@class, "topbar")]//a[contains(normalize-space(), "My Containers")]',
+            )
+            diagnostics = d.find_element(
+                By.XPATH,
+                '//nav[contains(@class, "topbar")]//a[contains(normalize-space(), "Diagnostics")]',
+            )
+            logs = d.find_element(
+                By.XPATH,
+                '//nav[contains(@class, "topbar")]//a[contains(normalize-space(), "Logs")]',
+            )
+            ping_host = d.find_element(By.CSS_SELECTOR, '[title="Ping host"]')
+            restart = d.find_element(By.CSS_SELECTOR, '[title="Restart Docker Container"]')
+            delete_backups = d.find_element(By.CSS_SELECTOR, '[title="Delete old backups"]')
+        except Exception:
+            return False
+
+        return topbar.is_displayed() and all(
+            element.is_displayed()
+            for element in (
+                my_servers,
+                my_containers,
+                diagnostics,
+                logs,
+                ping_host,
+                restart,
+                delete_backups,
+            )
+        )
+
+    WebDriverWait(driver, timeout).until(ready)
+
+
+def run(driver):
+    _wait_for_topbar_navigation(driver)
+    time.sleep(0.2)

BIN
docs/modules/ROOT/images/advanced_configuration/webui/topbar/topbar.png


+ 2 - 0
docs/modules/ROOT/images/advanced_configuration/webui/with-navigation/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/advanced_configuration/webui/with-navigation/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../../screenshots.mk

+ 47 - 0
docs/modules/ROOT/images/advanced_configuration/webui/with-navigation/config.yaml

@@ -0,0 +1,47 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:11337
+
+logLevel: "WARN"
+checkForUpdates: false
+showFooter: false
+
+actions:
+  - title: Ping the Internet
+    icon: ping
+    shell: ping -c 1 127.0.0.1
+  - title: Check disk space
+    icon: disk
+    shell: df -h
+  - title: check dmesg logs
+    icon: logs
+    shell: dmesg | tail -n 1
+  - title: date
+    icon: clock
+    shell: date
+  - title: Run backup script
+    icon: backup
+    shell: echo backup
+  - title: Ping host
+    icon: ping
+    shell: ping -c 1 127.0.0.1
+  - title: Restart Docker Container
+    icon: restart
+    shell: echo restart
+  - title: Delete old backups
+    icon: ashtonished
+    shell: echo delete
+  - title: Get OliveTin Theme
+    icon: theme
+    shell: echo theme
+  - title: Setup easy SSH
+    icon: ssh
+    shell: echo ssh
+  - title: Restart httpd on server1
+    icon: restart
+    shell: echo restart httpd
+  - title: Toggle GPIO light
+    icon: light
+    shell: echo toggle
+  - title: Run Automation Playbook
+    icon: robot
+    shell: echo ansible

+ 11 - 0
docs/modules/ROOT/images/advanced_configuration/webui/with-navigation/screenshots.ini

@@ -0,0 +1,11 @@
+[DEFAULT]
+base_url = http://localhost:11337/
+dir = .
+width = 1180
+height = 760
+post_script_sleep = 0.5
+
+[with-navigation]
+url = .
+name = with-navigation
+script = setup_with_navigation.py

+ 23 - 0
docs/modules/ROOT/images/advanced_configuration/webui/with-navigation/setup_with_navigation.py

@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+"""Capture the default Actions view with sidebar navigation visible."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def run(driver):
+    WebDriverWait(driver, 15).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute("loaded-dashboard"))
+    )
+    WebDriverWait(driver, 15).until(
+        lambda d: len(d.find_elements(By.CSS_SELECTOR, ".action-button button")) >= 8
+    )
+
+    driver.find_element(By.ID, "sidebar-toggler-button").click()
+
+    WebDriverWait(driver, 15).until(
+        lambda d: d.find_element(By.ID, "mainnav").is_displayed()
+    )
+    time.sleep(0.2)

BIN
docs/modules/ROOT/images/advanced_configuration/webui/with-navigation/with-navigation.png


BIN
docs/modules/ROOT/images/args-choices-entities.png


BIN
docs/modules/ROOT/images/args-choices-exec.png


BIN
docs/modules/ROOT/images/args-multiline-text.png


+ 2 - 0
docs/modules/ROOT/images/args/checklist/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/args/checklist/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../screenshots.mk

BIN
docs/modules/ROOT/images/args/checklist/checklist.png


+ 22 - 0
docs/modules/ROOT/images/args/checklist/config.yaml

@@ -0,0 +1,22 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:11337
+
+logLevel: "WARN"
+checkForUpdates: false
+showFooter: false
+
+actions:
+  - title: Backup selected directories
+    shell: echo "Backing up {{ directories }}"
+    arguments:
+      - name: directories
+        title: Directories to back up
+        type: checklist
+        choices:
+          - title: Documents
+            value: documents
+          - title: Photos
+            value: photos
+          - title: Music
+            value: music
+        default: '["documents","photos"]'

+ 11 - 0
docs/modules/ROOT/images/args/checklist/screenshots.ini

@@ -0,0 +1,11 @@
+[DEFAULT]
+base_url = http://localhost:11337/
+dir = .
+width = 800
+height = 520
+post_script_sleep = 0.5
+
+[checklist]
+url = .
+name = checklist
+script = setup_checklist.py

+ 31 - 0
docs/modules/ROOT/images/args/checklist/setup_checklist.py

@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+"""Prepare the checklist argument form screenshot."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def _wait_for_body_attr(driver, attr, timeout=15):
+    WebDriverWait(driver, timeout).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute(attr))
+    )
+
+
+def run(driver):
+    _wait_for_body_attr(driver, "loaded-dashboard")
+
+    driver.find_element(
+        By.CSS_SELECTOR, '[title="Backup selected directories"]'
+    ).click()
+
+    _wait_for_body_attr(driver, "loaded-argument-form")
+
+    WebDriverWait(driver, 15).until(
+        lambda d: len(
+            d.find_elements(By.CSS_SELECTOR, ".choice-checklist-item input[type='checkbox']")
+        )
+        >= 3
+    )
+    time.sleep(0.2)

+ 2 - 0
docs/modules/ROOT/images/args/confirmation/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/args/confirmation/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../screenshots.mk

+ 14 - 0
docs/modules/ROOT/images/args/confirmation/config.yaml

@@ -0,0 +1,14 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:11337
+
+logLevel: "WARN"
+checkForUpdates: false
+showFooter: false
+
+actions:
+  - title: Delete old backups
+    icon: ashtonished
+    shell: echo "Deleted old backups"
+    arguments:
+      - type: confirmation
+        title: Are you sure?!

BIN
docs/modules/ROOT/images/args/confirmation/confirmation.png


+ 11 - 0
docs/modules/ROOT/images/args/confirmation/screenshots.ini

@@ -0,0 +1,11 @@
+[DEFAULT]
+base_url = http://localhost:11337/
+dir = .
+width = 800
+height = 420
+post_script_sleep = 0.5
+
+[confirmation]
+url = .
+name = confirmation
+script = setup_confirmation.py

+ 25 - 0
docs/modules/ROOT/images/args/confirmation/setup_confirmation.py

@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+"""Prepare the confirmation argument screenshot."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def _wait_for_body_attr(driver, attr, timeout=15):
+    WebDriverWait(driver, timeout).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute(attr))
+    )
+
+
+def run(driver):
+    _wait_for_body_attr(driver, "loaded-dashboard")
+
+    driver.find_element(By.CSS_SELECTOR, '[title="Delete old backups"]').click()
+    _wait_for_body_attr(driver, "loaded-argument-form")
+
+    WebDriverWait(driver, 15).until(
+        lambda d: not d.find_element(By.CSS_SELECTOR, 'button[name="start"]').is_enabled()
+    )
+    time.sleep(0.2)

+ 2 - 0
docs/modules/ROOT/images/args/dropdown/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/args/dropdown/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../screenshots.mk

+ 32 - 0
docs/modules/ROOT/images/args/dropdown/config.yaml

@@ -0,0 +1,32 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:11337
+
+logLevel: "WARN"
+checkForUpdates: false
+showFooter: false
+
+entities:
+  - file: entities/containers.json
+    name: container
+
+actions:
+  - title: Print a message
+    icon: smile
+    shell: echo "{{ message }}"
+    arguments:
+      - name: message
+        description: The message you want to print out.
+        choices:
+          - title: Hello
+            value: Hello there!
+          - title: Goodbye
+            value: Aww, goodbye. :-(
+  - title: restart container
+    shell: 'docker restart {{ containerToRestart }}'
+    arguments:
+      - name: containerToRestart
+        entity: container
+        title: Select Container
+        choices:
+          - value: '{{ container.Names }}'
+            title: '{{ container.Names }}'

BIN
docs/modules/ROOT/images/args/dropdown/dropdown-entities.png


BIN
docs/modules/ROOT/images/args/dropdown/dropdown-logs-detail.png


BIN
docs/modules/ROOT/images/args/dropdown/dropdown-logs-list.png


BIN
docs/modules/ROOT/images/args/dropdown/dropdown.png


+ 2 - 0
docs/modules/ROOT/images/args/dropdown/entities/containers.json

@@ -0,0 +1,2 @@
+{"Names":"media-indexer"}
+{"Names":"game-server"}

+ 29 - 0
docs/modules/ROOT/images/args/dropdown/screenshots.ini

@@ -0,0 +1,29 @@
+[DEFAULT]
+base_url = http://localhost:11337/
+dir = .
+width = 800
+height = 480
+post_script_sleep = 0.5
+
+[dropdown]
+url = .
+name = dropdown
+script = setup_dropdown.py
+
+[dropdown-logs-list]
+url = .
+name = dropdown-logs-list
+script = setup_dropdown_logs_list.py
+width = 960
+height = 640
+
+[dropdown-logs-detail]
+url = .
+name = dropdown-logs-detail
+script = setup_dropdown_logs_detail.py
+height = 560
+
+[dropdown-entities]
+url = .
+name = dropdown-entities
+script = setup_dropdown_entities.py

+ 37 - 0
docs/modules/ROOT/images/args/dropdown/setup_dropdown.py

@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+"""Prepare the dropdown argument form screenshot."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def _wait_for_body_attr(driver, attr, timeout=15):
+    WebDriverWait(driver, timeout).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute(attr))
+    )
+
+
+def _open_choice_list(driver, input_id):
+    combobox_input = driver.find_element(By.ID, input_id)
+    combobox_input.click()
+    WebDriverWait(driver, 15).until(
+        lambda d: len(
+            d.find_elements(
+                By.CSS_SELECTOR,
+                f"#{input_id}-listbox li, #{input_id} + input + ul li",
+            )
+        )
+        >= 2
+        or len(d.find_elements(By.CSS_SELECTOR, ".choice-combobox-list li")) >= 2
+    )
+
+
+def run(driver):
+    _wait_for_body_attr(driver, "loaded-dashboard")
+
+    driver.find_element(By.CSS_SELECTOR, '[title="Print a message"]').click()
+    _wait_for_body_attr(driver, "loaded-argument-form")
+    _open_choice_list(driver, "message")
+    time.sleep(0.2)

+ 30 - 0
docs/modules/ROOT/images/args/dropdown/setup_dropdown_entities.py

@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+"""Prepare the entity-backed dropdown screenshot."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def _wait_for_body_attr(driver, attr, timeout=15):
+    WebDriverWait(driver, timeout).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute(attr))
+    )
+
+
+def _open_choice_list(driver, input_id):
+    combobox_input = driver.find_element(By.ID, input_id)
+    combobox_input.click()
+    WebDriverWait(driver, 15).until(
+        lambda d: len(d.find_elements(By.CSS_SELECTOR, ".choice-combobox-list li")) >= 2
+    )
+
+
+def run(driver):
+    _wait_for_body_attr(driver, "loaded-dashboard")
+
+    driver.find_element(By.CSS_SELECTOR, '[title="restart container"]').click()
+    _wait_for_body_attr(driver, "loaded-argument-form")
+    _open_choice_list(driver, "containerToRestart")
+    time.sleep(0.2)

+ 73 - 0
docs/modules/ROOT/images/args/dropdown/setup_dropdown_logs_detail.py

@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+"""Prepare the execution results screenshot for a dropdown action."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def _wait_for_dashboard(driver, timeout=15):
+    WebDriverWait(driver, timeout).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute("loaded-dashboard"))
+    )
+    WebDriverWait(driver, timeout).until(
+        lambda d: d.execute_script("return !!window.client")
+    )
+
+
+def _wait_for_logs_page(driver, timeout=15):
+    WebDriverWait(driver, timeout).until(
+        lambda d: "/logs/" in d.current_url and not d.current_url.rstrip("/").endswith("/logs")
+    )
+
+
+def _wait_for_execution_complete(driver, timeout=15):
+    def finished(d):
+        try:
+            status = d.find_element(By.CSS_SELECTOR, ".execution-dialog-status").text
+        except Exception:
+            return False
+        return "Still running" not in status and "Queued" not in status
+
+    WebDriverWait(driver, timeout).until(finished)
+
+
+def _wait_for_terminal_output(driver, expected, timeout=15):
+    WebDriverWait(driver, timeout).until(
+        lambda d: expected
+        in d.execute_script(
+            """
+            if (!window.terminal || !window.terminal.getBufferAsString) {
+              return '';
+            }
+            return window.terminal.getBufferAsString();
+            """
+        )
+    )
+
+
+def run(driver):
+    _wait_for_dashboard(driver)
+
+    driver.execute_async_script(
+        """
+        const done = arguments[arguments.length - 1];
+        const button = document.querySelector('[title="Print a message"]');
+        const bindingId = button.closest('.action-button').id.replace('actionButton-', '');
+
+        window.client.startAction({
+          bindingId: bindingId,
+          arguments: [{ name: 'message', value: 'Hello there!' }],
+          uniqueTrackingId: 'doc-screenshot-' + Date.now(),
+        }).then((response) => {
+          window.location.href = '/logs/' + response.executionTrackingId;
+          done(true);
+        }).catch((err) => done(String(err)));
+        """
+    )
+
+    _wait_for_logs_page(driver)
+    _wait_for_execution_complete(driver)
+    _wait_for_terminal_output(driver, "Hello there!")
+    time.sleep(0.2)

+ 45 - 0
docs/modules/ROOT/images/args/dropdown/setup_dropdown_logs_list.py

@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+"""Prepare the logs list screenshot after running a dropdown action."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def _wait_for_dashboard(driver, timeout=15):
+    WebDriverWait(driver, timeout).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute("loaded-dashboard"))
+    )
+    WebDriverWait(driver, timeout).until(
+        lambda d: d.execute_script("return !!window.client")
+    )
+
+
+def _wait_for_logs_table(driver, timeout=15):
+    WebDriverWait(driver, timeout).until(
+        lambda d: len(d.find_elements(By.CSS_SELECTOR, ".logs-table tbody tr")) >= 1
+    )
+
+
+def run(driver):
+    _wait_for_dashboard(driver)
+
+    driver.execute_async_script(
+        """
+        const done = arguments[arguments.length - 1];
+        const button = document.querySelector('[title="Print a message"]');
+        const bindingId = button.closest('.action-button').id.replace('actionButton-', '');
+
+        window.client.startAction({
+          bindingId: bindingId,
+          arguments: [{ name: 'message', value: 'Hello there!' }],
+          uniqueTrackingId: 'doc-screenshot-' + Date.now(),
+        }).then(() => done(true)).catch((err) => done(String(err)));
+        """
+    )
+
+    time.sleep(0.5)
+    driver.execute_script("window.location.href = '/logs'")
+    _wait_for_logs_table(driver)
+    time.sleep(0.2)

BIN
docs/modules/ROOT/images/args/input/args1.png


BIN
docs/modules/ROOT/images/args/input/args2.png


BIN
docs/modules/ROOT/images/args/input/args3.png


+ 1 - 0
docs/modules/ROOT/images/args/input/screenshots.ini

@@ -19,3 +19,4 @@ script = setup_args2.py
 url = .
 name = args3
 script = setup_args3.py
+height = 560

+ 11 - 1
docs/modules/ROOT/images/args/input/setup_args3.py

@@ -65,5 +65,15 @@ def run(driver):
 
     _wait_for_logs_page(driver)
     _wait_for_execution_complete(driver)
-
+    WebDriverWait(driver, 15).until(
+        lambda d: "Hello World"
+        in d.execute_script(
+            """
+            if (!window.terminal || !window.terminal.getBufferAsString) {
+              return '';
+            }
+            return window.terminal.getBufferAsString();
+            """
+        )
+    )
     time.sleep(0.2)

+ 2 - 0
docs/modules/ROOT/images/args/textarea/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/args/textarea/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../screenshots.mk

+ 17 - 0
docs/modules/ROOT/images/args/textarea/config.yaml

@@ -0,0 +1,17 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:11337
+
+logLevel: "WARN"
+checkForUpdates: false
+showFooter: false
+
+actions:
+  - title: Save text to file
+    shell: 'echo "{{ content }}" > file'
+    arguments:
+      - type: raw_string_multiline
+        name: content
+        title: Content
+        default: |
+          Line one of the message
+          Line two of the message

BIN
docs/modules/ROOT/images/args/textarea/multiline-text.png


+ 11 - 0
docs/modules/ROOT/images/args/textarea/screenshots.ini

@@ -0,0 +1,11 @@
+[DEFAULT]
+base_url = http://localhost:11337/
+dir = .
+width = 800
+height = 520
+post_script_sleep = 0.5
+
+[multiline-text]
+url = .
+name = multiline-text
+script = setup_textarea.py

+ 27 - 0
docs/modules/ROOT/images/args/textarea/setup_textarea.py

@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+"""Prepare the multiline textarea argument screenshot."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def _wait_for_body_attr(driver, attr, timeout=15):
+    WebDriverWait(driver, timeout).until(
+        lambda d: bool(d.find_element(By.TAG_NAME, "body").get_attribute(attr))
+    )
+
+
+def run(driver):
+    _wait_for_body_attr(driver, "loaded-dashboard")
+
+    driver.find_element(By.CSS_SELECTOR, '[title="Save text to file"]').click()
+
+    WebDriverWait(driver, 15).until(
+        lambda d: len(
+            d.find_elements(By.CSS_SELECTOR, "#argument-popup textarea#content")
+        )
+        >= 1
+    )
+    time.sleep(0.2)

BIN
docs/modules/ROOT/images/args4.png


+ 2 - 0
docs/modules/ROOT/images/dashboards/fieldsets/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/dashboards/fieldsets/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../screenshots.mk

+ 25 - 0
docs/modules/ROOT/images/dashboards/fieldsets/config.yaml

@@ -0,0 +1,25 @@
+---
+listenAddressSingleHTTPFrontend: 0.0.0.0:11337
+
+logLevel: "WARN"
+checkForUpdates: false
+showFooter: false
+
+actions:
+  - title: Placeholder 1
+    shell: echo "placeholder 1"
+  - title: Placeholder 2
+    shell: echo "placeholder 2"
+
+dashboards:
+  - title: My First Dashboard
+    contents:
+      - title: Fieldset 1
+        type: fieldset
+        contents:
+          - title: Folder 1
+            contents:
+              - title: Placeholder 1
+          - title: Folder 2
+            contents:
+              - title: Placeholder 2

BIN
docs/modules/ROOT/images/dashboards/fieldsets/fieldset.png


+ 11 - 0
docs/modules/ROOT/images/dashboards/fieldsets/screenshots.ini

@@ -0,0 +1,11 @@
+[DEFAULT]
+base_url = http://localhost:11337/
+dir = .
+width = 980
+height = 400
+post_script_sleep = 0.5
+
+[fieldset]
+url = /dashboards/My%%20First%%20Dashboard
+name = fieldset
+script = setup_fieldset.py

+ 35 - 0
docs/modules/ROOT/images/dashboards/fieldsets/setup_fieldset.py

@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+"""Open My First Dashboard from dashboards/2-fieldsets.adoc."""
+
+import time
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+def _wait_for_fieldset_dashboard(driver, timeout=30):
+    WebDriverWait(driver, timeout).until(
+        lambda d: d.find_element(By.TAG_NAME, "body").get_attribute("loaded-dashboard")
+        == "My First Dashboard"
+    )
+
+    def ready(d):
+        try:
+            folder1 = d.find_element(
+                By.XPATH,
+                '//button[contains(@class, "directory-button")]//span[contains(@class, "title") and text()="Folder 1"]',
+            )
+            folder2 = d.find_element(
+                By.XPATH,
+                '//button[contains(@class, "directory-button")]//span[contains(@class, "title") and text()="Folder 2"]',
+            )
+        except Exception:
+            return False
+        return all(element.is_displayed() for element in (folder1, folder2))
+
+    WebDriverWait(driver, timeout).until(ready)
+
+
+def run(driver):
+    _wait_for_fieldset_dashboard(driver)
+    time.sleep(0.2)

+ 2 - 0
docs/modules/ROOT/images/dashboards/folders/.gitignore

@@ -0,0 +1,2 @@
+custom-webui/
+__pycache__/

+ 2 - 0
docs/modules/ROOT/images/dashboards/folders/Makefile

@@ -0,0 +1,2 @@
+CONFIGDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+include ../../screenshots.mk

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels