[READ-ONLY] Mirror of https://github.com/FoxxMD/multi-scrobbler. Scrobble plays from multiple sources to multiple clients docs.multi-scrobbler.app
deezer docker jellyfin koito lastfm listenbrainz maloja mopidy mpris music music-assistant plex scrobble self-hosted spotify subsonic tautulli youtube-music
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(ui): Use ansi-to-react for log coloring

Vastly simplifies log formatting on the dashboard

FoxxMD (Mar 21, 2024, 3:33 PM EDT) c7d2e7ac 2dde3840

+49 -30
+31 -4
package-lock.json
··· 12 12 "dependencies": { 13 13 "@astronautlabs/mdns": "^1.0.7", 14 14 "@awaitjs/express": "^0.6.3", 15 + "@curvenote/ansi-to-react": "^7.0.0", 15 16 "@fortawesome/fontawesome-svg-core": "^6.4.2", 16 17 "@fortawesome/free-solid-svg-icons": "^6.4.2", 17 18 "@fortawesome/react-fontawesome": "^0.2.0", 18 19 "@foxxmd/chromecast-client": "^1.0.4", 19 - "@foxxmd/logging": "^0.1.11", 20 + "@foxxmd/logging": "^0.1.12", 20 21 "@foxxmd/string-sameness": "^0.4.0", 21 22 "@kenyip/backoff-strategies": "^1.0.4", 22 23 "@react-nano/use-event-source": "^0.13.0", ··· 1067 1068 "@jridgewell/sourcemap-codec": "^1.4.10" 1068 1069 } 1069 1070 }, 1071 + "node_modules/@curvenote/ansi-to-react": { 1072 + "version": "7.0.0", 1073 + "resolved": "https://registry.npmjs.org/@curvenote/ansi-to-react/-/ansi-to-react-7.0.0.tgz", 1074 + "integrity": "sha512-+m4V86QPmaZ7udMp7Yg81A31dLBGO8gglkPGWPzUJNxLCSyOrJ04pQmdZQelNBEA7MSGz8wf+6RHcuEaujdhHw==", 1075 + "dependencies": { 1076 + "anser": "^2.1.1", 1077 + "escape-carriage": "^1.3.1" 1078 + }, 1079 + "engines": { 1080 + "node": ">=16" 1081 + }, 1082 + "peerDependencies": { 1083 + "react": "^16.3.2 || ^17.0.0 || ^18.0.0", 1084 + "react-dom": "^16.3.2 || ^17.0.0 || ^18.0.0" 1085 + } 1086 + }, 1070 1087 "node_modules/@dbus-types/dbus": { 1071 1088 "version": "0.0.4", 1072 1089 "resolved": "https://registry.npmjs.org/@dbus-types/dbus/-/dbus-0.0.4.tgz", ··· 1626 1643 } 1627 1644 }, 1628 1645 "node_modules/@foxxmd/logging": { 1629 - "version": "0.1.11", 1630 - "resolved": "https://registry.npmjs.org/@foxxmd/logging/-/logging-0.1.11.tgz", 1631 - "integrity": "sha512-k0qdUFvtyzqlrCDxaXNJCjpKCzfa8jdspArhE/pXd5Lnv69jJvwNVFIwgbfm+Yhrr7rwY+y5/gxSpZKZc19reg==", 1646 + "version": "0.1.12", 1647 + "resolved": "https://registry.npmjs.org/@foxxmd/logging/-/logging-0.1.12.tgz", 1648 + "integrity": "sha512-vLu0Ip+eDY9k7X7PmefY+Aj6gDwjN/MWJp4icW/Op4By6vZrSJV6eJyrYX9hoXppBUYOH0us09tqGhwSYLtQxA==", 1632 1649 "dependencies": { 1633 1650 "pino": "^8.19.0", 1634 1651 "pino-abstract-transport": "^1.1.0", ··· 3342 3359 "url": "https://github.com/sponsors/epoberezkin" 3343 3360 } 3344 3361 }, 3362 + "node_modules/anser": { 3363 + "version": "2.1.1", 3364 + "resolved": "https://registry.npmjs.org/anser/-/anser-2.1.1.tgz", 3365 + "integrity": "sha512-nqLm4HxOTpeLOxcmB3QWmV5TcDFhW9y/fyQ+hivtDFcK4OQ+pQ5fzPnXHM1Mfcm0VkLtvVi1TCPr++Qy0Q/3EQ==" 3366 + }, 3345 3367 "node_modules/ansi-colors": { 3346 3368 "version": "4.1.1", 3347 3369 "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", ··· 4821 4843 "engines": { 4822 4844 "node": ">=6" 4823 4845 } 4846 + }, 4847 + "node_modules/escape-carriage": { 4848 + "version": "1.3.1", 4849 + "resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz", 4850 + "integrity": "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==" 4824 4851 }, 4825 4852 "node_modules/escape-html": { 4826 4853 "version": "1.0.3",
+2 -1
package.json
··· 46 46 "dependencies": { 47 47 "@astronautlabs/mdns": "^1.0.7", 48 48 "@awaitjs/express": "^0.6.3", 49 + "@curvenote/ansi-to-react": "^7.0.0", 49 50 "@fortawesome/fontawesome-svg-core": "^6.4.2", 50 51 "@fortawesome/free-solid-svg-icons": "^6.4.2", 51 52 "@fortawesome/react-fontawesome": "^0.2.0", 52 53 "@foxxmd/chromecast-client": "^1.0.4", 53 - "@foxxmd/logging": "^0.1.11", 54 + "@foxxmd/logging": "^0.1.12", 54 55 "@foxxmd/string-sameness": "^0.4.0", 55 56 "@kenyip/backoff-strategies": "^1.0.4", 56 57 "@react-nano/use-event-source": "^0.13.0",
+2 -2
src/backend/common/logging.ts
··· 21 21 const stream = new PassThrough({objectMode: true}); 22 22 const logger = buildLogger('debug', [ 23 23 buildDestinationStdout(opts.console), 24 - buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: false}) 24 + buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: true}) 25 25 ]); 26 26 return [logger, stream]; 27 27 } ··· 32 32 const logger = await loggerAppRolling(config, { 33 33 logBaseDir: typeof process.env.CONFIG_DIR === 'string' ? process.env.CONFIG_DIR : undefined, 34 34 destinations: [ 35 - buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: false}) 35 + buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: true}) 36 36 ] 37 37 }); 38 38 return [logger, stream];
+12 -5
src/client/index.css
··· 30 30 .dark .loading { 31 31 fill: white; 32 32 } 33 - .blue { 33 + .blue,.ansi-blue-fg { 34 34 color: rgb(97, 175, 239) 35 35 } 36 - .red { 36 + .red,.ansi-red-fg { 37 37 color: rgb(255 123 133); 38 38 } 39 - .green { 39 + .green,.ansi-green-fg { 40 40 color: rgb(170 255 109); 41 41 } 42 - .purple { 42 + .purple,.ansi-magenta-fg { 43 43 color: rgb(224 124 253); 44 44 } 45 - .yellow { 45 + .yellow,.ansi-yellow-fg { 46 46 color: rgb(253 198 94); 47 47 } 48 + .ansi-cyan-fg { 49 + color: rgb(255, 255, 255) 50 + } 51 + 52 + .ansi-bright-black-fg { 53 + color: rgb(169, 169, 169) 54 + }
+2 -18
src/client/logs/LogLine.tsx
··· 1 1 import React, {PropsWithChildren} from 'react'; 2 - import {parseLogLine} from "../utils/index"; 2 + import Ansi from "@curvenote/ansi-to-react"; 3 3 4 - const getClass = (level: string) => { 5 - switch(level) { 6 - case 'debug': 7 - return 'debug blue'; 8 - case 'warn': 9 - return 'warn yellow'; 10 - case 'info': 11 - return 'info green'; 12 - case 'verbose': 13 - return 'verbose purple'; 14 - case 'error': 15 - return 'error red'; 16 - } 17 - } 18 4 const LogLine = (props: PropsWithChildren<{level: number, levelLabel: string, message: string}>) => { 19 - const lineParts = parseLogLine(props.message); 20 - const levelClass = getClass(props.levelLabel); 21 5 return ( 22 - <div className="line">{lineParts.timestamp} <span className={levelClass}>{props.levelLabel.padEnd(7, ' ')}</span> : {lineParts.message}</div> 6 + <div className="line"><Ansi useClasses>{props.message}</Ansi></div> 23 7 ) 24 8 }; 25 9