This commit is contained in:
May 2023-02-26 20:34:41 -08:00
parent 2a09405645
commit edb04cc3ec
24 changed files with 225 additions and 74 deletions

View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2v6a2 2 0 0 0 2 2h6v10a2 2 0 0 1-2 2h-6.81A6.5 6.5 0 0 0 4 11.498V4a2 2 0 0 1 2-2h6Z" fill="#DDDDDD"/><path d="M13.5 2.5V8a.5.5 0 0 0 .5.5h5.5l-6-6ZM6.5 12a5.5 5.5 0 1 0 0 11 5.5 5.5 0 0 0 0-11Zm2.478 3.731-1.77 1.77 1.77 1.769a.5.5 0 1 1-.707.707l-1.77-1.77-1.769 1.768a.5.5 0 1 1-.707-.708L5.794 17.5 4.025 15.73a.5.5 0 1 1 .707-.707l1.77 1.769 1.77-1.769a.5.5 0 0 1 .706.707Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 510 B

View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M14 14.05V14H4.253a2.249 2.249 0 0 0-2.25 2.25v.919c0 .572.18 1.13.511 1.596C4.056 20.929 6.58 22 10 22c.715 0 1.39-.046 2.026-.14A2.51 2.51 0 0 1 12 21.5v-5a2.5 2.5 0 0 1 2-2.45ZM10 2.005a5 5 0 1 1 0 10 5 5 0 0 1 0-10ZM15 15v-1a2.5 2.5 0 0 1 5 0v1h.5a1.5 1.5 0 0 1 1.5 1.5v5a1.5 1.5 0 0 1-1.5 1.5h-6a1.5 1.5 0 0 1-1.5-1.5v-5a1.5 1.5 0 0 1 1.5-1.5h.5Zm1.5-1v1h2v-1a1 1 0 1 0-2 0Zm2 5a1 1 0 1 0-2 0 1 1 0 0 0 2 0Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 540 B

View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12.022 14A6.47 6.47 0 0 0 11 17.5c0 1.63.6 3.12 1.592 4.261-.796.16-1.66.24-2.592.24-3.42 0-5.944-1.072-7.486-3.237a2.75 2.75 0 0 1-.51-1.595v-.92a2.249 2.249 0 0 1 2.249-2.25h7.77Zm5.478-2a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11Zm0 7.75a.625.625 0 1 0 0 1.25.625.625 0 0 0 0-1.25Zm0-5.876c-1.048 0-1.864.817-1.853 1.954a.5.5 0 1 0 1-.01c-.006-.579.36-.944.853-.944.473 0 .854.392.854.95 0 .192-.056.342-.224.56l-.094.117-.1.113-.265.29-.136.157c-.384.457-.535.793-.535 1.31a.5.5 0 1 0 1 0c0-.203.059-.359.239-.59l.085-.104.1-.116.267-.29.134-.155c.378-.45.529-.783.529-1.293 0-1.103-.823-1.95-1.854-1.95ZM10 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 772 B

View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11 15c0-.35.06-.687.17-1H4.253a2.249 2.249 0 0 0-2.249 2.249v.92c0 .572.179 1.13.51 1.596C4.057 20.929 6.58 22 10 22c.397 0 .783-.014 1.156-.043A2.997 2.997 0 0 1 11 21v-6ZM10 2.005a5 5 0 1 1 0 10 5 5 0 0 1 0-10ZM12 15a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2v-6Zm2.5 1a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6Zm0 3a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 496 B

1
assets/icons/delete.svg Normal file
View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m4.21 4.387.083-.094a1 1 0 0 1 1.32-.083l.094.083L12 10.585l6.293-6.292a1 1 0 1 1 1.414 1.414L13.415 12l6.292 6.293a1 1 0 0 1 .083 1.32l-.083.094a1 1 0 0 1-1.32.083l-.094-.083L12 13.415l-6.293 6.292a1 1 0 0 1-1.414-1.414L10.585 12 4.293 5.707a1 1 0 0 1-.083-1.32l.083-.094-.083.094Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 410 B

1
assets/icons/link.svg Normal file
View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.502 3.003a3.5 3.5 0 0 0-3.5 3.5v6a3.5 3.5 0 0 0 3.5 3.5H7v-2h-.497a1.5 1.5 0 0 1-1.5-1.5v-6a1.5 1.5 0 0 1 1.5-1.5h6a1.5 1.5 0 0 1 1.5 1.5v6a1.5 1.5 0 0 1-1.5 1.5h-1.506v2h1.506a3.5 3.5 0 0 0 3.5-3.5v-6a3.5 3.5 0 0 0-3.5-3.5h-6Z" fill="#DDDDDD"/><path d="M10 11.5a1.5 1.5 0 0 1 1.5-1.5h1.499V8H11.5A3.5 3.5 0 0 0 8 11.5v6a3.5 3.5 0 0 0 3.5 3.5h6a3.5 3.5 0 0 0 3.5-3.5v-6A3.5 3.5 0 0 0 17.5 8h-.495v2h.495a1.5 1.5 0 0 1 1.5 1.5v6a1.5 1.5 0 0 1-1.5 1.5h-6a1.5 1.5 0 0 1-1.5-1.5v-6Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 609 B

View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7.207 2.543a1 1 0 0 1 0 1.414L5.414 5.75h7.836a8 8 0 1 1-8 8 1 1 0 1 1 2 0 6 6 0 1 0 6-6H5.414l1.793 1.793a1 1 0 0 1-1.414 1.414l-3.5-3.5a1 1 0 0 1 0-1.414l3.5-3.5a1 1 0 0 1 1.414 0Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 311 B

1
assets/icons/person.svg Normal file
View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17.754 14a2.249 2.249 0 0 1 2.25 2.249v.918a2.75 2.75 0 0 1-.513 1.599C17.945 20.929 15.42 22 12 22c-3.422 0-5.945-1.072-7.487-3.237a2.75 2.75 0 0 1-.51-1.595v-.92a2.249 2.249 0 0 1 2.249-2.25h11.501ZM12 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 369 B

1
assets/icons/private.svg Normal file
View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M2.22 2.22a.75.75 0 0 0-.073.976l.073.084 4.034 4.035a9.986 9.986 0 0 0-3.955 5.75.75.75 0 0 0 1.455.364 8.49 8.49 0 0 1 3.58-5.034l1.81 1.81A4 4 0 0 0 14.8 15.86l5.919 5.92a.75.75 0 0 0 1.133-.977l-.073-.084-6.113-6.114.001-.002-6.95-6.946.002-.002-1.133-1.13L3.28 2.22a.75.75 0 0 0-1.06 0ZM12 5.5c-1 0-1.97.148-2.889.425l1.237 1.236a8.503 8.503 0 0 1 9.899 6.272.75.75 0 0 0 1.455-.363A10.003 10.003 0 0 0 12 5.5Zm.195 3.51 3.801 3.8a4.003 4.003 0 0 0-3.801-3.8Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 592 B

1
assets/icons/public.svg Normal file
View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 9.005a4 4 0 1 1 0 8 4 4 0 0 1 0-8ZM12 5.5c4.613 0 8.596 3.15 9.701 7.564a.75.75 0 1 1-1.455.365 8.503 8.503 0 0 0-16.493.004.75.75 0 0 1-1.455-.363A10.003 10.003 0 0 1 12 5.5Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 307 B

1
assets/icons/tag.svg Normal file
View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19.75 2A2.25 2.25 0 0 1 22 4.25v5.462a3.25 3.25 0 0 1-.952 2.298l-8.5 8.503a3.255 3.255 0 0 1-4.597.001L3.489 16.06a3.25 3.25 0 0 1-.003-4.596l8.5-8.51A3.25 3.25 0 0 1 14.284 2h5.465ZM17 5.502a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 358 B

View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19.75 2A2.25 2.25 0 0 1 22 4.25v5.462a3.25 3.25 0 0 1-.952 2.298l-.026.026a6.5 6.5 0 0 0-9.028 8.92 3.256 3.256 0 0 1-4.043-.442L3.489 16.06a3.25 3.25 0 0 1-.004-4.596l8.5-8.51a3.25 3.25 0 0 1 2.3-.953h5.465ZM17 5.502a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3ZM23 17.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0Zm-7.147-2.354a.5.5 0 0 0-.707.708l1.647 1.646-1.647 1.646a.5.5 0 0 0 .707.708l1.647-1.647 1.646 1.647a.5.5 0 0 0 .707-.708L18.207 17.5l1.646-1.646a.5.5 0 0 0-.707-.708L17.5 16.793l-1.647-1.647Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 623 B

1
assets/icons/update.svg Normal file
View file

@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22 12.001c0-5.523-4.476-10-10-10-5.522 0-10 4.477-10 10s4.478 10 10 10c5.524 0 10-4.477 10-10Zm-14.53.28a.75.75 0 0 1-.073-.976l.073-.085 4-4a.75.75 0 0 1 .977-.073l.085.073 4 4.001a.75.75 0 0 1-.977 1.133l-.084-.072-2.72-2.722v6.691a.75.75 0 0 1-.649.744L12 17a.75.75 0 0 1-.743-.648l-.007-.102v-6.69l-2.72 2.72a.75.75 0 0 1-.976.073l-.084-.073Z" fill="#DDDDDD"/></svg>

After

Width:  |  Height:  |  Size: 475 B

View file

@ -1,24 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>monofile</title>
<meta name="og:site_name" content="monofile $Version">
<meta name="title" content="$CollectionName">
<meta name="description" content="$CollectionId - $Managers manager(s), $Files file(s)">
<!-- downloads.css is good eenough for this -->
<link
rel="stylesheet"
href="/static/style/downloads.css"
>
</head>
<body>
</body>
</html>

View file

@ -8,9 +8,9 @@
<!--metaTags--> <!--metaTags-->
<meta name="og:site_name" content="monofile $Version"> <meta name="og:site_name" content="$Uploader">
<meta name="title" content="$FileName"> <meta name="title" content="$FileName">
<meta name="description" content="$FileSize file on monofile, the Discord-based file sharing service"> <meta name="description" content="$FileSize file on monofile $Version, the Discord-based file sharing service">
<link <link
rel="stylesheet" rel="stylesheet"
@ -24,10 +24,10 @@
$FileName $FileName
</h1> </h1>
<p style="color:#999999"> <p style="color:#999999">
<span class="number">$FileSize</span>&nbsp;&nbsp;&nbsp;&nbsp;file id <span class="number">$FileId</span> <span class="number">$FileSize</span>&nbsp;&nbsp;&nbsp;&nbsp;uploaded by <span class="number">$Uploader</span>
</p> </p>
<!-- todo: previews --> <!--preview-->
<button style="position:relative;width:100%;top:10px;"> <button style="position:relative;width:100%;top:10px;">
<a id="dlbtn" href="/file/$FileId" download="$FileName" style="position:absolute;left:0px;top:0px;height:100%;width:100%;"></a> <a id="dlbtn" href="/file/$FileId" download="$FileName" style="position:absolute;left:0px;top:0px;height:100%;width:100%;"></a>

View file

@ -13,17 +13,5 @@ export default [
resolve(), resolve(),
svelte({}) svelte({})
] ]
},
{
input: "src/client/collection.js",
output: {
file: 'out/client/collection.js',
format: 'esm',
sourcemap:true
},
plugins: [
resolve(),
svelte({})
]
} }
] ]

View file

@ -1,5 +0,0 @@
import App from "../svelte/Collections.svelte"
new App({
target: document.body
})

View file

@ -143,10 +143,23 @@ app.get("/download/:fileId",(req,res) => {
? `<meta name="og:image" content="https://${req.headers.host}/file/${req.params.fileId}" />` ? `<meta name="og:image" content="https://${req.headers.host}/file/${req.params.fileId}" />`
: ( : (
file.mime.startsWith("video/") file.mime.startsWith("video/")
? `<meta name="og:video:url" content="https://${req.headers.host}/file/${req.params.fileId}" />\n<meta name="og:video:type" content="${file.mime.replace(/\"/g,"")}">` ? `<meta name="og:video" content="https://${req.headers.host}/file/${req.params.fileId}" />
<meta name="og:video:url" content="https://${req.headers.host}/file/${req.params.fileId}" />
<meta name="og:video:secure_url" content="https://${req.headers.host}/file/${req.params.fileId}">
<meta name="og:video:type" content="${file.mime.replace(/\"/g,"")}">`
: "" : ""
) )
) )
.replace(/\<\!\-\-preview\-\-\>/g,
file.mime.startsWith("image/")
? `<div style="min-height:10px"></div><img src="http${req.secure ? "s" :""}://${req.headers.host}/file/${req.params.fileId}" />`
: (
file.mime.startsWith("video/")
? `<div style="min-height:10px"></div><video src="http${req.secure ? "s" :""}://${req.headers.host}/file/${req.params.fileId}" controls></video>`
: ""
)
)
.replace(/\$Uploader/g,file.anonymous||!file.owner ? "Anonymous" : `@${Accounts.getFromId(file.owner)?.username || "Deleted User"}`)
) )
}) })
} else { } else {

View file

@ -21,7 +21,8 @@ export interface FileUploadSettings {
name?: string, name?: string,
mime: string, mime: string,
uploadId?: string, uploadId?: string,
owner?:string owner?:string,
anonymous?:boolean
} }
export interface Configuration { export interface Configuration {
@ -43,7 +44,9 @@ export interface FilePointer {
mime:string, mime:string,
messageids:string[], messageids:string[],
owner?:string, owner?:string,
sizeInBytes?:number sizeInBytes?:number,
tag?:string,
anonymous?:boolean
} }
export interface StatusCodeError { export interface StatusCodeError {
@ -181,7 +184,8 @@ export default class Files {
mime:settings.mime, mime:settings.mime,
sizeInBytes:fBuffer.byteLength, sizeInBytes:fBuffer.byteLength,
owner:settings.owner owner:settings.owner,
anonymous: typeof settings.anonymous == "boolean" ? settings.anonymous : false
} }
)) ))
}) })
@ -258,6 +262,9 @@ export default class Files {
unlink(uploadId:string):Promise<void> { unlink(uploadId:string):Promise<void> {
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
let tmp = this.files[uploadId]; let tmp = this.files[uploadId];
if (tmp.owner) {
files.deindex(tmp.owner,uploadId)
}
delete this.files[uploadId]; delete this.files[uploadId];
writeFile(process.cwd()+"/.data/files.json",JSON.stringify(this.files),(err) => { writeFile(process.cwd()+"/.data/files.json",JSON.stringify(this.files),(err) => {
if (err) { if (err) {

View file

@ -123,6 +123,52 @@ authRoutes.post("/logout", (req,res) => {
res.send("logged out") res.send("logged out")
}) })
authRoutes.post("/change_password", (req,res) => {
let acc = Accounts.getFromToken(req.cookies.auth)
if (!acc) {
ServeError(res, 401, "not logged in")
return
}
let body:{[key:string]:any}
try {
body = JSON.parse(req.body)
} catch {
ServeError(res,400,"bad request")
return
}
if (body.password.length < 8) {
ServeError(res,400,"password must be 8 characters or longer")
return
}
let accId = acc.id
Accounts.password.set(accId,req.body.password)
auth.AuthTokens.filter(e => e.account == accId).forEach((v) => {
auth.invalidate(v.token)
})
res.send("password changed - logged out all sessions")
})
authRoutes.post("/logout_sessions", (req,res) => {
let acc = Accounts.getFromToken(req.cookies.auth)
if (!acc) {
ServeError(res, 401, "not logged in")
return
}
let accId = acc.id
auth.AuthTokens.filter(e => e.account == accId).forEach((v) => {
auth.invalidate(v.token)
})
res.send("logged out all sessions")
})
authRoutes.get("/me", (req,res) => { authRoutes.get("/me", (req,res) => {
if (!auth.validate(req.cookies.auth)) { if (!auth.validate(req.cookies.auth)) {
@ -133,5 +179,12 @@ authRoutes.get("/me", (req,res) => {
// lazy rn so // lazy rn so
let acc = Accounts.getFromToken(req.cookies.auth) let acc = Accounts.getFromToken(req.cookies.auth)
res.send(acc) if (acc) {
let accId = acc.id
res.send({
...acc,
sessionCount: auth.AuthTokens.filter(e => e.account == accId && e.expire > Date.now()).length,
sessionExpires: auth.AuthTokens.find(e => e.token == req.cookies.auth)?.expire
})
}
}) })

View file

@ -143,14 +143,27 @@
top:10px; top:10px;
width:calc( 100% - 20px ); width:calc( 100% - 20px );
height:calc( 100% - 20px ); height:calc( 100% - 20px );
overflow-y:auto;
h1 { h1 {
font-weight:600; font-weight:600;
font-size:20px; font-size:20px;
color: #AAAAAA; color: #AAAAAA;
@media screen and (max-width:500px) {
font-size:24px;
}
.monospace {
font-size:18px;
@media screen and (max-width:500px) {
font-size:22px;
}
}
} }
.accountOptions { .accountOptions {
button { button {
position:relative; position:relative;
width:100%; width:100%;
@ -169,12 +182,18 @@
} }
p { p {
text-align:left;
position:absolute; position:absolute;
top:50%; top:50%;
left:50px; left:50px;
color:#DDDDDD; color:#DDDDDD;
transform:translateY(-50%); transform:translateY(-50%);
font-size:14px; font-size:14px;
span {
color:#777777;
font-size:12px;
}
} }
&:hover { &:hover {
@ -182,6 +201,38 @@
background-color: #252525; background-color: #252525;
} }
@media screen and (max-width:500px) {
height:70px;
p {
font-size:16px;
left:70px;
span {
font-size:14px;
}
}
img {
width:30px;
height:30px;
left:20px;
}
}
}
.category {
border-bottom: 1px solid #AAAAAA;
p {
color: #AAAAAA;
font-size: 14px;
margin: 10px 0px 3px 0px;
@media screen and (max-width:500px) {
font-size:16px;
}
}
} }
} }
} }

View file

@ -18,3 +18,9 @@
background-image: linear-gradient(#303030,base.$Background); background-image: linear-gradient(#303030,base.$Background);
} }
} }
#uploadWindow {
img, video {
width:100%;
}
}

View file

@ -1,12 +0,0 @@
<script>
import { onMount } from "svelte";
let collection = {}
</script>
<div id="appContent">
<div id="uploadWindow">
<h2>{collection.name}</h2>
<p><span class="number">{collection.id}</span>&nbsp;&nbsp—&nbsp;&nbsp;by <strong>@{collection.owner}</strong></p>
</div>
</div>

View file

@ -36,11 +36,10 @@
message: res.statusText message: res.statusText
} }
}) })
} else {
authError = null, username = "", password = "";
fetchAccountData();
} }
fetchAccountData();
}).catch(() => {}) }).catch(() => {})
} }
@ -106,26 +105,88 @@
<div class="loggedIn" transition:fade={{duration:200}}> <div class="loggedIn" transition:fade={{duration:200}}>
<h1> <h1>
Hey there, <span class="monospace" style:font-size="18px">@{$account.username}</span> Hey there, <span class="monospace">@{$account.username}</span>
</h1> </h1>
<div style:min-height="10px" style:border-bottom="1px solid #AAAAAA" />
<div class="accountOptions"> <div class="accountOptions">
<div class="category">
<p>Account</p>
</div>
<button> <button>
<img src="/static/assets/icons/change_password.svg" alt="change password"> <img src="/static/assets/icons/change_username.svg" alt="change username">
<p>Change password</p> <p>Change username</p>
</button> </button>
<button> <button>
<img src="/static/assets/icons/delete_account.svg" alt="delete account"> <img src="/static/assets/icons/change_password.svg" alt="change password">
<p>Delete account</p> <p>Change password<span><br />You will be logged out</span></p>
</button>
{#if !$account.admin}
<button>
<img src="/static/assets/icons/delete_account.svg" alt="delete account">
<p>Delete account</p>
</button>
{/if}
<div class="category">
<p>Uploads</p>
</div>
<button>
<img src="/static/assets/icons/public.svg" alt="public">
<p>Default file visibility<span><br />Uploads will be <strong>public</strong> by default</span></p>
</button>
<button>
<img src="/static/assets/icons/update.svg" alt="update">
<p>Make all of my files public<span><br />Matches your default file visibility</p>
</button>
<div class="category">
<p>Sessions</p>
</div>
<button on:click={() => fetch(`/auth/logout_sessions`,{method:"POST"}).then(() => fetchAccountData())}>
<img src="/static/assets/icons/logout_all.svg" alt="logout_all">
<p>Log out all sessions<span><br />{$account.sessionCount} session(s) active</span></p>
</button> </button>
<button on:click={() => fetch(`/auth/logout`,{method:"POST"}).then(() => fetchAccountData())}> <button on:click={() => fetch(`/auth/logout`,{method:"POST"}).then(() => fetchAccountData())}>
<img src="/static/assets/icons/logout.svg" alt="logout"> <img src="/static/assets/icons/logout.svg" alt="logout">
<p>Log out</p> <p>Log out<span><br />Session expires {new Date($account.sessionExpires).toLocaleDateString()}</span></p>
</button> </button>
{#if $account.admin}
<div class="category">
<p>Admin</p>
</div>
<button>
<img src="/static/assets/icons/delete_account.svg" alt="delete account">
<p>Delete user account</p>
</button>
<button>
<img src="/static/assets/icons/change_password.svg" alt="change password">
<p>Change user password</p>
</button>
<button>
<img src="/static/assets/icons/admin/elevate_user.svg" alt="elevate account">
<p>Elevate account to admin</p>
</button>
<button>
<img src="/static/assets/icons/admin/delete_file.svg" alt="delete file">
<p>Delete file</p>
</button>
{/if}
<p style="font-size:12px;color:#AAAAAA;text-align:center;" class="monospace"><br />{$account.id}</p>
</div> </div>
</div> </div>