feat(webdav): Anleitung zum Einhaengen neben den App-Passwoertern
Ein Info-Knopf unter Einstellungen -> Access oeffnet drei Wege: Windows (Netzlaufwerk), Linux ueber davfs2 samt fstab und secrets, und Linux ueber den Dateimanager. Dazu WebDAV-Adresse, Benutzername und Cloud-Adresse zum Kopieren. Die WebDAV-Adresse stand bisher nirgends in der Oberflaeche - sie ist zwar ableitbar, aber nur wenn man weiss, dass es sie gibt. Sie entsteht aus window.location statt aus PUBLIC_URL: Was im Browser traegt, traegt auch im Dateimanager. Mitgenommen sind die drei Stellen, an denen es sonst scheitert: Windows bricht ohne HTTPS ab und laedt nichts ueber 50 MB, die Konsole braucht davfs2, und Dateimanager wollen davs:// statt https://. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9a2f90c89b
commit
0ef7bd67ab
3 files changed
+364
-1
No files matched your search
+20
@@ -1336,6 +1336,26 @@ Kreise plus Rechteck dazwischen, keine Freihandkurve) und in RGBA gerendert.
|
|||||||
- [ ] Auf dem Startbildschirm frühestens nach Neu-Installieren (Chrome hält
|
- [ ] Auf dem Startbildschirm frühestens nach Neu-Installieren (Chrome hält
|
||||||
eine eigene Kopie)
|
eine eigene Kopie)
|
||||||
|
|
||||||
|
## Anleitung „Connect as a drive" (24.08.2026)
|
||||||
|
|
||||||
|
Neuer Info-Knopf unter Einstellungen → Access, neben den App-Passwörtern.
|
||||||
|
Die Adressen entstehen aus `window.location`, nicht aus `PUBLIC_URL` – was im
|
||||||
|
Browser trägt, trägt auch im Dateimanager.
|
||||||
|
|
||||||
|
Geprüft auf 1280×900 und 412×915:
|
||||||
|
|
||||||
|
- Alle drei Reiter (Windows, Linux · Konsole, Linux · Dateimanager) öffnen.
|
||||||
|
- Über HTTP erscheint der rote Hinweis und der Dateimanager-Reiter bietet
|
||||||
|
`dav://` an; über HTTPS `davs://` und kein Hinweis.
|
||||||
|
- Die Kopierknöpfe stehen an WebDAV-Adresse, Benutzername, Cloud-Adresse und
|
||||||
|
an jedem Befehlsblock.
|
||||||
|
|
||||||
|
- [ ] Auf der produktiven Instanz gegenprüfen, dass dort `https://…/dav/`
|
||||||
|
steht und der HTTPS-Hinweis **nicht** erscheint
|
||||||
|
- [ ] Einen der Befehle wirklich ausführen (davfs2-Einhängen)
|
||||||
|
- [ ] Windows-Weg auf einem echten Windows durchspielen – die 50-MB-Grenze und
|
||||||
|
der Registry-Pfad stammen aus der Dokumentation, nicht aus einem Versuch
|
||||||
|
|
||||||
## Automatisiert geprüft (22.08.2026)
|
## Automatisiert geprüft (22.08.2026)
|
||||||
|
|
||||||
Diese Punkte hat Claude per API/`curl` gegen Wegwerf-Konten durchgespielt; sie
|
Diese Punkte hat Claude per API/`curl` gegen Wegwerf-Konten durchgespielt; sie
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { appPasswordsApi, type AppPassword } from '../api/client'
|
import { appPasswordsApi, type AppPassword } from '../api/client'
|
||||||
import ConfirmDialog from './ConfirmDialog'
|
import ConfirmDialog from './ConfirmDialog'
|
||||||
|
import WebDavGuide from './WebDavGuide'
|
||||||
|
import { InfoIcon } from './icons'
|
||||||
|
|
||||||
/** Zugangsdaten für WebDAV-Clients, die das Hauptpasswort ersetzen: Geht ein
|
/** Zugangsdaten für WebDAV-Clients, die das Hauptpasswort ersetzen: Geht ein
|
||||||
* Gerät verloren, widerruft man dieses eine Token statt das Konto neu
|
* Gerät verloren, widerruft man dieses eine Token statt das Konto neu
|
||||||
@@ -12,6 +14,7 @@ export default function AppPasswords({ username }: { username: string }) {
|
|||||||
const [creating, setCreating] = useState(false)
|
const [creating, setCreating] = useState(false)
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
const [revoking, setRevoking] = useState<AppPassword | null>(null)
|
const [revoking, setRevoking] = useState<AppPassword | null>(null)
|
||||||
|
const [guide, setGuide] = useState(false)
|
||||||
|
|
||||||
// Nur einmal im Klartext vorhanden – danach kennt der Server nur den Hash.
|
// Nur einmal im Klartext vorhanden – danach kennt der Server nur den Hash.
|
||||||
const [freshToken, setFreshToken] = useState<string | null>(null)
|
const [freshToken, setFreshToken] = useState<string | null>(null)
|
||||||
@@ -67,12 +70,29 @@ export default function AppPasswords({ username }: { username: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p style={{ margin: '-8px 0 16px', fontSize: 12, color: 'var(--color-text-muted)', lineHeight: 1.5 }}>
|
<p style={{ margin: '-8px 0 12px', fontSize: 12, color: 'var(--color-text-muted)', lineHeight: 1.5 }}>
|
||||||
WebDAV and sync clients need an app password — your account password does
|
WebDAV and sync clients need an app password — your account password does
|
||||||
not work there. Each one can be revoked on its own, so a lost device costs
|
not work there. Each one can be revoked on its own, so a lost device costs
|
||||||
you one password, not your account.
|
you one password, not your account.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{/* Die Anleitung steht neben der Erklärung, nicht in ihr: Wer schon weiß,
|
||||||
|
wie es geht, braucht sie nie – wer sie braucht, sucht sie genau hier,
|
||||||
|
weil hier das Passwort entsteht. Die WebDAV-Adresse stand vorher
|
||||||
|
nirgends in der Oberfläche. */}
|
||||||
|
<button
|
||||||
|
onClick={() => setGuide(true)}
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex', alignItems: 'center', gap: 6, marginBottom: 16,
|
||||||
|
padding: '6px 12px', borderRadius: 8,
|
||||||
|
border: '1px solid var(--color-border)', background: 'transparent',
|
||||||
|
color: 'var(--color-text)', fontSize: 12, fontWeight: 600, cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InfoIcon size={14} />
|
||||||
|
How to connect a drive
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* Der Klartext erscheint genau einmal; deshalb prominent und mit
|
{/* Der Klartext erscheint genau einmal; deshalb prominent und mit
|
||||||
deutlicher Ansage statt als beiläufige Zeile in der Liste. */}
|
deutlicher Ansage statt als beiläufige Zeile in der Liste. */}
|
||||||
{freshToken && (
|
{freshToken && (
|
||||||
@@ -202,6 +222,8 @@ export default function AppPasswords({ username }: { username: string }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{guide && <WebDavGuide username={username} onClose={() => setGuide(false)} />}
|
||||||
|
|
||||||
{revoking && (
|
{revoking && (
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
title="Revoke app password"
|
title="Revoke app password"
|
||||||
|
|||||||
@@ -0,0 +1,321 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useOverlayDismiss } from '../hooks/useOverlayDismiss'
|
||||||
|
import Tabs from './Tabs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wie hänge ich diese Cloud als Laufwerk ein?
|
||||||
|
*
|
||||||
|
* Die Adresse steht bisher nirgends. Sie ist zwar ableitbar – Cloud-Adresse
|
||||||
|
* plus `/dav/` –, aber „ableitbar" heißt: Man muss wissen, dass es sie gibt.
|
||||||
|
* Und die drei Wege dorthin (Windows, Konsole, Dateimanager) haben je eine
|
||||||
|
* Eigenheit, an der es sonst scheitert:
|
||||||
|
*
|
||||||
|
* * Windows verlangt HTTPS und lädt ohne Registry-Eingriff nichts über 50 MB.
|
||||||
|
* * Die Konsole braucht ein zusätzliches Paket (davfs2).
|
||||||
|
* * Dateimanager wollen ein eigenes Schema: `davs://` statt `https://`.
|
||||||
|
*
|
||||||
|
* Die Adressen entstehen aus `window.location`, nicht aus einer Einstellung:
|
||||||
|
* Was im Browser funktioniert, funktioniert auch im Dateimanager – eine
|
||||||
|
* falsch gesetzte PUBLIC_URL würde hier sonst eine Adresse anbieten, die
|
||||||
|
* nachweislich nicht die ist, über die der Betrachter gerade hereinkam.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
username: string
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tab = 'windows' | 'terminal' | 'files'
|
||||||
|
|
||||||
|
/** Eine Zeile zum Abschreiben – mit Knopf, weil genau das sonst schiefgeht. */
|
||||||
|
function CopyRow({ label, value, hint }: { label: string; value: string; hint?: string }) {
|
||||||
|
const [copied, setCopied] = useState(false)
|
||||||
|
|
||||||
|
function copy() {
|
||||||
|
navigator.clipboard?.writeText(value).then(() => {
|
||||||
|
setCopied(true)
|
||||||
|
setTimeout(() => setCopied(false), 1500)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: 10 }}>
|
||||||
|
<div style={{ fontSize: 11, color: 'var(--color-text-muted)', marginBottom: 4 }}>{label}</div>
|
||||||
|
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||||
|
<code style={{
|
||||||
|
flex: 1, minWidth: 0, padding: '8px 10px', borderRadius: 8,
|
||||||
|
background: 'var(--color-surface-2)', color: 'var(--color-text)',
|
||||||
|
fontSize: 12.5, fontFamily: 'monospace',
|
||||||
|
overflowX: 'auto', whiteSpace: 'nowrap', userSelect: 'all',
|
||||||
|
}}>{value}</code>
|
||||||
|
<button
|
||||||
|
onClick={copy}
|
||||||
|
style={{
|
||||||
|
padding: '8px 12px', borderRadius: 8, border: '1px solid var(--color-border)',
|
||||||
|
background: 'transparent', color: 'var(--color-text)', flexShrink: 0,
|
||||||
|
fontSize: 11.5, fontWeight: 600, cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{copied ? 'Copied' : 'Copy'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{hint && (
|
||||||
|
<div style={{ fontSize: 11, color: 'var(--color-text-muted)', marginTop: 4, lineHeight: 1.5 }}>
|
||||||
|
{hint}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Step({ n, children }: { n: number; children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', gap: 10, marginBottom: 9 }}>
|
||||||
|
<div style={{
|
||||||
|
width: 19, height: 19, borderRadius: '50%', flexShrink: 0, marginTop: 1,
|
||||||
|
background: 'var(--color-surface-2)', color: 'var(--color-text-muted)',
|
||||||
|
fontSize: 11, fontWeight: 700,
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
}}>{n}</div>
|
||||||
|
<div style={{ fontSize: 13, color: 'var(--color-text)', lineHeight: 1.55 }}>{children}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ein Kasten für das, was an dieser Stelle erfahrungsgemäß schiefgeht. */
|
||||||
|
function Caveat({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
marginTop: 12, padding: '10px 12px', borderRadius: 9,
|
||||||
|
border: '1px solid var(--color-border)',
|
||||||
|
background: 'var(--color-surface-2)',
|
||||||
|
fontSize: 12, color: 'var(--color-text-muted)', lineHeight: 1.6,
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Code({ children }: { children: string }) {
|
||||||
|
const [copied, setCopied] = useState(false)
|
||||||
|
return (
|
||||||
|
<div style={{ position: 'relative', marginBottom: 10 }}>
|
||||||
|
<pre style={{
|
||||||
|
margin: 0, padding: '10px 12px', paddingRight: 62, borderRadius: 8,
|
||||||
|
background: 'var(--color-surface-2)', color: 'var(--color-text)',
|
||||||
|
fontSize: 12, lineHeight: 1.65, overflowX: 'auto',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
}}>{children}</pre>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard?.writeText(children).then(() => {
|
||||||
|
setCopied(true)
|
||||||
|
setTimeout(() => setCopied(false), 1500)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
position: 'absolute', top: 6, right: 6,
|
||||||
|
padding: '4px 9px', borderRadius: 6, border: '1px solid var(--color-border)',
|
||||||
|
background: 'var(--color-surface)', color: 'var(--color-text-muted)',
|
||||||
|
fontSize: 10.5, fontWeight: 600, cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{copied ? 'Copied' : 'Copy'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WebDavGuide({ username, onClose }: Props) {
|
||||||
|
const overlay = useOverlayDismiss(onClose)
|
||||||
|
const [tab, setTab] = useState<Tab>('windows')
|
||||||
|
|
||||||
|
const origin = window.location.origin
|
||||||
|
const davURL = `${origin}/dav/`
|
||||||
|
// Dateimanager sprechen nicht https, sondern davs (verschlüsselt) bzw. dav.
|
||||||
|
const davsURL = davURL.replace(/^https:/, 'davs:').replace(/^http:/, 'dav:')
|
||||||
|
const isHTTPS = window.location.protocol === 'https:'
|
||||||
|
const host = window.location.host
|
||||||
|
// Dolphin kennt beide Schreibweisen; die verschlüsselte heißt anders.
|
||||||
|
const kioURL = `${isHTTPS ? 'webdavs' : 'webdav'}://${host}/dav/`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||||
|
style={{ background: 'rgba(0,0,0,0.6)' }}
|
||||||
|
{...overlay}>
|
||||||
|
<div className="w-full rounded-xl shadow-2xl border"
|
||||||
|
style={{
|
||||||
|
maxWidth: 620, maxHeight: '86vh', overflowY: 'auto',
|
||||||
|
padding: 20,
|
||||||
|
background: 'var(--color-surface)', borderColor: 'var(--color-border)',
|
||||||
|
}}>
|
||||||
|
<h2 className="font-semibold mb-1" style={{ color: 'var(--color-text)' }}>
|
||||||
|
Connect as a drive
|
||||||
|
</h2>
|
||||||
|
<p style={{ margin: '0 0 16px', fontSize: 12.5, color: 'var(--color-text-muted)', lineHeight: 1.55 }}>
|
||||||
|
Your files over WebDAV — same folders, in your file manager. Sign in with
|
||||||
|
your username and an <strong style={{ color: 'var(--color-text)' }}>app password</strong>;
|
||||||
|
your account password is refused here on purpose.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<CopyRow label="WebDAV address" value={davURL} />
|
||||||
|
<CopyRow label="Username" value={username} />
|
||||||
|
<CopyRow
|
||||||
|
label="Cloud address (browser)"
|
||||||
|
value={origin}
|
||||||
|
hint="The normal address — for sharing links or opening the cloud on another device."
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!isHTTPS && (
|
||||||
|
<Caveat>
|
||||||
|
<strong style={{ color: 'var(--color-danger)' }}>This page is not using HTTPS.</strong>{' '}
|
||||||
|
Windows refuses WebDAV over plain HTTP outright, and every other client
|
||||||
|
sends your app password unencrypted. Put a reverse proxy with TLS in
|
||||||
|
front before you connect a device.
|
||||||
|
</Caveat>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ marginTop: 18 }}>
|
||||||
|
<Tabs<Tab>
|
||||||
|
tabs={[
|
||||||
|
{ id: 'windows', label: 'Windows' },
|
||||||
|
{ id: 'terminal', label: 'Linux · terminal' },
|
||||||
|
{ id: 'files', label: 'Linux · file manager' },
|
||||||
|
]}
|
||||||
|
active={tab}
|
||||||
|
onChange={setTab}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{tab === 'windows' && (
|
||||||
|
<div>
|
||||||
|
<Step n={1}>Open Explorer, right-click <strong>This PC</strong> → <strong>Map network drive…</strong></Step>
|
||||||
|
<Step n={2}>Folder: <code style={{ fontSize: 12 }}>{davURL}</code></Step>
|
||||||
|
<Step n={3}>Tick <strong>Connect using different credentials</strong>.</Step>
|
||||||
|
<Step n={4}>Username <strong>{username}</strong>, password = your app password.</Step>
|
||||||
|
|
||||||
|
<Caveat>
|
||||||
|
<strong style={{ color: 'var(--color-text)' }}>Two things Windows will do to you.</strong>
|
||||||
|
<br />
|
||||||
|
Downloads stop at <strong>50 MB</strong> — that is a built-in limit of the
|
||||||
|
Windows WebDAV client, not of this server. Raise it in
|
||||||
|
<code style={{ fontSize: 11.5 }}> HKLM\SYSTEM\CurrentControlSet\Services\WebClient\Parameters </code>
|
||||||
|
→ <code style={{ fontSize: 11.5 }}>FileSizeLimitInBytes</code> (decimal
|
||||||
|
4294967295 = 4 GB), then restart the <em>WebClient</em> service.
|
||||||
|
<br /><br />
|
||||||
|
If mapping fails immediately, that service is usually not running:
|
||||||
|
<code style={{ fontSize: 11.5 }}> services.msc </code>→ WebClient → start,
|
||||||
|
set to Automatic.
|
||||||
|
<br /><br />
|
||||||
|
For large files, a dedicated client (WinSCP, Cyberduck, RaiDrive) is the
|
||||||
|
calmer route — none of them have that limit.
|
||||||
|
</Caveat>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tab === 'terminal' && (
|
||||||
|
<div>
|
||||||
|
<p style={{ margin: '0 0 10px', fontSize: 13, color: 'var(--color-text)', lineHeight: 1.55 }}>
|
||||||
|
Mount it with <strong>davfs2</strong>. It asks for username and password
|
||||||
|
on the first mount.
|
||||||
|
</p>
|
||||||
|
<Code>{`sudo apt install davfs2 # Debian/Ubuntu
|
||||||
|
# sudo dnf install davfs2 # Fedora
|
||||||
|
# sudo pacman -S davfs2 # Arch
|
||||||
|
|
||||||
|
sudo mkdir -p /mnt/aether
|
||||||
|
sudo mount -t davfs ${davURL} /mnt/aether`}</Code>
|
||||||
|
|
||||||
|
<p style={{ margin: '14px 0 8px', fontSize: 13, fontWeight: 600, color: 'var(--color-text)' }}>
|
||||||
|
Without typing the password every time
|
||||||
|
</p>
|
||||||
|
<p style={{ margin: '0 0 8px', fontSize: 12.5, color: 'var(--color-text-muted)', lineHeight: 1.55 }}>
|
||||||
|
Put the credentials in <code style={{ fontSize: 12 }}>~/.davfs2/secrets</code> — the
|
||||||
|
file must be readable by you alone, davfs2 refuses it otherwise:
|
||||||
|
</p>
|
||||||
|
<Code>{`mkdir -p ~/.davfs2
|
||||||
|
echo '${davURL} ${username} YOUR-APP-PASSWORD' >> ~/.davfs2/secrets
|
||||||
|
chmod 600 ~/.davfs2/secrets`}</Code>
|
||||||
|
|
||||||
|
<p style={{ margin: '14px 0 8px', fontSize: 13, fontWeight: 600, color: 'var(--color-text)' }}>
|
||||||
|
Mount it at boot
|
||||||
|
</p>
|
||||||
|
<p style={{ margin: '0 0 8px', fontSize: 12.5, color: 'var(--color-text-muted)', lineHeight: 1.55 }}>
|
||||||
|
One line in <code style={{ fontSize: 12 }}>/etc/fstab</code>:
|
||||||
|
</p>
|
||||||
|
<Code>{`${davURL} /mnt/aether davfs user,noauto,_netdev 0 0`}</Code>
|
||||||
|
|
||||||
|
<Caveat>
|
||||||
|
<code style={{ fontSize: 11.5 }}>_netdev</code> matters: without it the
|
||||||
|
system tries to mount before the network is up and the boot stalls.
|
||||||
|
<br /><br />
|
||||||
|
davfs2 caches in <code style={{ fontSize: 11.5 }}>~/.davfs2/cache</code>.
|
||||||
|
A large file is written there first and only then uploaded — so a copy
|
||||||
|
can look finished while it is still on its way.
|
||||||
|
<br /><br />
|
||||||
|
Prefer <strong>rclone</strong>? <code style={{ fontSize: 11.5 }}>rclone config</code> →
|
||||||
|
type <code style={{ fontSize: 11.5 }}>webdav</code> → vendor{' '}
|
||||||
|
<code style={{ fontSize: 11.5 }}>other</code> → URL as above. It handles
|
||||||
|
interrupted transfers better.
|
||||||
|
</Caveat>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tab === 'files' && (
|
||||||
|
<div>
|
||||||
|
<p style={{ margin: '0 0 12px', fontSize: 13, color: 'var(--color-text)', lineHeight: 1.55 }}>
|
||||||
|
GNOME Files, Dolphin, Nemo and Thunar all speak WebDAV natively — but they
|
||||||
|
want their own scheme, not <code style={{ fontSize: 12 }}>https://</code>:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<CopyRow
|
||||||
|
label="Address for the file manager"
|
||||||
|
value={davsURL}
|
||||||
|
hint={isHTTPS
|
||||||
|
? 'davs:// means WebDAV over TLS. Plain dav:// would send your password in the clear.'
|
||||||
|
: 'dav:// (unencrypted) because this page is not on HTTPS — see the warning above.'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Step n={1}>
|
||||||
|
<strong>GNOME Files:</strong> <em>Other Locations</em> at the bottom of the
|
||||||
|
sidebar → <em>Connect to Server</em> → paste the address → Connect.
|
||||||
|
</Step>
|
||||||
|
<Step n={2}>
|
||||||
|
<strong>KDE Dolphin:</strong> type the address straight into the location
|
||||||
|
bar (Ctrl + L). Dolphin also accepts{' '}
|
||||||
|
<code style={{ fontSize: 12 }}>{kioURL}</code>.
|
||||||
|
</Step>
|
||||||
|
<Step n={3}>
|
||||||
|
Username <strong>{username}</strong>, password = your app password. Tick
|
||||||
|
“remember” so the keyring keeps it.
|
||||||
|
</Step>
|
||||||
|
<Step n={4}>
|
||||||
|
Right-click the mount in the sidebar → <em>Add Bookmark</em>, and it stays
|
||||||
|
one click away.
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Caveat>
|
||||||
|
These mounts live in GVFS/KIO, not in the kernel — they show up in the file
|
||||||
|
manager, but not always for other programs. If a tool cannot see the files,
|
||||||
|
look under <code style={{ fontSize: 11.5 }}>/run/user/$(id -u)/gvfs/</code>{' '}
|
||||||
|
or mount with davfs2 instead (the terminal tab).
|
||||||
|
</Caveat>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 18 }}>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
style={{
|
||||||
|
padding: '8px 18px', borderRadius: 8, border: 'none',
|
||||||
|
background: 'var(--color-primary)', color: '#fff',
|
||||||
|
fontSize: 13, fontWeight: 600, cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in new issue
Block a user