aio-interface: improve headers (#7690)

Signed-off-by: Zoey <zoey@z0ey.de>
Signed-off-by: Simon L. <szaimen@e.mail.de>
Signed-off-by: Pablo Zmdl <pablo@nextcloud.com>
Co-authored-by: Simon L. <szaimen@e.mail.de>
Co-authored-by: Pablo Zmdl <pablo@nextcloud.com>
This commit is contained in:
Zoey
2026-04-16 17:20:50 +02:00
committed by GitHub
parent 82959585a8
commit 12e129f1f6
20 changed files with 162 additions and 100 deletions

View File

@@ -68,6 +68,8 @@ jobs:
--publish 8080:8080 \ --publish 8080:8080 \
--volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \ --volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \
--volume ./php:/var/www/docker-aio/php \ --volume ./php:/var/www/docker-aio/php \
--volume ./Containers/mastercontainer/internal.Caddyfile:/internal.Caddyfile \
--volume ./Containers/mastercontainer/headers.Caddyfile:/headers.Caddyfile \
--volume /var/run/docker.sock:/var/run/docker.sock:ro \ --volume /var/run/docker.sock:/var/run/docker.sock:ro \
--env SKIP_DOMAIN_VALIDATION=true \ --env SKIP_DOMAIN_VALIDATION=true \
--env APACHE_PORT=11000 \ --env APACHE_PORT=11000 \
@@ -97,6 +99,8 @@ jobs:
--publish 8080:8080 \ --publish 8080:8080 \
--volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \ --volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \
--volume ./php:/var/www/docker-aio/php \ --volume ./php:/var/www/docker-aio/php \
--volume ./Containers/mastercontainer/internal.Caddyfile:/internal.Caddyfile \
--volume ./Containers/mastercontainer/headers.Caddyfile:/headers.Caddyfile \
--volume /var/run/docker.sock:/var/run/docker.sock:ro \ --volume /var/run/docker.sock:/var/run/docker.sock:ro \
--env SKIP_DOMAIN_VALIDATION=false \ --env SKIP_DOMAIN_VALIDATION=false \
--env APACHE_PORT=11000 \ --env APACHE_PORT=11000 \

View File

@@ -17,8 +17,11 @@
https://{$ADDITIONAL_TRUSTED_DOMAIN}:443, https://{$ADDITIONAL_TRUSTED_DOMAIN}:443,
http://{$APACHE_HOST}.nextcloud-aio:23973, # For Collabora callback and WOPI requests, see containers.json http://{$APACHE_HOST}.nextcloud-aio:23973, # For Collabora callback and WOPI requests, see containers.json
{$PROTOCOL}://{$NC_DOMAIN}:{$APACHE_PORT} { {$PROTOCOL}://{$NC_DOMAIN}:{$APACHE_PORT} {
header -Server header {
header -X-Powered-By -Server
-X-Powered-By
-Via
}
# Collabora # Collabora
route /browser/* { route /browser/* {

View File

@@ -33,6 +33,8 @@ http://:80 {
} }
https://:8443 { https://:8443 {
import headers.Caddyfile
@denied { @denied {
path /api/auth/login /api/auth/getlogin path /api/auth/login /api/auth/getlogin
remote_host nextcloud-aio-nextcloud remote_host nextcloud-aio-nextcloud

View File

@@ -0,0 +1,27 @@
header {
# CSP limits which features can be used. By default we allow nothing and only allow required options. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy
# default-src 'none'; Allow nothing by default
# script-src-elem/style-src-elem 'self'; Only allow loading css/js files from same origin (AIO itself) while blocking all inline css/js
# img-src 'self'; Only allow loading images from same origin (from AIO itself)
# connect-src 'self'; Allow fetch to only connect same origin (to AIO itself)
# frame-src 'self'; Allow AIO to only embed itself "what can be embedded"
# base-uri 'none'; This does not fallback to default-src, AIO does not use the html base tag
# form-action 'self'; Html forms are only allowed to submit to AIO and not cross origin
# frame-ancestors 'self'; Only allow AIO itself to embed it self "who can embed"
# upgrade-insecure-requests; Upgrade all http embedings to https
# require-trusted-types-for 'script'; trusted-types 'none'; Blocks DOM changes via js
Content-Security-Policy "default-src 'none'; script-src-elem 'self'; style-src-elem 'self'; img-src 'self'; connect-src 'self'; frame-src 'self'; base-uri 'none'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests; require-trusted-types-for 'script'; trusted-types 'none';"
X-Content-Type-Options "nosniff" # This forces the browser to use the MIME type of the Content-Type header. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Content-Type-Options
X-Frame-Options "SAMEORIGIN" # Only allow AIO itself to embed itself, this is also enforced as part of the CSP frame-ancestors. See https://developer.mozilla.org/de/docs/Web/HTTP/Reference/Headers/X-Frame-Options
X-Permitted-Cross-Domain-Policies "none" # We block all cross origin request, including ones from Adobe Acrobat or Microsoft Silverlight and Adobe Flash Player. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Permitted-Cross-Domain-Policies
X-DNS-Prefetch-Control "off" # Tells the browser to not pre-fetch the DNS of linked pages. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-DNS-Prefetch-Control
Referrer-Policy "no-referrer" # Tells the browser to never sent a Referer header. See https://developer.mozilla.org/de/docs/Web/HTTP/Reference/Headers/Referrer-Policy
X-Robots-Tag "noindex, nofollow" # Tells web crawlers to not index this page. See https://developer.mozilla.org/de/docs/Web/HTTP/Reference/Headers/X-Robots-Tag
Cross-Origin-Opener-Policy "same-origin"; # AIO does not use any popup, still we can isolate its BCG if it is opened as a pop up by another page. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy
Cross-Origin-Embedder-Policy "require-corp"; # Harder rules for cross origin embeds. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Embedder-Policy
Cross-Origin-Resource-Policy "same-origin"; # Only allow the same origin to load resources. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cross-Origin_Resource_Policy
-Server
-X-Powered-By
-Via
}

View File

@@ -24,6 +24,8 @@
} }
https://:8080 { https://:8080 {
import headers.Caddyfile
@denied { @denied {
path /api/auth/login /api/auth/getlogin path /api/auth/login /api/auth/getlogin
remote_host nextcloud-aio-nextcloud remote_host nextcloud-aio-nextcloud

View File

@@ -0,0 +1,27 @@
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll('input[data-confirm]').forEach((element) => {
element.addEventListener('click', (event) => {
if (!confirm(element.dataset.confirm)) {
event.preventDefault();
}
});
});
document.querySelectorAll('input[data-input-show-password]').forEach((element) => {
element.addEventListener('input', (element) => {
let passwordField = element
if (passwordField.type === "password" && passwordField.value !== "") {
passwordField.type = "text";
} else if (passwordField.type === "text" && passwordField.value === "") {
passwordField.type = "password";
}
});
});
document.querySelectorAll('[data-stop-event-propagation="true"]').forEach((element) => {
element.addEventListener('click', (event) => {
event.stopPropagation();
});
});
});

View File

@@ -1,14 +1,5 @@
"use strict"; "use strict";
function showPassword(id) {
let passwordField = document.getElementById(id);
if (passwordField.type === "password" && passwordField.value !== "") {
passwordField.type = "text";
} else if (passwordField.type === "text" && passwordField.value === "") {
passwordField.type = "password";
}
}
(function (){ (function (){
let lastError; let lastError;

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="vertical-align: middle; margin-left: 4px;">
<path d="M6 12L10 8L6 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 270 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="vertical-align: middle; margin-right: 6px;">
<path d="M2 2L14 14M2 14L14 2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 253 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="vertical-align: middle; margin-left: 4px;">
<path d="M6 12L10 8L6 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 270 B

49
php/public/logs.css Normal file
View File

@@ -0,0 +1,49 @@
html, body {
height: 100%;
overflow: hidden;
padding: 0;
margin: 0;
}
pre {
height: 100%;
overflow: auto;
margin: 0;
padding: 1rem;
box-sizing: border-box;
}
#floating-box {
position: fixed;
top: 1rem;
right: 1rem;
max-width: calc(100vw - 2rem);
z-index: 10;
display: flex;
justify-content: end;
align-items: center;
}
#autoloading-box {
display: grid;
gap: 0.5rem;
font-size: large;
border: solid thin gray;
background-color: #f9f9f9;
width: 10rem;
padding: 0.5rem 1rem;
margin: 0 0 0 1rem;
}
.loader {
opacity: 1;
width: 40px;
height: 40px;
align-self: inherit;
}
@starting-style {
.loader {
opacity: 0;
}
}
.loader.hidden {
display: none;
opacity: 0;
transition: opacity 1s, display 1s allow-discrete;
}

View File

@@ -0,0 +1,9 @@
const observer = new MutationObserver((records) => {
const node = records[0]?.addedNodes[0];
// Text nodes also appear here but can't be scrolled to, so we have to check for the
// function being present.
if (node && typeof(node.scrollIntoView) === 'function') {
node.scrollIntoView();
}
});
observer.observe(document, {childList: true, subtree: true});

View File

@@ -32,4 +32,7 @@ function setThemeIcon(theme) {
setThemeToDOM(getSavedTheme()); setThemeToDOM(getSavedTheme());
// Apply theme when the page loads // Apply theme when the page loads
document.addEventListener('DOMContentLoaded', () => setThemeIcon(getSavedTheme())); document.addEventListener('DOMContentLoaded', () => {
setThemeIcon(getSavedTheme())
document.querySelector('button#theme-toggle')?.addEventListener('click', () => toggleTheme());
});

View File

@@ -381,17 +381,7 @@ readonly class DockerController {
<html lang="en" class="overlay-iframe"> <html lang="en" class="overlay-iframe">
<head> <head>
<link rel="stylesheet" href="../../style.css?v8" media="all" /> <link rel="stylesheet" href="../../style.css?v8" media="all" />
<script> <script type="text/javascript" src="../../scroll-into-view.js"></script>
const observer = new MutationObserver((records) => {
const node = records[0]?.addedNodes[0];
// Text nodes also appear here but can't be scrolled to, so we have to check for the
// function being present.
if (node && typeof(node.scrollIntoView) === 'function') {
node.scrollIntoView();
}
});
observer.observe(document, {childList: true, subtree: true});
</script>
</head> </head>
<body> <body>

View File

@@ -153,7 +153,7 @@
<form method="POST" action="api/docker/backup-check-repair" target="overlay-log"> <form method="POST" action="api/docker/backup-check-repair" target="overlay-log">
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}"> <input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}"> <input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="submit" value="Check and repair backup integrity" onclick="return confirm('Check and repair backup integrity? Are you sure that you want to check and repair the backup integrity? This should only be done after reading the mentioned documentation.')"/> <input type="submit" value="Check and repair backup integrity" data-confirm='Check and repair backup integrity? Are you sure that you want to check and repair the backup integrity? This should only be done after reading the mentioned documentation.'/>
</form> </form>
</details> </details>
{% endif %} {% endif %}
@@ -178,7 +178,7 @@
{% endfor %} {% endfor %}
</select><br> </select><br>
<input type="checkbox" id="restore-exclude-previews" name="restore-exclude-previews"><label for="restore-exclude-previews">Exclude previews from restore which will speed up the restore process but will trigger a scan of the preview folder as soon as the Nextcloud container starts the next time</label><br> <input type="checkbox" id="restore-exclude-previews" name="restore-exclude-previews"><label for="restore-exclude-previews">Exclude previews from restore which will speed up the restore process but will trigger a scan of the preview folder as soon as the Nextcloud container starts the next time</label><br>
<input type="submit" value="Restore selected backup" onclick="return confirm('⚠️ Important: If the backup that you want to restore contained any community container, you need to restore the same backup a second time after this attempt so that the community container data is also correctly restored.')"/> <input type="submit" value="Restore selected backup" data-confirm='⚠️ Important: If the backup that you want to restore contained any community container, you need to restore the same backup a second time after this attempt so that the community container data is also correctly restored.'/>
</form> </form>
{% endif %} {% endif %}
{% elseif borg_backup_mode == 'restore' %} {% elseif borg_backup_mode == 'restore' %}
@@ -366,7 +366,7 @@
{% if bypass_container_update == true %} {% if bypass_container_update == true %}
<input type="hidden" name="bypass_container_update" value="true"> <input type="hidden" name="bypass_container_update" value="true">
{% endif %} {% endif %}
<input class="button " type="submit" value="Start and update containers" onclick="return confirm('Start and update containers? You should consider creating a backup first.')" /> <input class="button " type="submit" value="Start and update containers" data-confirm='Start and update containers? You should consider creating a backup first.' />
</form> </form>
{% endif %} {% endif %}
{% endif %} {% endif %}
@@ -413,7 +413,7 @@
<form method="POST" action="api/docker/backup-check-repair" target="overlay-log"> <form method="POST" action="api/docker/backup-check-repair" target="overlay-log">
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}"> <input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}"> <input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="submit" value="Check and repair backup integrity" onclick="return confirm('Check and repair backup integrity? Are you sure that you want to check and repair the backup integrity? This should only be done after reading the mentioned documentation.')"/> <input type="submit" value="Check and repair backup integrity" data-confirm='Check and repair backup integrity? Are you sure that you want to check and repair the backup integrity? This should only be done after reading the mentioned documentation.'/>
</form> </form>
</details> </details>
{% endif %} {% endif %}
@@ -478,7 +478,7 @@
<form method="POST" action="api/docker/backup" target="overlay-log"> <form method="POST" action="api/docker/backup" target="overlay-log">
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}"> <input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}"> <input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="submit" value="Create backup" onclick="return confirm('Create backup? Are you sure that you want to create a backup? This will stop all running containers and create the backup.')" /> <input type="submit" value="Create backup" data-confirm='Create backup? Are you sure that you want to create a backup? This will stop all running containers and create the backup.' />
</form> </form>
{% if has_backup_run_once == true %} {% if has_backup_run_once == true %}
@@ -490,7 +490,7 @@
<form method="POST" action="api/docker/backup-check" target="overlay-log"> <form method="POST" action="api/docker/backup-check" target="overlay-log">
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}"> <input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}"> <input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="submit" value="Check backup integrity" onclick="return confirm('Check backup integrity? Are you sure that you want to check the backup? This can take a long time depending on the size of your backup.')" /> <input type="submit" value="Check backup integrity" data-confirm='Check backup integrity? Are you sure that you want to check the backup? This can take a long time depending on the size of your backup.' />
</form> </form>
<h3>Backup restore</h3> <h3>Backup restore</h3>
@@ -503,7 +503,7 @@
<option value="{{ restore_time }}">{{ restore_time }} UTC</option> <option value="{{ restore_time }}">{{ restore_time }} UTC</option>
{% endfor %} {% endfor %}
</select> </select>
<input type="submit" value="Restore selected backup" onclick="return confirm('Restore the selected backup? Are you sure that you want to restore the selected backup? This will stop all running containers and restore the selected backup. It is recommended to create a backup first. You might also want to check the backup integrity.')" /> <input type="submit" value="Restore selected backup" data-confirm='Restore the selected backup? Are you sure that you want to restore the selected backup? This will stop all running containers and restore the selected backup. It is recommended to create a backup first. You might also want to check the backup integrity.' />
</form> </form>
<h3>Update backup list</h3> <h3>Update backup list</h3>
@@ -570,7 +570,7 @@
<input type="hidden" name="delete_borg_backup_location_vars" value="yes"/> <input type="hidden" name="delete_borg_backup_location_vars" value="yes"/>
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}"> <input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}"> <input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="submit" value="Reset backup location" onclick="return confirm('Are you sure that you want to reset the backup location?')" /> <input type="submit" value="Reset backup location" data-confirm='Are you sure that you want to reset the backup location?' />
</form> </form>
{% endif %} {% endif %}
{% if has_backup_run_once == true %} {% if has_backup_run_once == true %}
@@ -587,8 +587,8 @@
<summary>Click here to change your AIO passphrase</summary> <summary>Click here to change your AIO passphrase</summary>
<p>You can change your AIO passphrase below:</p> <p>You can change your AIO passphrase below:</p>
<form method="POST" action="api/configuration" class="xhr"> <form method="POST" action="api/configuration" class="xhr">
<input type="password" autocomplete="current-password" name="current-master-password" placeholder="Your current AIO passphrase" id="current-master-password" oninput="showPassword('current-master-password')"> <input type="password" autocomplete="current-password" name="current-master-password" placeholder="Your current AIO passphrase" id="current-master-password" data-input-show-password="showPassword('current-master-password')">
<input type="password" autocomplete="new-password" name="new-master-password" placeholder="Your new AIO passphrase" id="new-master-password" oninput="showPassword('new-master-password')"> <input type="password" autocomplete="new-password" name="new-master-password" placeholder="Your new AIO passphrase" id="new-master-password" data-input-show-password="showPassword('new-master-password')">
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}"> <input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}"> <input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="submit" value="Submit passphrase change" /> <input type="submit" value="Submit passphrase change" />
@@ -616,7 +616,7 @@
<input type="text" id="timezone" name="timezone" placeholder="Europe/Berlin" /> <input type="text" id="timezone" name="timezone" placeholder="Europe/Berlin" />
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}"> <input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}"> <input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="submit" value="Submit timezone" onclick="return confirm('Are you sure that this is a valid timezone? Please double check by following the wikipedia article and checking the correct column. If the timezone is not valid, it will break the startup since the database will not be correctly initialized and you will end up in a startup loop.')" /> <input type="submit" value="Submit timezone" data-confirm='Are you sure that this is a valid timezone? Please double check by following the wikipedia article and checking the correct column. If the timezone is not valid, it will break the startup since the database will not be correctly initialized and you will end up in a startup loop.' />
</form> </form>
<p>You need to make sure that the timezone that you enter is valid. An example is <strong>Europe/Berlin</strong>. You can get valid values by looking at the 'TZ identifier' column of this list: <a target="_blank" href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List"><strong>click here</strong></a>. The default is <strong>Etc/UTC</strong> if nothing is entered.</p> <p>You need to make sure that the timezone that you enter is valid. An example is <strong>Europe/Berlin</strong>. You can get valid values by looking at the 'TZ identifier' column of this list: <a target="_blank" href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List"><strong>click here</strong></a>. The default is <strong>Etc/UTC</strong> if nothing is entered.</p>
{% else %} {% else %}

View File

@@ -37,6 +37,6 @@
</p> </p>
{% endfor %} {% endfor %}
<input id="community-form-submit" type="submit" value="Save changes" onclick="return confirm('Are you sure that you read the documentation of all community containers that you enabled? If no, please do not continue as this might break your instance!')" /> <input id="community-form-submit" type="submit" value="Save changes" data-confirm='Are you sure that you read the documentation of all community containers that you enabled? If no, please do not continue as this might break your instance!' />
</form> </form>
</details> </details>

View File

@@ -41,11 +41,9 @@
<li>Best support for legacy files</li> <li>Best support for legacy files</li>
</ul> </ul>
{% if isAnyRunning == false %} {% if isAnyRunning == false %}
<a href="https://www.collaboraoffice.com/code/" target="_blank" class="office-learn-more" onclick="event.stopPropagation();"> <a href="https://www.collaboraoffice.com/code/" target="_blank" class="office-learn-more" data-stop-event-propagation="true">
Learn more Learn more
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="vertical-align: middle; margin-left: 4px;"> <img src="img/collabora.svg" alt="Collabora Logo" />
<path d="M6 12L10 8L6 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a> </a>
{% endif %} {% endif %}
</label> </label>
@@ -76,11 +74,9 @@
<li>Limited ODF compatibility</li> <li>Limited ODF compatibility</li>
</ul> </ul>
{% if isAnyRunning == false %} {% if isAnyRunning == false %}
<a href="https://www.onlyoffice.com/" target="_blank" class="office-learn-more" onclick="event.stopPropagation();"> <a href="https://www.onlyoffice.com/" target="_blank" class="office-learn-more" data-stop-event-propagation="true">
Learn more Learn more
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="vertical-align: middle; margin-left: 4px;"> <img src="img/onlyoffice.svg" alt="Onlyoffice Logo" />
<path d="M6 12L10 8L6 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a> </a>
{% endif %} {% endif %}
</label> </label>
@@ -99,9 +95,7 @@
{% endif %} {% endif %}
> >
<label class="office-none-label" for="office-none"> <label class="office-none-label" for="office-none">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="vertical-align: middle; margin-right: 6px;"> <img src="img/office-none.svg" alt="Disable Office icon" />
<path d="M2 2L14 14M2 14L14 2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
Disable office suite Disable office suite
</label> </label>
</div> </div>

View File

@@ -1,10 +1,12 @@
<html> <!DOCTYPE html>
<html lang="en">
<head> <head>
<title>AIO</title> <title>AIO</title>
<link rel="stylesheet" href="style.css?v9" media="all" /> <link rel="stylesheet" href="style.css?v9" media="all" />
<link rel="icon" href="img/favicon.png"> <link rel="icon" href="img/favicon.png">
<script type="text/javascript" src="forms.js?v2"></script> <script type="text/javascript" src="forms.js?v2"></script>
<script type="text/javascript" src="toggle-dark-mode.js?v1"></script> <script type="text/javascript" src="toggle-dark-mode.js?v1"></script>
<script type="text/javascript" src="click-handlers.js?v1"></script>
</head> </head>
<body> <body>
@@ -15,7 +17,7 @@
<div class="loader"></div> <div class="loader"></div>
<iframe name="overlay-log" id="overlay-log"></iframe> <iframe name="overlay-log" id="overlay-log"></iframe>
</div> </div>
<button id="theme-toggle" onclick="toggleTheme()"> <button id="theme-toggle">
<span id="theme-icon"></span> <span id="theme-icon"></span>
</button> </button>
</body> </body>

View File

@@ -2,57 +2,7 @@
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<style> <link rel="stylesheet" href="logs.css">
html, body {
height: 100%;
overflow: hidden;
padding: 0;
margin: 0;
}
pre {
height: 100%;
overflow: auto;
margin: 0;
padding: 1rem;
box-sizing: border-box;
}
#floating-box {
position: fixed;
top: 1rem;
right: 1rem;
max-width: calc(100vw - 2rem);
z-index: 10;
display: flex;
justify-content: end;
align-items: center;
}
#autoloading-box {
display: grid;
gap: 0.5rem;
font-size: large;
border: solid thin gray;
background-color: #f9f9f9;
width: 10rem;
padding: 0.5rem 1rem;
margin: 0 0 0 1rem;
}
.loader {
opacity: 1;
width: 40px;
height: 40px;
align-self: inherit;
}
@starting-style {
.loader {
opacity: 0;
}
}
.loader.hidden {
display: none;
opacity: 0;
transition: opacity 1s, display 1s allow-discrete;
}
</style>
<script src="log-view.js?v1"></script> <script src="log-view.js?v1"></script>
</head> </head>
<body data-container-id="{{ id }}"> <body data-container-id="{{ id }}">

View File

@@ -11,7 +11,7 @@
{% if is_login_allowed == true %} {% if is_login_allowed == true %}
<p>Log in using your Nextcloud AIO passphrase:</p> <p>Log in using your Nextcloud AIO passphrase:</p>
<form method="POST" action="api/auth/login" class="xhr"> <form method="POST" action="api/auth/login" class="xhr">
<input type="password" autocomplete="current-password" name="password" placeholder="Password" id="master-password" oninput="showPassword('master-password')"> <input type="password" autocomplete="current-password" name="password" placeholder="Password" id="master-password" data-input-show-password="showPassword('master-password')">
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}"> <input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}"> <input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="submit" class="button" value="Log in" /> <input type="submit" class="button" value="Log in" />