fix: admin terminal + missing icons

scarlett
NGPixel 1 month ago
parent 2acd026c43
commit e393e720fd
No known key found for this signature in database

@ -0,0 +1,66 @@
import type { FastifyInstance, FastifyRequest } from 'fastify'
import type { WebSocket } from 'ws'
/**
* _terminal Routes
*
* The websocket behind the admin area's terminal view, which streams this instance's log lines to a
* browser as they are written. Read-only: nothing a client sends is looked at, and the socket carries
* exactly what `core/logger.ts` hands to `console.log`, formatted string and all — including the ANSI
* colours, which xterm renders.
*
* Only this instance's own main thread is on that stream. Worker threads build their own logger
* (`worker.ts`), and other instances write to their own consoles, so a clustered deployment shows the
* terminal of whichever instance the socket happened to land on.
*
* Log lines quote paths, e-mail addresses and query failures, so the handshake takes `manage:system`
* — the same permission as the rest of the system views, and never granted to a group by accident.
*/
/**
* How much unsent traffic a client may accumulate before its stream starts skipping lines.
*
* A browser that has stopped reading — a backgrounded tab on a slow link, most likely — would
* otherwise have the server hold every line since it stalled. Dropping is right here: the terminal is
* a live view of what is happening now, not a transcript that has to be complete.
*/
const MAX_BUFFERED = 1048576 // 1mb
async function routes(app: FastifyInstance) {
app.get(
'/logs',
{ websocket: true, schema: { hide: true } },
(socket: WebSocket, req: FastifyRequest) => {
/*
Refusals close the socket with a code in the private 4000 range, where the browser hands both
code and reason to the page — which is how the terminal can print why it was turned away and
know not to offer a reconnect. See `pages/AdminTerminal.vue`.
*/
if (!req.session?.authenticated) {
return socket.close(4401, 'Authentication is required')
}
if (!req.session.permissions?.includes('manage:system')) {
return socket.close(4403, 'You are not allowed to read the server logs')
}
const send = (line: string) => {
if (socket.readyState !== socket.OPEN || socket.bufferedAmount > MAX_BUFFERED) {
return
}
socket.send(line)
}
// -> A terminal that opens onto an idle server would otherwise sit empty and look broken
for (const line of WIKI.logger.backlog()) {
send(line)
}
WIKI.logger.ws.on('log', send)
socket.on('close', () => {
WIKI.logger.ws.off('log', send)
})
}
)
}
export default routes

@ -5,6 +5,12 @@ export type LogLevel = 'error' | 'warn' | 'info' | 'debug'
export type IgnoredLogLevel = 'verbose' | 'silly'
export type LogFn = (...args: unknown[]) => void
/**
* Formatted lines kept in memory, replayed to an admin terminal the moment it connects
* (`controllers/terminal.ts`). Enough to see how the instance got to where it is, not a log file.
*/
const BACKLOG_SIZE = 100
const LEVELS: LogLevel[] = ['error', 'warn', 'info', 'debug']
const LEVELSIGNORED: IgnoredLogLevel[] = ['verbose', 'silly']
const LEVELCOLORS: Record<LogLevel, 'red' | 'yellow' | 'green' | 'cyan'> = {
@ -18,6 +24,7 @@ class Logger extends EventEmitter {
// -> Assigned dynamically in init(). `declare` keeps these type-only so that no class field is
// emitted, leaving the runtime shape of the instance untouched.
declare ws: EventEmitter
declare backlog: () => string[]
declare error: LogFn
declare warn: LogFn
declare info: LogFn
@ -32,8 +39,13 @@ export default {
const primaryLogger = new Logger()
let ignoreNextLevels = false
const backlog: string[] = []
primaryLogger.ws = new EventEmitter()
// -> One listener per connected admin terminal, so the default cap of 10 is a leak warning rather
// than a limit worth respecting
primaryLogger.ws.setMaxListeners(0)
primaryLogger.backlog = () => [...backlog]
LEVELS.forEach((lvl) => {
primaryLogger[lvl] = (...args: unknown[]) => {
@ -58,6 +70,11 @@ export default {
}
console.log(formatted)
backlog.push(formatted)
if (backlog.length > BACKLOG_SIZE) {
backlog.shift()
}
primaryLogger.ws.emit('log', formatted)
})
}

@ -63,6 +63,7 @@ const SERVER_ROUTE_SEGMENTS = new Set([
'_icons',
'_render',
'_site',
'_terminal',
'_thumb',
'_user'
])
@ -273,9 +274,10 @@ async function initHTTPServer() {
app.register(fastifySensible)
app.register(fastifyCompress, { global: true })
/*
Websocket upgrades, for live collaborative editing (`controllers/collab.ts`). Registered on the
root instance because the upgrade handler is installed on the HTTP server itself, and before the
routes below because a route declaring `websocket: true` needs it already there.
Websocket upgrades, for live collaborative editing (`controllers/collab.ts`) and the admin
terminal's log stream (`controllers/terminal.ts`). Registered on the root instance because the
upgrade handler is installed on the HTTP server itself, and before the routes below because a
route declaring `websocket: true` needs it already there.
`maxPayload` bounds a single frame: these carry keystrokes and cursor positions, and the largest
legitimate one is a client handing over a document it edited while offline.
@ -666,6 +668,7 @@ async function initHTTPServer() {
app.register(import('./controllers/site.ts'), { prefix: '/_site' })
app.register(import('./controllers/icons.ts'), { prefix: '/_icons' })
app.register(import('./controllers/render.ts'), { prefix: '/_render' })
app.register(import('./controllers/terminal.ts'), { prefix: '/_terminal' })
app.register(import('./controllers/thumb.ts'), { prefix: '/_thumb' })
app.register(import('./controllers/user.ts'), { prefix: '/_user' })

@ -11,6 +11,7 @@
"@simplewebauthn/browser": "13.3.0",
"@tailwindcss/vite": "4.3.3",
"@twemoji/api": "17.0.2",
"@xterm/addon-fit": "0.11.0",
"@xterm/xterm": "6.0.0",
"browser-fs-access": "0.38.0",
"clipboard": "2.0.11",
@ -40,7 +41,6 @@
"pinia": "4.0.2",
"semver": "7.8.5",
"slugify": "1.6.9",
"socket.io-client": "4.8.3",
"sortablejs": "1.15.7",
"sortablejs-vue3": "1.3.0",
"tailwindcss": "4.3.3",
@ -2067,12 +2067,6 @@
"integrity": "sha512-BE/UWv6FOToAdVk0EokzkqQQDOWtNydYlY6+OrmiZ5SCNmb41VehttboTetUM3T/fr6EAFYVXjz4My2wg230rQ==",
"license": "MIT"
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/@tailwindcss/node": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
@ -2650,6 +2644,12 @@
"integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==",
"license": "MIT"
},
"node_modules/@xterm/addon-fit": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz",
"integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==",
"license": "MIT"
},
"node_modules/@xterm/xterm": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz",
@ -2869,6 +2869,7 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@ -2956,28 +2957,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/engine.io-client": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz",
"integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.18.3",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/enhanced-resolve": {
"version": "5.24.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.3.tgz",
@ -3949,6 +3928,7 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/muggle-string": {
@ -4416,34 +4396,6 @@
"node": ">=8.0.0"
}
},
"node_modules/socket.io-client": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
"integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1",
"engine.io-client": "~6.6.1",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz",
"integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/sortablejs": {
"version": "1.15.7",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.7.tgz",
@ -5391,27 +5343,6 @@
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
"license": "MIT"
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/wsl-utils": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz",
@ -5429,14 +5360,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/y-monaco": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/y-monaco/-/y-monaco-0.1.6.tgz",

@ -20,6 +20,7 @@
"@simplewebauthn/browser": "13.3.0",
"@tailwindcss/vite": "4.3.3",
"@twemoji/api": "17.0.2",
"@xterm/addon-fit": "0.11.0",
"@xterm/xterm": "6.0.0",
"browser-fs-access": "0.38.0",
"clipboard": "2.0.11",
@ -49,7 +50,6 @@
"pinia": "4.0.2",
"semver": "7.8.5",
"slugify": "1.6.9",
"socket.io-client": "4.8.3",
"sortablejs": "1.15.7",
"sortablejs-vue3": "1.3.0",
"tailwindcss": "4.3.3",

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="FQ8i7cvCEIQ7zfeZdMxD3a" x1="16.803" x2="8.003" y1="32.228" y2="15.388" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#edbe00"/><stop offset="1" stop-color="#ffd000"/></linearGradient><path fill="url(#FQ8i7cvCEIQ7zfeZdMxD3a)" d="M24,44.056L5,35.611V12.389l19,7.643V44.056z"/><linearGradient id="FQ8i7cvCEIQ7zfeZdMxD3b" x1="24" x2="43" y1="28.222" y2="28.222" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f44f5a"/><stop offset=".443" stop-color="#ee3d4a"/><stop offset=".731" stop-color="#e92e3d"/><stop offset="1" stop-color="#e52030"/></linearGradient><path fill="url(#FQ8i7cvCEIQ7zfeZdMxD3b)" d="M24,44.056l19-8.444V12.389l-19,7.803V44.056z"/><linearGradient id="FQ8i7cvCEIQ7zfeZdMxD3c" x1="5" x2="43" y1="12.389" y2="12.389" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#21ad64"/><stop offset="1" stop-color="#088242"/></linearGradient><path fill="url(#FQ8i7cvCEIQ7zfeZdMxD3c)" d="M5,12.389l19-8.444l19,8.444l-19,8.444L5,12.389z"/><path fill="#fff" d="M20.451,38.349l-3.141-1.286l-0.91-3.457l-4.551-1.863l-0.901,2.715l-3.123-1.279l4.658-11.957 l3.418,1.4L20.451,38.349z M15.739,30.938l-1.374-5.223c-0.101-0.39-0.172-0.835-0.214-1.335l-0.072-0.029 c-0.029,0.394-0.104,0.766-0.223,1.117l-1.392,4.129L15.739,30.938z"/><path fill="#fff" d="M29.367,37.543V23.679l4.66-1.918c1.428-0.588,2.527-0.756,3.294-0.505 c0.768,0.252,1.151,0.892,1.151,1.924c0,0.748-0.234,1.498-0.701,2.251c-0.468,0.753-1.064,1.389-1.79,1.907v0.039 c0.911-0.253,1.638-0.188,2.183,0.194c0.544,0.382,0.817,1.008,0.817,1.878c0,1.27-0.42,2.45-1.258,3.539 c-0.84,1.09-1.985,1.934-3.437,2.532L29.367,37.543z M32.251,24.793v3.287l1.267-0.522c0.595-0.245,1.063-0.594,1.406-1.048 c0.342-0.454,0.513-0.954,0.513-1.502c0-1.018-0.702-1.238-2.107-0.66L32.251,24.793z M32.251,30.4v3.654l1.562-0.643 c0.666-0.274,1.188-0.656,1.566-1.147c0.378-0.491,0.567-1.026,0.567-1.606c0-0.555-0.186-0.913-0.558-1.076 c-0.372-0.162-0.891-0.107-1.558,0.167L32.251,30.4z"/><path fill="#fff" d="M22.207,16.998c-1.166-0.231-2.355-0.692-3.567-1.381c-1.581-0.899-2.296-1.857-2.145-2.874 c0.151-1.017,1.106-1.943,2.865-2.778c1.873-0.89,3.901-1.321,6.085-1.296c2.182,0.026,4.086,0.501,5.71,1.424 c1.006,0.573,1.71,1.123,2.111,1.653l-2.54,1.206c-0.221-0.615-0.773-1.173-1.654-1.674c-0.968-0.55-2.094-0.831-3.38-0.84 c-1.286-0.01-2.519,0.266-3.701,0.828c-1.133,0.538-1.755,1.125-1.866,1.764c-0.113,0.638,0.306,1.227,1.254,1.767 c0.905,0.515,1.984,0.868,3.237,1.058L22.207,16.998z"/></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="CvYJxEj4UN6unvuz0~iWMa" x1="45.027" x2="38.41" y1="27" y2="27" gradientTransform="matrix(-1 0 0 1 48 0)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#6d7479"/><stop offset=".425" stop-color="#565c60"/><stop offset="1" stop-color="#323538"/></linearGradient><path fill="url(#CvYJxEj4UN6unvuz0~iWMa)" d="M4,28h9c0.552,0,1-0.448,1-1s-0.448-1-1-1H4c-0.552,0-1,0.448-1,1S3.448,28,4,28z"/><linearGradient id="CvYJxEj4UN6unvuz0~iWMb" x1="76.027" x2="69.41" y1="29" y2="29" gradientTransform="matrix(1 0 0 -1 -31 56)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#6d7479"/><stop offset=".425" stop-color="#565c60"/><stop offset="1" stop-color="#323538"/></linearGradient><path fill="url(#CvYJxEj4UN6unvuz0~iWMb)" d="M44,26h-9c-0.552,0-1,0.448-1,1s0.448,1,1,1h9c0.552,0,1-0.448,1-1S44.552,26,44,26z"/><linearGradient id="CvYJxEj4UN6unvuz0~iWMc" x1="42.928" x2="37.665" y1="15.789" y2="18.01" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#6d7479"/><stop offset=".425" stop-color="#565c60"/><stop offset="1" stop-color="#323538"/></linearGradient><path fill="url(#CvYJxEj4UN6unvuz0~iWMc)" d="M42.422,16.907l-8.157,3.804c-0.501,0.233-1.096,0.017-1.329-0.484 c-0.233-0.501-0.017-1.096,0.484-1.329l8.157-3.804c0.501-0.233,1.096-0.017,1.329,0.484S42.923,16.673,42.422,16.907z"/><linearGradient id="CvYJxEj4UN6unvuz0~iWMd" x1="42.067" x2="37.625" y1="37.735" y2="35.176" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#6d7479"/><stop offset=".425" stop-color="#565c60"/><stop offset="1" stop-color="#323538"/></linearGradient><path fill="url(#CvYJxEj4UN6unvuz0~iWMd)" d="M41.639,36.796l-7.372-5.162c-0.452-0.317-1.076-0.207-1.393,0.246s-0.207,1.076,0.246,1.393 l7.372,5.162c0.452,0.317,1.076,0.207,1.393-0.246S42.091,37.113,41.639,36.796z"/><linearGradient id="CvYJxEj4UN6unvuz0~iWMe" x1="42.928" x2="37.665" y1="15.789" y2="18.01" gradientTransform="matrix(-1 0 0 1 48 0)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#6d7479"/><stop offset=".425" stop-color="#565c60"/><stop offset="1" stop-color="#323538"/></linearGradient><path fill="url(#CvYJxEj4UN6unvuz0~iWMe)" d="M5.578,16.907l8.157,3.804c0.501,0.233,1.096,0.017,1.329-0.484 c0.233-0.501,0.017-1.096-0.484-1.329l-8.157-3.804c-0.501-0.233-1.096-0.017-1.329,0.484C4.861,16.078,5.077,16.673,5.578,16.907z"/><linearGradient id="CvYJxEj4UN6unvuz0~iWMf" x1="42.067" x2="37.625" y1="37.735" y2="35.176" gradientTransform="matrix(-1 0 0 1 48 0)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#6d7479"/><stop offset=".425" stop-color="#565c60"/><stop offset="1" stop-color="#323538"/></linearGradient><path fill="url(#CvYJxEj4UN6unvuz0~iWMf)" d="M6.361,36.796l7.372-5.162c0.452-0.317,1.076-0.207,1.393,0.246s0.207,1.076-0.246,1.393 l-7.372,5.162c-0.452,0.317-1.076,0.207-1.393-0.246C5.799,37.737,5.909,37.113,6.361,36.796z"/><linearGradient id="CvYJxEj4UN6unvuz0~iWMg" x1="13.057" x2="16.216" y1="3.712" y2="8.72" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#6d7479"/><stop offset=".425" stop-color="#565c60"/><stop offset="1" stop-color="#323538"/></linearGradient><path fill="url(#CvYJxEj4UN6unvuz0~iWMg)" d="M17.086,10.5l-4.793-4.793c-0.39-0.39-0.39-1.024,0-1.414l0,0c0.39-0.39,1.024-0.39,1.414,0 L18.5,9.086L17.086,10.5z"/><linearGradient id="CvYJxEj4UN6unvuz0~iWMh" x1="35.33" x2="31.616" y1="4.125" y2="8.586" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#6d7479"/><stop offset=".425" stop-color="#565c60"/><stop offset="1" stop-color="#323538"/></linearGradient><path fill="url(#CvYJxEj4UN6unvuz0~iWMh)" d="M30.914,10.5l4.793-4.793c0.39-0.39,0.39-1.024,0-1.414v0c-0.39-0.39-1.024-0.39-1.414,0 L29.5,9.086L30.914,10.5z"/><linearGradient id="CvYJxEj4UN6unvuz0~iWMi" x1="24" x2="24" y1="13.13" y2="43.661" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fed100"/><stop offset="1" stop-color="#e36001"/></linearGradient><path fill="url(#CvYJxEj4UN6unvuz0~iWMi)" d="M12.375,13C10.267,16.272,9,20.448,9,25c0,10.493,6.716,19,15,19s15-8.507,15-19 c0-4.552-1.267-8.728-3.375-12H12.375z"/><linearGradient id="CvYJxEj4UN6unvuz0~iWMj" x1="24" x2="24" y1="15.244" y2="44.605" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#dbb500"/><stop offset=".998" stop-color="#b54c01"/></linearGradient><path fill="url(#CvYJxEj4UN6unvuz0~iWMj)" d="M23,43.936C23.332,43.964,23.662,44,24,44s0.668-0.036,1-0.064V15.5h-2V43.936z"/><linearGradient id="CvYJxEj4UN6unvuz0~iWMk" x1="24" x2="24" y1="6.16" y2="17.316" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#6d7479"/><stop offset=".512" stop-color="#4d5256"/><stop offset="1" stop-color="#323538"/></linearGradient><path fill="url(#CvYJxEj4UN6unvuz0~iWMk)" d="M24,17c4.483,0,8.65-1.161,12.128-3.149C33.401,9.101,28.991,6,24,6 s-9.401,3.101-12.128,7.851C15.35,15.839,19.517,17,24,17z"/></svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#00c92c" d="M40,42h-3V28h5v12C42,41.1,41.1,42,40,42z"/><linearGradient id="UlIBuNxA3B~16Hyc2onYta" x1="97.915" x2="82.729" y1="-3180.451" y2="-3180.558" gradientTransform="matrix(0 -1 -1 0 -3141 115)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fede00"/><stop offset="1" stop-color="#f5aa00"/></linearGradient><path fill="url(#UlIBuNxA3B~16Hyc2onYta)" d="M40,31h-3V17h5v12C42,30.1,41.1,31,40,31z"/><linearGradient id="UlIBuNxA3B~16Hyc2onYtb" x1="108.915" x2="93.728" y1="-3180.444" y2="-3180.551" gradientTransform="matrix(0 -1 -1 0 -3141 115)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f44f5a"/><stop offset=".443" stop-color="#ee3d4a"/><stop offset="1" stop-color="#e52030"/></linearGradient><path fill="url(#UlIBuNxA3B~16Hyc2onYtb)" d="M40,20h-3V6h3c1.1,0,2,0.9,2,2v10C42,19.1,41.1,20,40,20z"/><linearGradient id="UlIBuNxA3B~16Hyc2onYtc" x1="110.794" x2="67.294" y1="-3167.353" y2="-3167.66" gradientTransform="matrix(0 -1 -1 0 -3141 115)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#28afea"/><stop offset="1" stop-color="#047ed6"/></linearGradient><path fill="url(#UlIBuNxA3B~16Hyc2onYtc)" d="M37,44H14V4h23c1.1,0,2,0.9,2,2v36C39,43.1,38.1,44,37,44z"/><linearGradient id="UlIBuNxA3B~16Hyc2onYtd" x1="106.415" x2="67.119" y1="-3152.799" y2="-3153.347" gradientTransform="matrix(0 -1 -1 0 -3141 115)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0077d2"/><stop offset="1" stop-color="#0b59a2"/></linearGradient><path fill="url(#UlIBuNxA3B~16Hyc2onYtd)" d="M10,4h6v40h-6c-1.1,0-2-0.9-2-2V6C8,4.9,8.9,4,10,4z"/><path d="M14.5,10.5L14.5,10.5c0,1.657-1.343,3-3,3H8v-6h3.5 C13.157,7.5,14.5,8.843,14.5,10.5z" opacity=".05"/><path d="M13.75,10.5L13.75,10.5c0,1.243-1.007,2.25-2.25,2.25H8v-4.5h3.5 C12.743,8.25,13.75,9.257,13.75,10.5z" opacity=".07"/><path d="M14.5,37.5L14.5,37.5c0,1.657-1.343,3-3,3H8v-6h3.5 C13.157,34.5,14.5,35.843,14.5,37.5z" opacity=".05"/><path d="M13.75,37.5L13.75,37.5c0,1.243-1.007,2.25-2.25,2.25H8v-4.5h3.5 C12.743,35.25,13.75,36.257,13.75,37.5z" opacity=".07"/><linearGradient id="UlIBuNxA3B~16Hyc2onYte" x1="6" x2="13" y1="37.5" y2="37.5" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#c8cdd1"/><stop offset="1" stop-color="#b3b3b3"/></linearGradient><path fill="url(#UlIBuNxA3B~16Hyc2onYte)" d="M13,37.5L13,37.5c0,0.828-0.672,1.5-1.5,1.5h-4C6.672,39,6,38.328,6,37.5l0,0 C6,36.672,6.672,36,7.5,36h4C12.328,36,13,36.672,13,37.5z"/><path d="M14.5,24.5L14.5,24.5c0,1.657-1.343,3-3,3H8v-6h3.5 C13.157,21.5,14.5,22.843,14.5,24.5z" opacity=".05"/><path d="M13.75,24.5L13.75,24.5c0,1.243-1.007,2.25-2.25,2.25H8v-4.5h3.5 C12.743,22.25,13.75,23.257,13.75,24.5z" opacity=".07"/><linearGradient id="UlIBuNxA3B~16Hyc2onYtf" x1="6" x2="13" y1="24.5" y2="24.5" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#c8cdd1"/><stop offset="1" stop-color="#b3b3b3"/></linearGradient><path fill="url(#UlIBuNxA3B~16Hyc2onYtf)" d="M13,24.5L13,24.5c0,0.828-0.672,1.5-1.5,1.5h-4C6.672,26,6,25.328,6,24.5l0,0 C6,23.672,6.672,23,7.5,23h4C12.328,23,13,23.672,13,24.5z"/><linearGradient id="UlIBuNxA3B~16Hyc2onYtg" x1="6" x2="13" y1="10.5" y2="10.5" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#c8cdd1"/><stop offset="1" stop-color="#b3b3b3"/></linearGradient><path fill="url(#UlIBuNxA3B~16Hyc2onYtg)" d="M13,10.5L13,10.5c0,0.828-0.672,1.5-1.5,1.5h-4C6.672,12,6,11.328,6,10.5l0,0 C6,9.672,6.672,9,7.5,9h4C12.328,9,13,9.672,13,10.5z"/><path fill="#f9f9f9" d="M34.464,21H20.536C20.24,21,20,20.776,20,20.5v-1c0-0.276,0.24-0.5,0.536-0.5h13.929 C34.76,19,35,19.224,35,19.5v1C35,20.776,34.76,21,34.464,21z"/><path fill="#f9f9f9" d="M34.464,26H20.536C20.24,26,20,25.776,20,25.5v-1c0-0.276,0.24-0.5,0.536-0.5h13.929 C34.76,24,35,24.224,35,24.5v1C35,25.776,34.76,26,34.464,26z"/></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px" baseProfile="basic"><linearGradient id="lpS7fBJRRW0qC3KsSzhFla" x1="24" x2="24" y1="5" y2="43" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#7e8b96"/><stop offset="1" stop-color="#586169"/></linearGradient><path fill="url(#lpS7fBJRRW0qC3KsSzhFla)" d="M42,8v32c0,1.657-1.343,3-3,3H9c-1.657,0-3-1.343-3-3V8c0-1.657,1.343-3,3-3h30 C40.657,5,42,6.343,42,8z"/><linearGradient id="lpS7fBJRRW0qC3KsSzhFlb" x1="24" x2="24" y1="39.574" y2="7.91" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#424b50"/><stop offset="1" stop-color="#31373b"/></linearGradient><path fill="url(#lpS7fBJRRW0qC3KsSzhFlb)" d="M38,40H10c-0.552,0-1-0.448-1-1V9 c0-0.552,0.448-1,1-1h28c0.552,0,1,0.448,1,1v30C39,39.552,38.552,40,38,40z"/><linearGradient id="lpS7fBJRRW0qC3KsSzhFlc" x1="24" x2="24" y1="107.973" y2="99.991" gradientTransform="matrix(1 0 0 -1 0 118)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#899198"/><stop offset="1" stop-color="#787e85"/></linearGradient><path fill="url(#lpS7fBJRRW0qC3KsSzhFlc)" d="M35,18H13c-1.1,0-2-0.9-2-2v-4 c0-1.1,0.9-2,2-2h22c1.1,0,2,0.9,2,2v4C37,17.1,36.1,18,35,18z"/><linearGradient id="lpS7fBJRRW0qC3KsSzhFld" x1="34.418" x2="31.536" y1="102.582" y2="105.464" gradientTransform="matrix(1 0 0 -1 0 118)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".242" stop-color="#f2f2f2"/><stop offset="1" stop-color="#ccc"/></linearGradient><path fill="url(#lpS7fBJRRW0qC3KsSzhFld)" d="M35,14c0,1.104-0.896,2-2,2s-2-0.896-2-2 s0.896-2,2-2S35,12.896,35,14z"/><linearGradient id="lpS7fBJRRW0qC3KsSzhFle" x1="32.293" x2="33.707" y1="104.707" y2="103.293" gradientTransform="matrix(1 0 0 -1 0 118)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#21ad64"/><stop offset="1" stop-color="#088242"/></linearGradient><path fill="url(#lpS7fBJRRW0qC3KsSzhFle)" d="M34,14c0,0.552-0.448,1-1,1s-1-0.448-1-1 s0.448-1,1-1S34,13.448,34,14z"/><path fill="#656c70" d="M13.5,13h15c0.276,0,0.5,0.224,0.5,0.5v1c0,0.276-0.224,0.5-0.5,0.5h-15c-0.276,0-0.5-0.224-0.5-0.5 v-1C13,13.224,13.224,13,13.5,13z"/><linearGradient id="lpS7fBJRRW0qC3KsSzhFlf" x1="24" x2="24" y1="97.973" y2="89.991" gradientTransform="matrix(1 0 0 -1 0 118)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#899198"/><stop offset="1" stop-color="#787e85"/></linearGradient><path fill="url(#lpS7fBJRRW0qC3KsSzhFlf)" d="M35,28H13c-1.1,0-2-0.9-2-2v-4 c0-1.1,0.9-2,2-2h22c1.1,0,2,0.9,2,2v4C37,27.1,36.1,28,35,28z"/><linearGradient id="lpS7fBJRRW0qC3KsSzhFlg" x1="34.418" x2="31.536" y1="92.582" y2="95.464" gradientTransform="matrix(1 0 0 -1 0 118)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".242" stop-color="#f2f2f2"/><stop offset="1" stop-color="#ccc"/></linearGradient><path fill="url(#lpS7fBJRRW0qC3KsSzhFlg)" d="M35,24c0,1.104-0.896,2-2,2s-2-0.896-2-2 s0.896-2,2-2S35,22.896,35,24z"/><path fill="#656c70" d="M13.5,23h15c0.276,0,0.5,0.224,0.5,0.5v1c0,0.276-0.224,0.5-0.5,0.5h-15c-0.276,0-0.5-0.224-0.5-0.5 v-1C13,23.224,13.224,23,13.5,23z"/><linearGradient id="lpS7fBJRRW0qC3KsSzhFlh" x1="24" x2="24" y1="87.973" y2="79.991" gradientTransform="matrix(1 0 0 -1 0 118)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#899198"/><stop offset="1" stop-color="#787e85"/></linearGradient><path fill="url(#lpS7fBJRRW0qC3KsSzhFlh)" d="M35,38H13c-1.1,0-2-0.9-2-2v-4 c0-1.1,0.9-2,2-2h22c1.1,0,2,0.9,2,2v4C37,37.1,36.1,38,35,38z"/><linearGradient id="lpS7fBJRRW0qC3KsSzhFli" x1="34.418" x2="31.536" y1="82.582" y2="85.464" gradientTransform="matrix(1 0 0 -1 0 118)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".242" stop-color="#f2f2f2"/><stop offset="1" stop-color="#ccc"/></linearGradient><path fill="url(#lpS7fBJRRW0qC3KsSzhFli)" d="M35,34c0,1.104-0.896,2-2,2s-2-0.896-2-2 s0.896-2,2-2S35,32.896,35,34z"/><linearGradient id="lpS7fBJRRW0qC3KsSzhFlj" x1="32.293" x2="33.707" y1="84.707" y2="83.293" gradientTransform="matrix(1 0 0 -1 0 118)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#21ad64"/><stop offset="1" stop-color="#088242"/></linearGradient><path fill="url(#lpS7fBJRRW0qC3KsSzhFlj)" d="M34,34c0,0.552-0.448,1-1,1s-1-0.448-1-1 s0.448-1,1-1S34,33.448,34,34z"/><path fill="#656c70" d="M13.5,33h15c0.276,0,0.5,0.224,0.5,0.5v1c0,0.276-0.224,0.5-0.5,0.5h-15c-0.276,0-0.5-0.224-0.5-0.5 v-1C13,33.224,13.224,33,13.5,33z"/><radialGradient id="lpS7fBJRRW0qC3KsSzhFlk" cx="32.374" cy="94.677" r="1.919" gradientTransform="matrix(1 0 0 -1 0 118)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fee100"/><stop offset="1" stop-color="#f7a511"/></radialGradient><path fill="url(#lpS7fBJRRW0qC3KsSzhFlk)" d="M34,24c0,0.552-0.448,1-1,1s-1-0.448-1-1 s0.448-1,1-1S34,23.448,34,24z"/></svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px" baseProfile="basic"><linearGradient id="p02sXA5NoBxd5iRdXcj0Ja" x1="24" x2="24" y1="6.955" y2="23.167" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#eba600"/><stop offset="1" stop-color="#c28200"/></linearGradient><path fill="url(#p02sXA5NoBxd5iRdXcj0Ja)" d="M24.414,10.414l-2.536-2.536C21.316,7.316,20.553,7,19.757,7H5C3.895,7,3,7.895,3,9v30 c0,1.105,0.895,2,2,2h38c1.105,0,2-0.895,2-2V13c0-1.105-0.895-2-2-2H25.828C25.298,11,24.789,10.789,24.414,10.414z"/><path fill="#f3f3f3" d="M39,30H9V16c0-0.552,0.448-1,1-1h28c0.552,0,1,0.448,1,1V30z"/><linearGradient id="p02sXA5NoBxd5iRdXcj0Jb" x1="1112.066" x2="1112.066" y1="19.228" y2="33.821" gradientTransform="matrix(-1 0 0 1 1136 0)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ffd869"/><stop offset="1" stop-color="#fec52b"/></linearGradient><path fill="url(#p02sXA5NoBxd5iRdXcj0Jb)" d="M24,23l3.854-3.854 C27.947,19.053,28.074,19,28.207,19H44.81c1.176,0,2.098,1.01,1.992,2.181l-1.636,18C45.072,40.211,44.208,41,43.174,41H4.79 c-1.019,0-1.875-0.766-1.988-1.779L1.062,23.555C1.029,23.259,1.261,23,1.559,23H24z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="4LyKu8AtUK4gmSHjQsrKja" x1="15.941" x2="35.261" y1="-158.617" y2="-188.37" gradientTransform="matrix(1 0 0 -1 0 -154)" gradientUnits="userSpaceOnUse"><stop offset=".002" stop-color="#f44f78"/><stop offset=".397" stop-color="#e4305d"/><stop offset=".763" stop-color="#d81b4a"/><stop offset="1" stop-color="#d41343"/></linearGradient><path fill="url(#4LyKu8AtUK4gmSHjQsrKja)" d="M24,12.25c0,0,4.701-5.25,10.5-5.25S45,11.765,45,17.5c0,3.555-3.501,6.999-3.501,6.999 l-15.728,15.73c-0.978,0.978-2.565,0.978-3.543,0L6.501,24.499c0,0-3.501-3.446-3.501-6.999C3,11.765,7.701,7,13.5,7 S24,12.25,24,12.25z"/></svg>

After

Width:  |  Height:  |  Size: 711 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><radialGradient id="oPXJPSjF6nlxQ7ZAIbFD6a" cx="24" cy="25" r="19" gradientTransform="matrix(1 0 0 -1 0 48)" gradientUnits="userSpaceOnUse"><stop offset=".078" stop-color="#fff"/><stop offset=".202" stop-color="#fff5a3"/><stop offset=".216" stop-color="#fff392"/><stop offset=".263" stop-color="#ffee5f"/><stop offset=".307" stop-color="#ffea37"/><stop offset=".347" stop-color="#ffe71a"/><stop offset=".382" stop-color="#ffe508"/><stop offset=".408" stop-color="#ffe402"/><stop offset=".545" stop-color="#fdde02"/><stop offset=".736" stop-color="#f9cc01"/><stop offset=".956" stop-color="#f2af00"/><stop offset="1" stop-color="#f0a800"/></radialGradient><circle cx="24" cy="23" r="19" fill="url(#oPXJPSjF6nlxQ7ZAIbFD6a)"/><radialGradient id="oPXJPSjF6nlxQ7ZAIbFD6b" cx="21.677" cy="10.537" r="10.066" gradientTransform="matrix(1 0 0 -1 0 48)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#6e757b"/><stop offset="1" stop-color="#4a4e52"/></radialGradient><path fill="url(#oPXJPSjF6nlxQ7ZAIbFD6b)" d="M29,38H19c0,0.7,0,2,0,2c0,2.8,2.2,5,5,5s5-2.2,5-5C29,40,29,38.7,29,38z"/><path fill="#fefefe" d="M34,23c0-5.5-4.5-10-10-10s-10,4.5-10,10c0,5,5,9,5,14v1h10v-1C29,32,34,28,34,23z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><linearGradient id="DIL4YXqTpzKGN6Y0goToWa" x1="2.315" x2="27.991" y1="21.22" y2="46.895" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f9f9f9"/><stop offset="1" stop-color="#c2c8cc"/></linearGradient><path fill="url(#DIL4YXqTpzKGN6Y0goToWa)" d="M12.16,21l-1.726,1.726c-0.511,0.511-0.874,1.151-1.051,1.852l-2.196,8.723L9.5,38.5 l5.183,2.304l8.727-2.197c0.701-0.177,1.341-0.54,1.853-1.051L27,35.81L12.16,21z"/><linearGradient id="DIL4YXqTpzKGN6Y0goToWb" x1="4.389" x2="11.881" y1="36.103" y2="43.595" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#787878"/><stop offset="1" stop-color="#666"/></linearGradient><path fill="url(#DIL4YXqTpzKGN6Y0goToWb)" d="M7.189,33.302l-2.117,8.396c-0.185,0.734,0.48,1.399,1.214,1.214l8.393-2.116L7.189,33.302z"/><path fill="#edbe00" d="M30.16,3L10.81,22.35C12.674,23.077,14,24.879,14,27c0,0,0,0.224,0,0.5c0,0.276,0.224,0.5,0.5,0.5 s0.5,0,0.5,0c2.761,0,5,2.239,5,5l0,0h0c0,0,0,0.224,0,0.5c0,0.276,0.224,0.5,0.5,0.5c0.276,0,0.5,0,0.5,0v0 c2.116,0,3.914,1.319,4.645,3.175L45,17.81L30.16,3z"/><linearGradient id="DIL4YXqTpzKGN6Y0goToWc" x1="25.22" x2="19.442" y1="18.489" y2="12.711" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fede00"/><stop offset="1" stop-color="#ffd000"/></linearGradient><path fill="url(#DIL4YXqTpzKGN6Y0goToWc)" d="M14.146,27.854L34.584,7.416L30.16,3L10.81,22.35C12.674,23.077,14,24.879,14,27 c0,0,0,0.224,0,0.5C14,27.638,14.056,27.763,14.146,27.854z"/><linearGradient id="DIL4YXqTpzKGN6Y0goToWd" x1="34.781" x2="14.864" y1="28.039" y2="8.123" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#eba601"/><stop offset="1" stop-color="#c18310"/></linearGradient><path fill="url(#DIL4YXqTpzKGN6Y0goToWd)" d="M20.146,33.854C20.237,33.944,20.362,34,20.5,34c0.276,0,0.5,0,0.5,0v0 c2.116,0,3.914,1.319,4.645,3.175L45,17.81l-4.409-4.401L20.146,33.854z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="DIL4YXqTpzKGN6Y0goToWa" x1="2.315" x2="27.991" y1="21.22" y2="46.895" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f9f9f9"/><stop offset="1" stop-color="#c2c8cc"/></linearGradient><path fill="url(#DIL4YXqTpzKGN6Y0goToWa)" d="M12.16,21l-1.726,1.726c-0.511,0.511-0.874,1.151-1.051,1.852l-2.196,8.723L9.5,38.5 l5.183,2.304l8.727-2.197c0.701-0.177,1.341-0.54,1.853-1.051L27,35.81L12.16,21z"/><linearGradient id="DIL4YXqTpzKGN6Y0goToWb" x1="4.389" x2="11.881" y1="36.103" y2="43.595" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#787878"/><stop offset="1" stop-color="#666"/></linearGradient><path fill="url(#DIL4YXqTpzKGN6Y0goToWb)" d="M7.189,33.302l-2.117,8.396c-0.185,0.734,0.48,1.399,1.214,1.214l8.393-2.116L7.189,33.302z"/><path fill="#edbe00" d="M30.16,3L10.81,22.35C12.674,23.077,14,24.879,14,27c0,0,0,0.224,0,0.5c0,0.276,0.224,0.5,0.5,0.5 s0.5,0,0.5,0c2.761,0,5,2.239,5,5l0,0h0c0,0,0,0.224,0,0.5c0,0.276,0.224,0.5,0.5,0.5c0.276,0,0.5,0,0.5,0v0 c2.116,0,3.914,1.319,4.645,3.175L45,17.81L30.16,3z"/><linearGradient id="DIL4YXqTpzKGN6Y0goToWc" x1="25.22" x2="19.442" y1="18.489" y2="12.711" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fede00"/><stop offset="1" stop-color="#ffd000"/></linearGradient><path fill="url(#DIL4YXqTpzKGN6Y0goToWc)" d="M14.146,27.854L34.584,7.416L30.16,3L10.81,22.35C12.674,23.077,14,24.879,14,27 c0,0,0,0.224,0,0.5C14,27.638,14.056,27.763,14.146,27.854z"/><linearGradient id="DIL4YXqTpzKGN6Y0goToWd" x1="34.781" x2="14.864" y1="28.039" y2="8.123" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#eba601"/><stop offset="1" stop-color="#c18310"/></linearGradient><path fill="url(#DIL4YXqTpzKGN6Y0goToWd)" d="M20.146,33.854C20.237,33.944,20.362,34,20.5,34c0.276,0,0.5,0,0.5,0v0 c2.116,0,3.914,1.319,4.645,3.175L45,17.81l-4.409-4.401L20.146,33.854z"/></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="H5zWGZDzzYRltSZyl6Krha" x1="10.517" x2="36.546" y1="5.342" y2="41.361" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2aa4f4"/><stop offset="1" stop-color="#007ad9"/></linearGradient><path fill="url(#H5zWGZDzzYRltSZyl6Krha)" d="M36,4H12c-1.105,0-2,0.895-2,2v36c0,1.105,0.895,2,2,2h24c1.105,0,2-0.895,2-2V6 C38,4.895,37.105,4,36,4z"/><radialGradient id="H5zWGZDzzYRltSZyl6Krhb" cx="24" cy="37" r="3" gradientUnits="userSpaceOnUse"><stop offset=".582" stop-opacity=".2"/><stop offset="1" stop-opacity="0"/></radialGradient><circle cx="24" cy="37" r="3" fill="url(#H5zWGZDzzYRltSZyl6Krhb)"/><circle cx="24" cy="37" r="2" fill="#fff"/><linearGradient id="H5zWGZDzzYRltSZyl6Krhc" x1="14" x2="34" y1="11" y2="11" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1d59b3"/><stop offset="1" stop-color="#195bbc"/></linearGradient><path fill="url(#H5zWGZDzzYRltSZyl6Krhc)" d="M33.5,14h-19c-0.276,0-0.5-0.224-0.5-0.5v-5C14,8.224,14.224,8,14.5,8h19 C33.776,8,34,8.224,34,8.5v5C34,13.776,33.776,14,33.5,14z"/><circle cx="31" cy="11" r="1" fill="#50e6ff"/><linearGradient id="H5zWGZDzzYRltSZyl6Krhd" x1="14" x2="34" y1="19" y2="19" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1d59b3"/><stop offset="1" stop-color="#195bbc"/></linearGradient><path fill="url(#H5zWGZDzzYRltSZyl6Krhd)" d="M33.5,22h-19c-0.276,0-0.5-0.224-0.5-0.5v-5c0-0.276,0.224-0.5,0.5-0.5h19 c0.276,0,0.5,0.224,0.5,0.5v5C34,21.776,33.776,22,33.5,22z"/><circle cx="31" cy="19" r="1" fill="#50e6ff"/><linearGradient id="H5zWGZDzzYRltSZyl6Krhe" x1="14" x2="34" y1="27" y2="27" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1d59b3"/><stop offset="1" stop-color="#195bbc"/></linearGradient><path fill="url(#H5zWGZDzzYRltSZyl6Krhe)" d="M33.5,30h-19c-0.276,0-0.5-0.224-0.5-0.5v-5c0-0.276,0.224-0.5,0.5-0.5h19 c0.276,0,0.5,0.224,0.5,0.5v5C34,29.776,33.776,30,33.5,30z"/><circle cx="31" cy="27" r="1" fill="#50e6ff"/></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="SVGID_1_" x1="24" x2="24" y1="656" y2="630.201" gradientTransform="matrix(1 0 0 -1 0 662)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#737b80"/><stop offset="1" stop-color="#575c61"/></linearGradient><path fill="url(#SVGID_1_)" d="M10.8,6h26.5c1,0,1.9,0.8,2,1.8l2.8,22c0.1,1.2-0.8,2.2-2,2.2H8c-1.2,0-2.1-1-2-2.2l2.8-22 C8.9,6.8,9.8,6,10.8,6z"/><linearGradient id="SVGID_2_" x1="24" x2="24" y1="631.917" y2="619.5" gradientTransform="matrix(1 0 0 -1 0 662)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#c3cdd9"/><stop offset="1" stop-color="#9fa7b0"/></linearGradient><path fill="url(#SVGID_2_)" d="M42,30H6v10c0,1.1,0.9,2,2,2h32c1.1,0,2-0.9,2-2V30z"/><path d="M35,35c1.1,0,2,0.9,2,2s-0.9,2-2,2s-2-0.9-2-2S33.9,35,35,35 M35,34c-1.7,0-3,1.3-3,3s1.3,3,3,3s3-1.3,3-3 S36.7,34,35,34L35,34z" opacity=".05"/><path d="M35,35c1.1,0,2,0.9,2,2s-0.9,2-2,2s-2-0.9-2-2S33.9,35,35,35 M35,34.5c-1.4,0-2.5,1.1-2.5,2.5s1.1,2.5,2.5,2.5 s2.5-1.1,2.5-2.5S36.4,34.5,35,34.5L35,34.5z" opacity=".07"/><linearGradient id="SVGID_3_" x1="35" x2="35" y1="627.333" y2="622.993" gradientTransform="matrix(1 0 0 -1 0 662)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#75ff8a"/><stop offset="1" stop-color="#1ee446"/></linearGradient><circle cx="35" cy="37" r="2" fill="url(#SVGID_3_)"/><path d="M22.2,22.6c0.5,0.5,1.1,0.7,1.8,0.7c0.7,0,1.3-0.3,1.8-0.7l6.7-6.7c0.5-0.5,0.6-1.2,0.4-1.9 C32.6,13.4,32,13,31.3,13H28V6h-8v7h-3.3c-0.7,0-1.3,0.4-1.6,1.1c-0.3,0.6-0.1,1.4,0.4,1.9L22.2,22.6z" opacity=".05"/><path d="M22.6,22.3c0.4,0.4,0.9,0.6,1.4,0.6c0.5,0,1-0.2,1.4-0.6l6.7-6.7c0.3-0.3,0.5-0.9,0.3-1.3 c-0.2-0.5-0.6-0.7-1.1-0.7h-3.8V6h-7v7.5h-3.8c-0.5,0-0.9,0.3-1.1,0.7c-0.2,0.5-0.1,1,0.3,1.3L22.6,22.3z" opacity=".07"/><path fill="#50e6ff" d="M31.8,15.2l-6.7,6.7c-0.6,0.6-1.5,0.6-2.1,0l-6.7-6.7c-0.4-0.4-0.1-1.2,0.5-1.2H21V1c0-0.6,0.4-1,1-1h4 c0.6,0,1,0.4,1,1v13h4.3C31.9,14,32.2,14.8,31.8,15.2z"/></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="SVGID_1_" x1="23.855" x2="23.983" y1="4.908" y2="17.227" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#7dd8f3"/><stop offset="1" stop-color="#45b0d0"/></linearGradient><circle cx="24" cy="18.9" r="15" fill="url(#SVGID_1_)"/><linearGradient id="SVGID_2_" x1="18.175" x2="29.825" y1="19.021" y2="19.021" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#3079d6"/><stop offset="1" stop-color="#297cd2"/></linearGradient><circle cx="24" cy="19" r="5.8" fill="url(#SVGID_2_)"/><circle cx="24" cy="19" r="2.3" fill="#fff"/><path d="M9,18.9c0,8.3,6.7,15,15,15s15-6.7,15-15c0-0.3,0-0.6,0-0.9H9C9,18.3,9,18.6,9,18.9z" opacity=".05"/><path d="M9,18.9c0,8.3,6.7,15,15,15s15-6.7,15-15c0-0.1,0-0.2,0-0.4H9C9,18.6,9,18.7,9,18.9z" opacity=".07"/><linearGradient id="SVGID_3_" x1="16.786" x2="30.174" y1="-440.286" y2="-472.232" gradientTransform="matrix(1 0 0 -1 0 -426)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#eba84b"/><stop offset="1" stop-color="#d97218"/></linearGradient><path fill="url(#SVGID_3_)" d="M41,44H7c-0.6,0-1-0.5-1-1V20c0-0.6,0.4-1,1-1h34c0.6,0,1,0.4,1,1v23C42,43.5,41.5,44,41,44z"/><path fill="#633412" d="M20,23h8c0.6,0,1,0.4,1,1l0,0c0,0.6-0.4,1-1,1h-8c-0.6,0-1-0.4-1-1l0,0C19,23.4,19.4,23,20,23z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="m4nSpqaCyTO_acMrcTW8ta" x1="12.686" x2="35.58" y1="-566.592" y2="-603.841" gradientTransform="matrix(1 0 0 -1 0 -562)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#33bef0"/><stop offset="1" stop-color="#0a85d9"/></linearGradient><path fill="url(#m4nSpqaCyTO_acMrcTW8ta)" d="M42,8H6c-1.105,0-2,0.895-2,2v26c0,1.105,0.895,2,2,2h8v7.998 c0,0.891,1.077,1.337,1.707,0.707L24.412,38H42c1.105,0,2-0.895,2-2V10C44,8.895,43.105,8,42,8z"/><radialGradient id="m4nSpqaCyTO_acMrcTW8tb" cx="37.979" cy="9.511" r="12.349" gradientUnits="userSpaceOnUse"><stop offset="0"/><stop offset=".979" stop-opacity="0"/></radialGradient><path fill="url(#m4nSpqaCyTO_acMrcTW8tb)" d="M42,8H26.181C26.071,8.652,26,9.317,26,10c0,6.617,5.383,12,12,12 c2.187,0,4.233-0.597,6-1.623V10C44,8.895,43.105,8,42,8z"/><linearGradient id="m4nSpqaCyTO_acMrcTW8tc" x1="30.929" x2="45.071" y1="-564.929" y2="-579.071" gradientTransform="matrix(1 0 0 -1 0 -562)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f44f5a"/><stop offset=".443" stop-color="#ee3d4a"/><stop offset="1" stop-color="#e52030"/></linearGradient><path fill="url(#m4nSpqaCyTO_acMrcTW8tc)" d="M48,10c0,5.522-4.478,10-10,10s-10-4.478-10-10S32.478,0,38,0S48,4.478,48,10z"/><polygon points="34.774,15 34.774,11.594 36.445,11.594 36.445,8.809 34.729,9.181 34.729,5.682 40.217,4.576 40.217,11.594 41.877,11.594 41.877,15" opacity=".05"/><polygon points="35.274,14.5 35.274,12.094 36.945,12.094 36.945,8.188 35.229,8.561 35.229,6.091 39.717,5.187 39.717,12.094 41.377,12.094 41.377,14.5" opacity=".07"/><path fill="#fff" d="M40.878,14h-5.104v-1.406h1.671V7.569L35.729,7.94V6.5l3.489-0.703v6.797h1.66V14z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="CnwFb63xJOzKHmQaoj6ZXa" x1="6" x2="31" y1="18.5" y2="18.5" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#42a3f2"/><stop offset="1" stop-color="#42a4eb"/></linearGradient><path fill="url(#CnwFb63xJOzKHmQaoj6ZXa)" d="M6,8c0-1.104,0.896-2,2-2h21c1.104,0,2,0.896,2,2v21c0,1.104-0.896,2-2,2H8 c-1.104,0-2-0.896-2-2V8z"/><linearGradient id="CnwFb63xJOzKHmQaoj6ZXb" x1="11.916" x2="24.084" y1="19.737" y2="19.737" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#11408a"/><stop offset="1" stop-color="#103f8f"/></linearGradient><path fill="url(#CnwFb63xJOzKHmQaoj6ZXb)" d="M12.832,25.854l-0.916-1.776l0.889-0.459c0.061-0.031,6.101-3.208,9.043-9.104l0.446-0.895 l1.79,0.893l-0.447,0.895c-3.241,6.496-9.645,9.85-9.916,9.989L12.832,25.854z"/><linearGradient id="CnwFb63xJOzKHmQaoj6ZXc" x1="12" x2="26" y1="19" y2="19" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#11408a"/><stop offset="1" stop-color="#103f8f"/></linearGradient><path fill="url(#CnwFb63xJOzKHmQaoj6ZXc)" d="M24.019,25l-0.87-0.49c-0.207-0.116-5.092-2.901-8.496-7.667l1.627-1.162 c3.139,4.394,7.805,7.061,7.851,7.087L25,23.26L24.019,25z M12,13h14v2H12V13z"/><linearGradient id="CnwFb63xJOzKHmQaoj6ZXd" x1="18" x2="20" y1="13" y2="13" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#11408a"/><stop offset="1" stop-color="#103f8f"/></linearGradient><path fill="url(#CnwFb63xJOzKHmQaoj6ZXd)" d="M18,11h2v4h-2V11z"/><linearGradient id="CnwFb63xJOzKHmQaoj6ZXe" x1="17" x2="42" y1="29.5" y2="29.5" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1d59b3"/><stop offset="1" stop-color="#195bbc"/></linearGradient><path fill="url(#CnwFb63xJOzKHmQaoj6ZXe)" d="M31,17h9c1.104,0,2,0.896,2,2v21c0,1.104-0.896,2-2,2H19c-1.104,0-2-0.896-2-2v-9L31,17z"/><path fill="#fff" d="M32.172,33h-4.359l-1.008,3H24l4.764-13h2.444L36,36h-2.805L32.172,33z M28.444,31h3.101 l-1.559-4.714L28.444,31z"/><linearGradient id="CnwFb63xJOzKHmQaoj6ZXf" x1="6" x2="10.241" y1="80" y2="80" gradientTransform="matrix(1 0 0 -1 0 118)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#c965eb"/><stop offset="1" stop-color="#c767e5"/></linearGradient><path fill="url(#CnwFb63xJOzKHmQaoj6ZXf)" d="M6,41.595c0,0.361,0.436,0.541,0.691,0.285l3.332-3.351c0.291-0.293,0.291-0.766,0-1.058 L6.691,34.12C6.436,33.864,6,34.044,6,34.405V41.595z"/><linearGradient id="CnwFb63xJOzKHmQaoj6ZXg" x1="-97.759" x2="-93.517" y1="-28" y2="-28" gradientTransform="matrix(-1 0 0 1 -55.759 38)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#c965eb"/><stop offset="1" stop-color="#c767e5"/></linearGradient><path fill="url(#CnwFb63xJOzKHmQaoj6ZXg)" d="M42,6.405c0-0.361-0.436-0.541-0.691-0.285l-3.332,3.351c-0.291,0.293-0.291,0.766,0,1.058 l3.332,3.351C41.564,14.136,42,13.956,42,13.595V6.405z"/></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="7sslct0wN76PxhO4NsdWha" x1="15.002" x2="33.584" y1="-1506.384" y2="-1542.537" gradientTransform="matrix(1 0 0 -1 0 -1499.89)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#33d7f0"/><stop offset="1" stop-color="#0083c4"/></linearGradient><path fill="url(#7sslct0wN76PxhO4NsdWha)" d="M44,24c0,11.045-8.955,20-20,20S4,35.045,4,24S12.955,4,24,4S44,12.955,44,24z"/><linearGradient id="7sslct0wN76PxhO4NsdWhb" x1="15.002" x2="33.584" y1="-1506.384" y2="-1542.537" gradientTransform="matrix(1 0 0 -1 0 -1499.89)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#33d7f0"/><stop offset="1" stop-color="#0083c4"/></linearGradient><path fill="url(#7sslct0wN76PxhO4NsdWhb)" d="M44,24c0,11.045-8.955,20-20,20S4,35.045,4,24S12.955,4,24,4S44,12.955,44,24z"/><linearGradient id="7sslct0wN76PxhO4NsdWhc" x1="39.38" x2="42.324" y1="-1532.197" y2="-1529.185" gradientTransform="matrix(1 0 0 -1 0 -1499.89)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-opacity="0"/><stop offset="1" stop-opacity=".3"/></linearGradient><path fill="url(#7sslct0wN76PxhO4NsdWhc)" d="M41.188,34.188c0.904-1.521,1.609-3.168,2.086-4.914L41,27l-3.5,3.5L41.188,34.188z"/><linearGradient id="7sslct0wN76PxhO4NsdWhd" x1="105.38" x2="108.324" y1="809.583" y2="812.595" gradientTransform="matrix(-1 0 0 1 114 -793.89)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-opacity="0"/><stop offset="1" stop-opacity=".3"/></linearGradient><path fill="url(#7sslct0wN76PxhO4NsdWhd)" d="M6.812,13.812c-0.904,1.521-1.609,3.168-2.086,4.914L7,21l3.5-3.5L6.812,13.812z"/><linearGradient id="7sslct0wN76PxhO4NsdWhe" x1="12.013" x2="37.021" y1="-1511.903" y2="-1536.911" gradientTransform="matrix(1 0 0 -1 0 -1499.89)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4b4b4b"/><stop offset=".531" stop-color="#393939"/><stop offset="1" stop-color="#252525"/></linearGradient><path fill="url(#7sslct0wN76PxhO4NsdWhe)" d="M39.757,24H42c0-9.941-8.059-18-18-18C15.303,6,8.048,12.167,6.367,20.367l2.39,2.39 C9.216,23.216,8.891,24,8.243,24H6c0,9.941,8.059,18,18,18c8.697,0,15.952-6.167,17.633-14.367l-2.39-2.39 C38.784,24.784,39.109,24,39.757,24z"/><radialGradient id="7sslct0wN76PxhO4NsdWhf" cx="23.789" cy="-1525.339" r="11.736" gradientTransform="matrix(1 0 0 -1 0 -1499.89)" gradientUnits="userSpaceOnUse"><stop offset=".376" stop-color="#261103"/><stop offset=".68" stop-color="#431d06"/><stop offset="1" stop-color="#662c09"/></radialGradient><path fill="url(#7sslct0wN76PxhO4NsdWhf)" d="M24,14.83c-8.814,0-11,14.232-11,14.232c0,2.534,1.177,4.754,3.641,5.342 c2.11,0.504,4.766,0.971,7.359,0.971s5.248-0.467,7.359-0.971C33.823,33.816,35,31.595,35,29.062C35,29.062,32.814,14.83,24,14.83 z"/><radialGradient id="7sslct0wN76PxhO4NsdWhg" cx="247.816" cy="-1559.323" r="9.38" gradientTransform="matrix(-.9136 0 0 -.9667 250.405 -1482.867)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ffcf54"/><stop offset=".261" stop-color="#fdcb4d"/><stop offset=".639" stop-color="#f7c13a"/><stop offset="1" stop-color="#f0b421"/></radialGradient><path fill="url(#7sslct0wN76PxhO4NsdWhg)" d="M24,14.062c3.454,0,7,0.606,7,8.507c0,0.632,0,5.019,0,5.695C31,31.085,26.713,35,24,35 s-7-3.915-7-6.736c0-0.675,0-5.062,0-5.695C17,14.875,20.546,14.062,24,14.062z"/><radialGradient id="7sslct0wN76PxhO4NsdWhh" cx="17.979" cy="-1511.31" r="28.154" gradientTransform="matrix(1 0 0 -1 0 -1499.89)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#c26715"/><stop offset=".508" stop-color="#b85515"/><stop offset="1" stop-color="#ad3f16"/></radialGradient><path fill="url(#7sslct0wN76PxhO4NsdWhh)" d="M24,11c-4.54,0-5,3-5,3c-1.846,0.272-6,0.207-6,15c0,3.533,1.613,4.625,2.731,5.088 C12.662,22.544,21.379,23.906,20,16c3.54,10.336,15.558,3.338,12.269,18.088C33.985,33.362,35,31.186,35,29 C35,16.92,30.474,11,24,11z"/></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.8 KiB

@ -78,7 +78,10 @@
</w-item-section>
<w-item-section>{{ t('common.header.admin') }}</w-item-section>
</w-item>
<w-separator class="my-2" />
<!-- -> Only once there is something above it to divide from the account rows below: a guest whose
rules grant nothing but reading has none of the four, and the menu opened on a rule with
blank space over it and Login alone underneath -->
<w-separator v-if="hasActionRows" class="my-2" />
<!--
The account rows, flattened into this list rather than opened as a second submenu: they are two
plain actions, and the panel they live in on a wide screen is 300px of card -- wider than this
@ -147,6 +150,21 @@ const canUseFileManager = computed(
() => userStore.can('write:assets') || userStore.can('write:pages')
)
/**
* Whether any row is shown above the account group, which is what decides the rule between the two.
*
* Restates the test each of those four rows makes rather than a shorter equivalent — `canUseFileManager`
* already implies the New Page row's permission today, but a row added or a test changed up there would
* otherwise leave this behind, and the failure is silent.
*/
const hasActionRows = computed(
() =>
userStore.can('write:pages') ||
canUseFileManager.value ||
userStore.authenticated ||
userStore.can('access:admin')
)
// METHODS
/**

@ -66,7 +66,7 @@ import { useMeta } from '@/composables/meta'
import { useSiteStore } from '@/stores/site'
import { io } from 'socket.io-client'
import { FitAddon } from '@xterm/addon-fit'
import { Terminal } from '@xterm/xterm'
import '@xterm/xterm/css/xterm.css'
@ -94,6 +94,8 @@ const state = reactive({
let socket = null
let term = null
let fitAddon = null
let resizeObserver = null
// REFS
@ -103,64 +105,90 @@ const termDiv = ref(null)
function clearTerminal() {
term.clear()
term.focus()
}
function connect() {
if (socket) {
return
}
state.connecting = true
socket.connect()
term.writeln(`> ${t('admin.terminal.connecting')}`)
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
socket = new WebSocket(`${protocol}//${window.location.host}/_terminal/logs`)
// -> Whether the stream ever started is what tells a session that was refused or never reached the
// server apart from one that ran and ended, and only `close` is guaranteed to fire
let opened = false
socket.addEventListener('open', () => {
opened = true
state.connected = true
state.connecting = false
term.writeln(`> ${t('admin.terminal.connected')}`)
})
socket.addEventListener('message', (ev) => {
term.writeln(ev.data)
})
socket.addEventListener('close', (ev) => {
socket = null
state.connected = false
state.connecting = false
/*
Codes in the 4000 range are the server's own (see `controllers/terminal.ts`) and mean the
session was refused rather than dropped, so the reason is worth printing — reconnecting with the
same session would be refused just as fast. Anything else that closes without ever having opened
never reached the server, and a browser will not say why.
*/
if (ev.code >= 4000) {
term.writeln(`!> ${t('admin.terminal.connectError')} ${ev.reason}`)
} else if (opened) {
term.writeln(`> ${t('admin.terminal.disconnected')}`)
} else {
term.writeln(`!> ${t('admin.terminal.connectError')}`)
}
})
}
function disconnect() {
socket.disconnect()
socket?.close()
}
// MOUNTED
onMounted(() => {
term = new Terminal({
cursorBlink: true,
cols: 128
cursorBlink: false,
// -> Nothing is sent back to the server: this is a log view, not a shell
disableStdin: true,
convertEol: true,
scrollback: 5000,
fontSize: 13
})
fitAddon = new FitAddon()
term.loadAddon(fitAddon)
term.open(termDiv.value)
term.writeln(`> ${t('admin.terminal.connecting')}`)
state.connecting = true
fitAddon.fit()
// socket = io(window.location.host, {
socket = io(window.location.host, {
path: '/_ws/',
auth: {
token: 'TEST' // TODO: Use active token
},
autoConnect: false
})
socket.on('connect', () => {
term.writeln(`> ${t('admin.terminal.connected')}`)
state.connected = true
state.connecting = false
socket.emit('server:logs')
// -> The terminal sizes itself in whole rows and columns, so it has to be told when the box it sits
// in changes — the admin drawer collapsing counts, not just the window
resizeObserver = new ResizeObserver(() => {
fitAddon.fit()
})
socket.on('disconnect', () => {
term.writeln(`> ${t('admin.terminal.disconnected')}`)
state.connected = false
})
socket.on('connect_error', (err) => {
console.warn(err)
term.writeln(`!> ${t('admin.terminal.connectError')} ${err.message}`)
})
socket.on('server:log', (msg) => {
term.writeln(msg)
term.focus()
})
socket.connect()
resizeObserver.observe(termDiv.value)
connect()
})
// BEFORE UNMOUNT
onBeforeUnmount(() => {
if (socket?.connected) {
socket.disconnect()
}
resizeObserver?.disconnect()
socket?.close()
socket = null
term?.dispose()
})
</script>
@ -168,6 +196,10 @@ onBeforeUnmount(() => {
.admin-terminal {
&-term {
width: 100%;
/* -> The terminal fits itself to this box, so the box has to have a height of its own: sized off
the viewport, it can't grow with its own output and set the resize observer off in a loop */
height: calc(100vh - 260px);
min-height: 240px;
background-color: #000;
border-radius: 5px;
overflow: hidden;

@ -210,20 +210,27 @@ export default defineConfig(({ mode }) => {
host: '0.0.0.0',
allowedHosts: true,
port: userConfig.dev?.port,
proxy: ['_api', '_blocks', '_collab', '_files', '_icons', '_site', '_thumb', '_user'].reduce(
(result, key) => {
proxy: [
'_api',
'_blocks',
'_collab',
'_files',
'_icons',
'_site',
'_terminal',
'_thumb',
'_user'
].reduce((result, key) => {
result[`/${key}`] = {
target: {
host: '127.0.0.1',
port: userConfig.port
},
// -> `_collab` is a websocket; the rest are unaffected by this being on
// -> `_collab` and `_terminal` are websockets; the rest are unaffected by this being on
ws: true
}
return result
},
{}
),
}, {}),
hmr: {
clientPort: userConfig.dev?.hmrClientPort
}

Loading…
Cancel
Save