fix: admin terminal + missing icons

scarlett
NGPixel 4 weeks 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) => {
result[`/${key}`] = {
target: {
host: '127.0.0.1',
port: userConfig.port
},
// -> `_collab` is a websocket; the rest are unaffected by this being on
ws: true
}
return result
},
{}
),
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` and `_terminal` are websockets; the rest are unaffected by this being on
ws: true
}
return result
}, {}),
hmr: {
clientPort: userConfig.dev?.hmrClientPort
}

Loading…
Cancel
Save