mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
add additional logging to controller and mutations
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { createSocket } from 'dgram';
|
import { createSocket } from 'dgram';
|
||||||
import { ipcMain } from 'electron';
|
import { ipcMain } from 'electron';
|
||||||
|
|
||||||
|
import { mainLogger } from '/@/main/logger';
|
||||||
import { DiscoveredServerItem, ServerType } from '/@/shared/types/types';
|
import { DiscoveredServerItem, ServerType } from '/@/shared/types/types';
|
||||||
|
|
||||||
type JellyfinResponse = {
|
type JellyfinResponse = {
|
||||||
@@ -26,7 +27,7 @@ function discoverJellyfin(reply: (server: DiscoveredServerItem) => void) {
|
|||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Got a spurious response, ignore?
|
// Got a spurious response, ignore?
|
||||||
console.error(e);
|
mainLogger.error('Autodiscover Jellyfin parse error', e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -51,5 +52,5 @@ ipcMain.on('autodiscover-ping', (ev) => {
|
|||||||
|
|
||||||
discoverAll((result) => port.postMessage(result))
|
discoverAll((result) => port.postMessage(result))
|
||||||
.then(() => port.close())
|
.then(() => port.close())
|
||||||
.catch((err) => console.error(err));
|
.catch((err) => mainLogger.error('Autodiscover failed', err));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
LyricSearchQuery,
|
LyricSearchQuery,
|
||||||
LyricSource,
|
LyricSource,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
import { mainLogger } from '../../../logger';
|
||||||
import { orderSearchResults } from './shared';
|
import { orderSearchResults } from './shared';
|
||||||
|
|
||||||
const SEARCH_URL = 'https://genius.com/api/search/song';
|
const SEARCH_URL = 'https://genius.com/api/search/song';
|
||||||
@@ -100,7 +101,7 @@ export async function getLyricsBySongId(url: string): Promise<null | string> {
|
|||||||
try {
|
try {
|
||||||
result = await axios.get<string>(url, { responseType: 'text' });
|
result = await axios.get<string>(url, { responseType: 'text' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Genius lyrics request got an error!', (e as Error)?.message);
|
mainLogger.error('Genius lyrics request failed', (e as Error)?.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +139,7 @@ export async function getSearchResults(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Genius search request got an error!', (e as Error)?.message);
|
mainLogger.error('Genius search request failed', (e as Error)?.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +194,7 @@ async function getSongId(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Genius search request got an error!', (e as Error)?.message);
|
mainLogger.error('Genius search request failed', (e as Error)?.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ipcMain } from 'electron';
|
import { ipcMain } from 'electron';
|
||||||
|
|
||||||
|
import { mainLogger } from '../../../logger';
|
||||||
import { store } from '../settings';
|
import { store } from '../settings';
|
||||||
import { getLyricsBySongId as getGenius, getSearchResults as searchGenius } from './genius';
|
import { getLyricsBySongId as getGenius, getSearchResults as searchGenius } from './genius';
|
||||||
import { getLyricsBySongId as getLrcLib, getSearchResults as searchLrcLib } from './lrclib';
|
import { getLyricsBySongId as getLrcLib, getSearchResults as searchLrcLib } from './lrclib';
|
||||||
@@ -96,7 +97,7 @@ const searchAllSources = async (
|
|||||||
allSearchResults.push(...result.value.searchResults);
|
allSearchResults.push(...result.value.searchResults);
|
||||||
} else if (result.status === 'rejected') {
|
} else if (result.status === 'rejected') {
|
||||||
const index = settled.indexOf(result);
|
const index = settled.indexOf(result);
|
||||||
console.error(`Error searching ${sources[index]} for lyrics:`, result.reason);
|
mainLogger.error(`Error searching ${sources[index]} for lyrics`, result.reason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return allSearchResults;
|
return allSearchResults;
|
||||||
@@ -160,7 +161,7 @@ const getRemoteLyrics = async (song: Song) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching lyrics from ${bestMatch.source}:`, error);
|
mainLogger.error(`Error fetching lyrics from ${bestMatch.source}`, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lyricsFromSource) {
|
if (lyricsFromSource) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
LyricSearchQuery,
|
LyricSearchQuery,
|
||||||
LyricSource,
|
LyricSource,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
import { mainLogger } from '../../../logger';
|
||||||
import { orderSearchResults } from './shared';
|
import { orderSearchResults } from './shared';
|
||||||
|
|
||||||
const FETCH_URL = 'https://lrclib.net/api/get';
|
const FETCH_URL = 'https://lrclib.net/api/get';
|
||||||
@@ -46,7 +47,7 @@ export async function getLyricsBySongId(songId: string): Promise<null | string>
|
|||||||
try {
|
try {
|
||||||
result = await axios.get<LrcLibTrackResponse>(`${FETCH_URL}/${songId}`);
|
result = await axios.get<LrcLibTrackResponse>(`${FETCH_URL}/${songId}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('LrcLib lyrics request got an error!', (e as Error)?.message);
|
mainLogger.error('LrcLib lyrics request failed', (e as Error)?.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ export async function getSearchResults(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('LrcLib search request got an error!', (e as Error)?.message);
|
mainLogger.error('LrcLib search request failed', (e as Error)?.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ export async function query(
|
|||||||
timeout: TIMEOUT_MS,
|
timeout: TIMEOUT_MS,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('LrcLib search request got an error!', (e as Error).message);
|
mainLogger.error('LrcLib search request failed', (e as Error).message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
LyricSearchQuery,
|
LyricSearchQuery,
|
||||||
LyricSource,
|
LyricSource,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
import { mainLogger } from '../../../logger';
|
||||||
import { store } from '../settings';
|
import { store } from '../settings';
|
||||||
import { orderSearchResults } from './shared';
|
import { orderSearchResults } from './shared';
|
||||||
|
|
||||||
@@ -81,7 +82,7 @@ export async function getLyricsBySongId(songId: string): Promise<null | string>
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('NetEase lyrics request got an error!', e);
|
mainLogger.error('NetEase lyrics request failed', e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const enableTranslation = store.get('enableNeteaseTranslation', false) as boolean;
|
const enableTranslation = store.get('enableNeteaseTranslation', false) as boolean;
|
||||||
@@ -114,7 +115,7 @@ export async function getSearchResults(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('NetEase search request got an error!', e);
|
mainLogger.error('NetEase search request failed', e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import console from 'console';
|
|
||||||
import { app, ipcMain } from 'electron';
|
import { app, ipcMain } from 'electron';
|
||||||
import { rm } from 'fs/promises';
|
import { rm } from 'fs/promises';
|
||||||
import uniq from 'lodash/uniq';
|
import uniq from 'lodash/uniq';
|
||||||
@@ -7,6 +6,7 @@ import { pid } from 'node:process';
|
|||||||
import process from 'process';
|
import process from 'process';
|
||||||
|
|
||||||
import { getMainWindow, sendToastToRenderer } from '../../../index';
|
import { getMainWindow, sendToastToRenderer } from '../../../index';
|
||||||
|
import { mainLogger } from '../../../logger';
|
||||||
import { createLog, isWindows } from '../../../utils';
|
import { createLog, isWindows } from '../../../utils';
|
||||||
import { store } from '../settings';
|
import { store } from '../settings';
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ const createMpv = async (data: {
|
|||||||
try {
|
try {
|
||||||
await mpv.start();
|
await mpv.start();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('mpv failed to start', error);
|
mainLogger.error('mpv failed to start', error);
|
||||||
} finally {
|
} finally {
|
||||||
await mpv.setMultipleProperties(properties || {});
|
await mpv.setMultipleProperties(properties || {});
|
||||||
}
|
}
|
||||||
@@ -672,7 +672,7 @@ process.on('SIGTERM', async () => {
|
|||||||
|
|
||||||
// Handle uncaught exceptions - cleanup mpv before crashing
|
// Handle uncaught exceptions - cleanup mpv before crashing
|
||||||
process.on('uncaughtException', async (error) => {
|
process.on('uncaughtException', async (error) => {
|
||||||
console.error('Uncaught exception:', error);
|
mainLogger.error('Uncaught exception', error);
|
||||||
await cleanupMpv(true).catch(() => {
|
await cleanupMpv(true).catch(() => {
|
||||||
// Ignore cleanup errors during crash
|
// Ignore cleanup errors during crash
|
||||||
});
|
});
|
||||||
@@ -680,7 +680,7 @@ process.on('uncaughtException', async (error) => {
|
|||||||
|
|
||||||
// Handle unhandled rejections - cleanup mpv
|
// Handle unhandled rejections - cleanup mpv
|
||||||
process.on('unhandledRejection', async (reason) => {
|
process.on('unhandledRejection', async (reason) => {
|
||||||
console.error('Unhandled rejection:', reason);
|
mainLogger.error('Unhandled rejection', reason);
|
||||||
await cleanupMpv(true).catch(() => {
|
await cleanupMpv(true).catch(() => {
|
||||||
// Ignore cleanup errors
|
// Ignore cleanup errors
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { deflate, gzip } from 'zlib';
|
|||||||
import manifest from './manifest.json';
|
import manifest from './manifest.json';
|
||||||
|
|
||||||
import { getMainWindow } from '/@/main/index';
|
import { getMainWindow } from '/@/main/index';
|
||||||
|
import { mainLogger } from '/@/main/logger';
|
||||||
import { isLinux } from '/@/main/utils';
|
import { isLinux } from '/@/main/utils';
|
||||||
import { QueueSong } from '/@/shared/types/domain-types';
|
import { QueueSong } from '/@/shared/types/domain-types';
|
||||||
import { ClientEvent, ServerEvent } from '/@/shared/types/remote-types';
|
import { ClientEvent, ServerEvent } from '/@/shared/types/remote-types';
|
||||||
@@ -349,7 +350,7 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
|||||||
}, 10000) as unknown as number;
|
}, 10000) as unknown as number;
|
||||||
}
|
}
|
||||||
|
|
||||||
ws.on('error', console.error);
|
ws.on('error', (err) => mainLogger.error('Remote WebSocket error', err));
|
||||||
|
|
||||||
ws.on('message', (data) => {
|
ws.on('message', (data) => {
|
||||||
try {
|
try {
|
||||||
@@ -488,7 +489,7 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
mainLogger.error('Remote message handler error', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+15
-14
@@ -29,6 +29,7 @@ import packageJson from '../../package.json';
|
|||||||
import { disableMediaKeys, enableMediaKeys } from './features/core/player/media-keys';
|
import { disableMediaKeys, enableMediaKeys } from './features/core/player/media-keys';
|
||||||
import { shutdownServer } from './features/core/remote';
|
import { shutdownServer } from './features/core/remote';
|
||||||
import { store } from './features/core/settings';
|
import { store } from './features/core/settings';
|
||||||
|
import { mainLogger } from './logger';
|
||||||
import MenuBuilder from './menu';
|
import MenuBuilder from './menu';
|
||||||
import {
|
import {
|
||||||
autoUpdaterLogInterface,
|
autoUpdaterLogInterface,
|
||||||
@@ -66,7 +67,7 @@ type UpdaterInstance = AppImageUpdater | MacUpdater | NsisUpdater | typeof autoU
|
|||||||
class AppUpdater {
|
class AppUpdater {
|
||||||
constructor() {
|
constructor() {
|
||||||
const effectiveChannel = store.get('release_channel') as string;
|
const effectiveChannel = store.get('release_channel') as string;
|
||||||
console.log('Effective update channel:', effectiveChannel);
|
mainLogger.info('Effective update channel:', effectiveChannel);
|
||||||
if (effectiveChannel === 'alpha') {
|
if (effectiveChannel === 'alpha') {
|
||||||
checkAllChannelsAndGetBest().then(({ updater: updaterInstance }) => {
|
checkAllChannelsAndGetBest().then(({ updater: updaterInstance }) => {
|
||||||
updaterInstance.autoInstallOnAppQuit = true;
|
updaterInstance.autoInstallOnAppQuit = true;
|
||||||
@@ -103,7 +104,7 @@ async function checkAllChannelsAndGetBest(): Promise<{
|
|||||||
alphaUpdater.allowDowngrade = true;
|
alphaUpdater.allowDowngrade = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Checking for updates on alpha channel');
|
mainLogger.info('Checking for updates on alpha channel');
|
||||||
const alphaResult = await alphaUpdater.checkForUpdates();
|
const alphaResult = await alphaUpdater.checkForUpdates();
|
||||||
if (
|
if (
|
||||||
alphaResult?.updateInfo?.version &&
|
alphaResult?.updateInfo?.version &&
|
||||||
@@ -120,7 +121,7 @@ async function checkAllChannelsAndGetBest(): Promise<{
|
|||||||
try {
|
try {
|
||||||
autoUpdater.setFeedURL(GITHUB_UPDATER_CONFIG);
|
autoUpdater.setFeedURL(GITHUB_UPDATER_CONFIG);
|
||||||
configureAutoUpdaterForChannel('latest');
|
configureAutoUpdaterForChannel('latest');
|
||||||
console.log('Checking for updates on latest channel (GitHub)');
|
mainLogger.info('Checking for updates on latest channel (GitHub)');
|
||||||
const latestResult = await autoUpdater.checkForUpdates();
|
const latestResult = await autoUpdater.checkForUpdates();
|
||||||
if (
|
if (
|
||||||
latestResult?.updateInfo?.version &&
|
latestResult?.updateInfo?.version &&
|
||||||
@@ -155,13 +156,13 @@ function configureAndGetUpdater(): UpdaterInstance {
|
|||||||
let releaseChannel = store.get('release_channel');
|
let releaseChannel = store.get('release_channel');
|
||||||
const isNotConfigured = !releaseChannel;
|
const isNotConfigured = !releaseChannel;
|
||||||
|
|
||||||
console.log('Release channel:', releaseChannel);
|
mainLogger.info('Release channel:', releaseChannel);
|
||||||
console.log('Is beta version:', isBetaVersion);
|
mainLogger.info('Is beta version:', isBetaVersion);
|
||||||
console.log('Is alpha version:', isAlphaVersion);
|
mainLogger.info('Is alpha version:', isAlphaVersion);
|
||||||
console.log('Is not configured:', isNotConfigured);
|
mainLogger.info('Is not configured:', isNotConfigured);
|
||||||
|
|
||||||
if (isNotConfigured) {
|
if (isNotConfigured) {
|
||||||
console.log('Release channel not configured, setting default channel');
|
mainLogger.info('Release channel not configured, setting default channel');
|
||||||
const defaultChannel = isAlphaVersion ? 'alpha' : isBetaVersion ? 'beta' : 'latest';
|
const defaultChannel = isAlphaVersion ? 'alpha' : isBetaVersion ? 'beta' : 'latest';
|
||||||
store.set('release_channel', defaultChannel);
|
store.set('release_channel', defaultChannel);
|
||||||
releaseChannel = defaultChannel;
|
releaseChannel = defaultChannel;
|
||||||
@@ -235,7 +236,7 @@ function createAlphaUpdaterInstance(): AppImageUpdater | MacUpdater | NsisUpdate
|
|||||||
protocol.registerSchemesAsPrivileged([{ privileges: { bypassCSP: true }, scheme: 'feishin' }]);
|
protocol.registerSchemesAsPrivileged([{ privileges: { bypassCSP: true }, scheme: 'feishin' }]);
|
||||||
|
|
||||||
process.on('uncaughtException', (error: any) => {
|
process.on('uncaughtException', (error: any) => {
|
||||||
console.error('Error in main process', error);
|
mainLogger.error('Uncaught exception in main process', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (store.get('ignore_ssl')) {
|
if (store.get('ignore_ssl')) {
|
||||||
@@ -521,12 +522,12 @@ async function createWindow(first = true): Promise<void> {
|
|||||||
'app-check-for-updates',
|
'app-check-for-updates',
|
||||||
async (): Promise<{ updateAvailable: boolean; version?: string }> => {
|
async (): Promise<{ updateAvailable: boolean; version?: string }> => {
|
||||||
if (disableAutoUpdates()) {
|
if (disableAutoUpdates()) {
|
||||||
console.log('Auto updates are disabled');
|
mainLogger.info('Auto updates are disabled');
|
||||||
return { updateAvailable: false };
|
return { updateAvailable: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Checking for updates');
|
mainLogger.info('Checking for updates');
|
||||||
const effectiveChannel = store.get('release_channel') as string;
|
const effectiveChannel = store.get('release_channel') as string;
|
||||||
let result: null | UpdateCheckResult;
|
let result: null | UpdateCheckResult;
|
||||||
let updater: UpdaterInstance;
|
let updater: UpdaterInstance;
|
||||||
@@ -541,9 +542,9 @@ async function createWindow(first = true): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateAvailable = result?.isUpdateAvailable ?? false;
|
const updateAvailable = result?.isUpdateAvailable ?? false;
|
||||||
console.log('Update available:', updateAvailable);
|
mainLogger.info('Update available:', updateAvailable);
|
||||||
if (updateAvailable && store.get('disable_auto_updates') !== true) {
|
if (updateAvailable && store.get('disable_auto_updates') !== true) {
|
||||||
console.log('Downloading update');
|
mainLogger.info('Downloading update');
|
||||||
updater.downloadUpdate();
|
updater.downloadUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,7 +553,7 @@ async function createWindow(first = true): Promise<void> {
|
|||||||
version: result?.updateInfo?.version,
|
version: result?.updateInfo?.version,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
console.log('Error checking for updates');
|
mainLogger.error('Error checking for updates');
|
||||||
return { updateAvailable: false };
|
return { updateAvailable: false };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
const pad = (n: number) => String(n).padStart(2, '0');
|
||||||
|
|
||||||
|
const timestamp = () => {
|
||||||
|
const d = new Date();
|
||||||
|
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const format = (level: string, message: string, ...args: unknown[]) => {
|
||||||
|
const prefix = `[${timestamp()}] [${level}] ${message}`;
|
||||||
|
if (args.length > 0) {
|
||||||
|
console.log(prefix, ...args);
|
||||||
|
} else {
|
||||||
|
console.log(prefix);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mainLogger = {
|
||||||
|
debug: (message: string, ...args: unknown[]) => format('DEBUG', message, ...args),
|
||||||
|
error: (message: string, ...args: unknown[]) => {
|
||||||
|
const prefix = `[${timestamp()}] [ERROR] ${message}`;
|
||||||
|
if (args.length > 0) {
|
||||||
|
console.error(prefix, ...args);
|
||||||
|
} else {
|
||||||
|
console.error(prefix);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
info: (message: string, ...args: unknown[]) => format('INFO', message, ...args),
|
||||||
|
warn: (message: string, ...args: unknown[]) => {
|
||||||
|
const prefix = `[${timestamp()}] [WARN] ${message}`;
|
||||||
|
if (args.length > 0) {
|
||||||
|
console.warn(prefix, ...args);
|
||||||
|
} else {
|
||||||
|
console.warn(prefix);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ import { NavidromeController } from '/@/renderer/api/navidrome/navidrome-control
|
|||||||
import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller';
|
import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller';
|
||||||
import { mergeMusicFolderId } from '/@/renderer/api/utils-music-folder';
|
import { mergeMusicFolderId } from '/@/renderer/api/utils-music-folder';
|
||||||
import { getServerById, useAuthStore, useSettingsStore } from '/@/renderer/store';
|
import { getServerById, useAuthStore, useSettingsStore } from '/@/renderer/store';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { toast } from '/@/shared/components/toast/toast';
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
import {
|
import {
|
||||||
AuthenticationResponse,
|
AuthenticationResponse,
|
||||||
@@ -31,6 +32,7 @@ const apiController = <K extends keyof ControllerEndpoint>(
|
|||||||
const serverType = type || useAuthStore.getState().currentServer?.type;
|
const serverType = type || useAuthStore.getState().currentServer?.type;
|
||||||
|
|
||||||
if (!serverType) {
|
if (!serverType) {
|
||||||
|
logFn.warn('No server selected', { category: LogCategory.API });
|
||||||
toast.error({
|
toast.error({
|
||||||
message: i18n.t('error.serverNotSelectedError', {
|
message: i18n.t('error.serverNotSelectedError', {
|
||||||
postProcess: 'sentenceCase',
|
postProcess: 'sentenceCase',
|
||||||
@@ -43,6 +45,10 @@ const apiController = <K extends keyof ControllerEndpoint>(
|
|||||||
const controllerFn = endpoints?.[serverType]?.[endpoint];
|
const controllerFn = endpoints?.[serverType]?.[endpoint];
|
||||||
|
|
||||||
if (typeof controllerFn !== 'function') {
|
if (typeof controllerFn !== 'function') {
|
||||||
|
logFn.warn('Endpoint not implemented', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: { endpoint, serverType },
|
||||||
|
});
|
||||||
toast.error({
|
toast.error({
|
||||||
message: `Endpoint ${endpoint} is not implemented for ${serverType}`,
|
message: `Endpoint ${endpoint} is not implemented for ${serverType}`,
|
||||||
title: i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' }) as string,
|
title: i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' }) as string,
|
||||||
@@ -57,6 +63,10 @@ const apiController = <K extends keyof ControllerEndpoint>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logFn.debug('API controller call', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: { endpoint, serverType },
|
||||||
|
});
|
||||||
return controllerFn;
|
return controllerFn;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import qs from 'qs';
|
|||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
import { authenticationFailure } from '/@/renderer/api/utils';
|
import { authenticationFailure } from '/@/renderer/api/utils';
|
||||||
import { useAuthStore } from '/@/renderer/store';
|
import { useAuthStore } from '/@/renderer/store';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { getServerUrl } from '/@/renderer/utils/normalize-server-url';
|
import { getServerUrl } from '/@/renderer/utils/normalize-server-url';
|
||||||
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
|
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
|
||||||
import { resultWithHeaders } from '/@/shared/api/utils';
|
import { resultWithHeaders } from '/@/shared/api/utils';
|
||||||
@@ -367,11 +368,21 @@ axiosClient.interceptors.response.use(
|
|||||||
})
|
})
|
||||||
.catch((newError: any) => {
|
.catch((newError: any) => {
|
||||||
if (newError !== TIMEOUT_ERROR) {
|
if (newError !== TIMEOUT_ERROR) {
|
||||||
console.error('Error when trying to reauthenticate: ', newError);
|
logFn.error('Reauthentication failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
message: (newError as Error)?.message,
|
||||||
|
serverId: currentServer.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (isAxiosError(newError) && newError.code === 'ERR_NETWORK') {
|
if (isAxiosError(newError) && newError.code === 'ERR_NETWORK') {
|
||||||
console.log(
|
logFn.info(
|
||||||
'Network error during reauthentication - preserving credentials',
|
'Network error during reauthentication - preserving credentials',
|
||||||
|
{
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: { serverId: currentServer.id },
|
||||||
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
limitedFail(currentServer);
|
limitedFail(currentServer);
|
||||||
@@ -387,7 +398,10 @@ axiosClient.interceptors.response.use(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isAxiosError(error) && error.code === 'ERR_NETWORK') {
|
if (isAxiosError(error) && error.code === 'ERR_NETWORK') {
|
||||||
console.log('Network error during authentication - preserving credentials');
|
logFn.info('Network error during authentication - preserving credentials', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: { serverId: useAuthStore.getState().currentServer?.id },
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
limitedFail(currentServer);
|
limitedFail(currentServer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
import { useAuthStore } from '/@/renderer/store';
|
import { useAuthStore } from '/@/renderer/store';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { toast } from '/@/shared/components/toast/toast';
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
import { ServerListItem } from '/@/shared/types/types';
|
import { ServerListItem } from '/@/shared/types/types';
|
||||||
|
|
||||||
export const authenticationFailure = (currentServer: null | ServerListItem) => {
|
export const authenticationFailure = (currentServer: null | ServerListItem) => {
|
||||||
|
logFn.error('Token expired', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: { serverId: currentServer?.id },
|
||||||
|
});
|
||||||
toast.error({
|
toast.error({
|
||||||
message: 'Your session has expired.',
|
message: 'Your session has expired.',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (currentServer) {
|
if (currentServer) {
|
||||||
const serverId = currentServer.id;
|
const serverId = currentServer.id;
|
||||||
const token = currentServer.ndCredential;
|
|
||||||
console.error(`token is expired: ${token}`);
|
|
||||||
useAuthStore.getState().actions.updateServer(serverId, { ndCredential: undefined });
|
useAuthStore.getState().actions.updateServer(serverId, { ndCredential: undefined });
|
||||||
useAuthStore.getState().actions.setCurrentServer(null);
|
useAuthStore.getState().actions.setCurrentServer(null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { AnimatedPage } from '/@/renderer/features/shared/components/animated-pa
|
|||||||
import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary';
|
import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useAuthStoreActions, useCurrentServer } from '/@/renderer/store';
|
import { useAuthStoreActions, useCurrentServer } from '/@/renderer/store';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { Button } from '/@/shared/components/button/button';
|
import { Button } from '/@/shared/components/button/button';
|
||||||
import { Center } from '/@/shared/components/center/center';
|
import { Center } from '/@/shared/components/center/center';
|
||||||
import { Code } from '/@/shared/components/code/code';
|
import { Code } from '/@/shared/components/code/code';
|
||||||
@@ -136,6 +137,10 @@ const LoginRoute = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
logFn.error('Login failed (no data returned)', {
|
||||||
|
category: LogCategory.SYSTEM,
|
||||||
|
meta: { serverName, serverType, serverUrl },
|
||||||
|
});
|
||||||
return toast.error({
|
return toast.error({
|
||||||
message: t('error.authenticationFailed', { postProcess: 'sentenceCase' }),
|
message: t('error.authenticationFailed', { postProcess: 'sentenceCase' }),
|
||||||
});
|
});
|
||||||
@@ -159,6 +164,10 @@ const LoginRoute = () => {
|
|||||||
addServer(serverItem);
|
addServer(serverItem);
|
||||||
setCurrentServer(serverItem);
|
setCurrentServer(serverItem);
|
||||||
|
|
||||||
|
logFn.info('Login successful', {
|
||||||
|
category: LogCategory.SYSTEM,
|
||||||
|
meta: { serverName, serverType, serverUrl, userId: data.userId },
|
||||||
|
});
|
||||||
toast.success({
|
toast.success({
|
||||||
message: t('form.addServer.success', { postProcess: 'sentenceCase' }),
|
message: t('form.addServer.success', { postProcess: 'sentenceCase' }),
|
||||||
});
|
});
|
||||||
@@ -175,6 +184,10 @@ const LoginRoute = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
logFn.error('Login failed', {
|
||||||
|
category: LogCategory.SYSTEM,
|
||||||
|
meta: { message: err?.message, serverName, serverType, serverUrl },
|
||||||
|
});
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return toast.error({ message: err?.message });
|
return toast.error({ message: err?.message });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useRecentPlaylists } from '/@/renderer/features/playlists/hooks/use-recent-playlists';
|
import { useRecentPlaylists } from '/@/renderer/features/playlists/hooks/use-recent-playlists';
|
||||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||||
import { useCurrentServerId } from '/@/renderer/store';
|
import { useCurrentServerId } from '/@/renderer/store';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { AddToPlaylistArgs, AddToPlaylistResponse } from '/@/shared/types/domain-types';
|
import { AddToPlaylistArgs, AddToPlaylistResponse } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export const useAddToPlaylist = (args: MutationHookArgs) => {
|
export const useAddToPlaylist = (args: MutationHookArgs) => {
|
||||||
@@ -22,6 +23,17 @@ export const useAddToPlaylist = (args: MutationHookArgs) => {
|
|||||||
apiClientProps: { serverId: args.apiClientProps.serverId },
|
apiClientProps: { serverId: args.apiClientProps.serverId },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onError: (error, variables) => {
|
||||||
|
logFn.error('Add to playlist failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
message: error?.message,
|
||||||
|
playlistId: variables.query.id,
|
||||||
|
serverId: variables.apiClientProps.serverId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
options?.onError?.(error);
|
||||||
|
},
|
||||||
onSuccess: (_data, variables, context) => {
|
onSuccess: (_data, variables, context) => {
|
||||||
const { apiClientProps } = variables;
|
const { apiClientProps } = variables;
|
||||||
const serverId = apiClientProps.serverId;
|
const serverId = apiClientProps.serverId;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { AxiosError } from 'axios';
|
|||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { CreatePlaylistArgs, CreatePlaylistResponse } from '/@/shared/types/domain-types';
|
import { CreatePlaylistArgs, CreatePlaylistResponse } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export const useCreatePlaylist = (args: MutationHookArgs) => {
|
export const useCreatePlaylist = (args: MutationHookArgs) => {
|
||||||
@@ -17,6 +18,16 @@ export const useCreatePlaylist = (args: MutationHookArgs) => {
|
|||||||
apiClientProps: { serverId: args.apiClientProps.serverId },
|
apiClientProps: { serverId: args.apiClientProps.serverId },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onError: (error, variables) => {
|
||||||
|
logFn.error('Create playlist failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
message: error?.message,
|
||||||
|
serverId: variables.apiClientProps.serverId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
options?.onError?.(error);
|
||||||
|
},
|
||||||
onSuccess: (_args, variables) => {
|
onSuccess: (_args, variables) => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
exact: false,
|
exact: false,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
restorePlaylistQueryData,
|
restorePlaylistQueryData,
|
||||||
} from '/@/renderer/features/playlists/mutations/playlist-optimistic-updates';
|
} from '/@/renderer/features/playlists/mutations/playlist-optimistic-updates';
|
||||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { DeletePlaylistArgs, DeletePlaylistResponse } from '/@/shared/types/domain-types';
|
import { DeletePlaylistArgs, DeletePlaylistResponse } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export const useDeletePlaylist = (args: MutationHookArgs) => {
|
export const useDeletePlaylist = (args: MutationHookArgs) => {
|
||||||
@@ -24,6 +25,14 @@ export const useDeletePlaylist = (args: MutationHookArgs) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (_error, _variables, context) => {
|
onError: (_error, _variables, context) => {
|
||||||
|
logFn.error('Delete playlist failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
message: _error?.message,
|
||||||
|
playlistId: _variables.query.id,
|
||||||
|
serverId: _variables.apiClientProps.serverId,
|
||||||
|
},
|
||||||
|
});
|
||||||
if (context) {
|
if (context) {
|
||||||
restorePlaylistQueryData(queryClient, context);
|
restorePlaylistQueryData(queryClient, context);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { AxiosError } from 'axios';
|
|||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { MutationOptions } from '/@/renderer/lib/react-query';
|
import { MutationOptions } from '/@/renderer/lib/react-query';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { RemoveFromPlaylistArgs, RemoveFromPlaylistResponse } from '/@/shared/types/domain-types';
|
import { RemoveFromPlaylistArgs, RemoveFromPlaylistResponse } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export const useRemoveFromPlaylist = (options?: MutationOptions) => {
|
export const useRemoveFromPlaylist = (options?: MutationOptions) => {
|
||||||
@@ -16,6 +17,17 @@ export const useRemoveFromPlaylist = (options?: MutationOptions) => {
|
|||||||
apiClientProps: { serverId: args.apiClientProps.serverId },
|
apiClientProps: { serverId: args.apiClientProps.serverId },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onError: (error, variables) => {
|
||||||
|
logFn.error('Remove from playlist failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
message: error?.message,
|
||||||
|
playlistId: variables.query.id,
|
||||||
|
serverId: variables.apiClientProps.serverId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
options?.onError?.(error);
|
||||||
|
},
|
||||||
onSuccess: (_data, variables) => {
|
onSuccess: (_data, variables) => {
|
||||||
const { apiClientProps } = variables;
|
const { apiClientProps } = variables;
|
||||||
const serverId = apiClientProps.serverId;
|
const serverId = apiClientProps.serverId;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useRecentPlaylists } from '/@/renderer/features/playlists/hooks/use-recent-playlists';
|
import { useRecentPlaylists } from '/@/renderer/features/playlists/hooks/use-recent-playlists';
|
||||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||||
import { useCurrentServerId } from '/@/renderer/store';
|
import { useCurrentServerId } from '/@/renderer/store';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { ReplacePlaylistArgs, ReplacePlaylistResponse } from '/@/shared/types/domain-types';
|
import { ReplacePlaylistArgs, ReplacePlaylistResponse } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export const useReplacePlaylist = (args: MutationHookArgs) => {
|
export const useReplacePlaylist = (args: MutationHookArgs) => {
|
||||||
@@ -22,6 +23,17 @@ export const useReplacePlaylist = (args: MutationHookArgs) => {
|
|||||||
apiClientProps: { serverId: args.apiClientProps.serverId },
|
apiClientProps: { serverId: args.apiClientProps.serverId },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onError: (error, variables) => {
|
||||||
|
logFn.error('Replace playlist failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
message: error?.message,
|
||||||
|
playlistId: variables.query.id,
|
||||||
|
serverId: variables.apiClientProps.serverId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
options?.onError?.(error);
|
||||||
|
},
|
||||||
onSuccess: (_data, variables, context) => {
|
onSuccess: (_data, variables, context) => {
|
||||||
const { apiClientProps } = variables;
|
const { apiClientProps } = variables;
|
||||||
const serverId = apiClientProps.serverId;
|
const serverId = apiClientProps.serverId;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { AxiosError } from 'axios';
|
|||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { UpdatePlaylistArgs, UpdatePlaylistResponse } from '/@/shared/types/domain-types';
|
import { UpdatePlaylistArgs, UpdatePlaylistResponse } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export const useUpdatePlaylist = (args: MutationHookArgs) => {
|
export const useUpdatePlaylist = (args: MutationHookArgs) => {
|
||||||
@@ -17,6 +18,17 @@ export const useUpdatePlaylist = (args: MutationHookArgs) => {
|
|||||||
apiClientProps: { serverId: args.apiClientProps.serverId },
|
apiClientProps: { serverId: args.apiClientProps.serverId },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onError: (error, variables) => {
|
||||||
|
logFn.error('Update playlist failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
message: error?.message,
|
||||||
|
playlistId: variables.query?.id,
|
||||||
|
serverId: variables.apiClientProps.serverId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
options?.onError?.(error);
|
||||||
|
},
|
||||||
onSuccess: (_data, variables) => {
|
onSuccess: (_data, variables) => {
|
||||||
const { apiClientProps, query } = variables;
|
const { apiClientProps, query } = variables;
|
||||||
const serverId = apiClientProps.serverId;
|
const serverId = apiClientProps.serverId;
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ export const EditRadioStationForm = ({ onCancel, station }: EditRadioStationForm
|
|||||||
{
|
{
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
logFn.error('An error occurred', {
|
logFn.error('An error occurred', {
|
||||||
category: LogCategory.OTHER,
|
category: LogCategory.OTHER,
|
||||||
meta: { error: error as Error },
|
meta: { error: error as Error },
|
||||||
});
|
});
|
||||||
|
|
||||||
toast.error({
|
toast.error({
|
||||||
message: (error as Error).message,
|
message: (error as Error).message,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { AxiosError } from 'axios';
|
|||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import {
|
import {
|
||||||
CreateInternetRadioStationArgs,
|
CreateInternetRadioStationArgs,
|
||||||
CreateInternetRadioStationResponse,
|
CreateInternetRadioStationResponse,
|
||||||
@@ -25,6 +26,16 @@ export const useCreateRadioStation = (args: MutationHookArgs) => {
|
|||||||
apiClientProps: { serverId: args.apiClientProps.serverId },
|
apiClientProps: { serverId: args.apiClientProps.serverId },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onError: (error, variables) => {
|
||||||
|
logFn.error('Create radio station failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
message: error?.message,
|
||||||
|
serverId: variables.apiClientProps.serverId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
options?.onError?.(error);
|
||||||
|
},
|
||||||
onSuccess: (_args, variables) => {
|
onSuccess: (_args, variables) => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
exact: false,
|
exact: false,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { AxiosError } from 'axios';
|
|||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import {
|
import {
|
||||||
DeleteInternetRadioStationArgs,
|
DeleteInternetRadioStationArgs,
|
||||||
DeleteInternetRadioStationResponse,
|
DeleteInternetRadioStationResponse,
|
||||||
@@ -25,6 +26,17 @@ export const useDeleteRadioStation = (args: MutationHookArgs) => {
|
|||||||
apiClientProps: { serverId: args.apiClientProps.serverId },
|
apiClientProps: { serverId: args.apiClientProps.serverId },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onError: (error, variables) => {
|
||||||
|
logFn.error('Delete radio station failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
message: error?.message,
|
||||||
|
serverId: variables.apiClientProps.serverId,
|
||||||
|
stationId: variables.query?.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
options?.onError?.(error);
|
||||||
|
},
|
||||||
onSuccess: (_args, variables) => {
|
onSuccess: (_args, variables) => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
exact: false,
|
exact: false,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { AxiosError } from 'axios';
|
|||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import {
|
import {
|
||||||
UpdateInternetRadioStationArgs,
|
UpdateInternetRadioStationArgs,
|
||||||
UpdateInternetRadioStationResponse,
|
UpdateInternetRadioStationResponse,
|
||||||
@@ -25,6 +26,17 @@ export const useUpdateRadioStation = (args: MutationHookArgs) => {
|
|||||||
apiClientProps: { serverId: args.apiClientProps.serverId },
|
apiClientProps: { serverId: args.apiClientProps.serverId },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onError: (error, variables) => {
|
||||||
|
logFn.error('Update radio station failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
message: error?.message,
|
||||||
|
serverId: variables.apiClientProps.serverId,
|
||||||
|
stationId: variables.query?.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
options?.onError?.(error);
|
||||||
|
},
|
||||||
onSuccess: (_args, variables) => {
|
onSuccess: (_args, variables) => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
exact: false,
|
exact: false,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import NavidromeIcon from '/@/renderer/features/servers/assets/navidrome.png';
|
|||||||
import SubsonicIcon from '/@/renderer/features/servers/assets/opensubsonic.png';
|
import SubsonicIcon from '/@/renderer/features/servers/assets/opensubsonic.png';
|
||||||
import { IgnoreCorsSslSwitches } from '/@/renderer/features/servers/components/ignore-cors-ssl-switches';
|
import { IgnoreCorsSslSwitches } from '/@/renderer/features/servers/components/ignore-cors-ssl-switches';
|
||||||
import { useAuthStoreActions } from '/@/renderer/store';
|
import { useAuthStoreActions } from '/@/renderer/store';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { Checkbox } from '/@/shared/components/checkbox/checkbox';
|
import { Checkbox } from '/@/shared/components/checkbox/checkbox';
|
||||||
import { Divider } from '/@/shared/components/divider/divider';
|
import { Divider } from '/@/shared/components/divider/divider';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
@@ -149,6 +150,10 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
logFn.error('Add server failed (no data returned)', {
|
||||||
|
category: LogCategory.SYSTEM,
|
||||||
|
meta: { name: values.name, serverType: values.type, url: values.url },
|
||||||
|
});
|
||||||
return toast.error({
|
return toast.error({
|
||||||
message: t('error.authenticationFailed', { postProcess: 'sentenceCase' }),
|
message: t('error.authenticationFailed', { postProcess: 'sentenceCase' }),
|
||||||
});
|
});
|
||||||
@@ -189,6 +194,10 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
|
|||||||
setCurrentServer(serverItem);
|
setCurrentServer(serverItem);
|
||||||
closeAllModals();
|
closeAllModals();
|
||||||
|
|
||||||
|
logFn.info('Add server successful', {
|
||||||
|
category: LogCategory.SYSTEM,
|
||||||
|
meta: { name: values.name, serverId: serverItem.id, serverType: values.type, url: values.url },
|
||||||
|
});
|
||||||
toast.success({
|
toast.success({
|
||||||
message: t('form.addServer.success', { postProcess: 'sentenceCase' }),
|
message: t('form.addServer.success', { postProcess: 'sentenceCase' }),
|
||||||
});
|
});
|
||||||
@@ -205,6 +214,10 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
logFn.error('Add server failed', {
|
||||||
|
category: LogCategory.SYSTEM,
|
||||||
|
meta: { message: err?.message, name: values.name, serverType: values.type, url: values.url },
|
||||||
|
});
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return toast.error({ message: err?.message });
|
return toast.error({ message: err?.message });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { Box } from '/@/shared/components/box/box';
|
import { Box } from '/@/shared/components/box/box';
|
||||||
import { Button } from '/@/shared/components/button/button';
|
import { Button } from '/@/shared/components/button/button';
|
||||||
import { Center } from '/@/shared/components/center/center';
|
import { Center } from '/@/shared/components/center/center';
|
||||||
@@ -43,5 +44,22 @@ interface ComponentErrorBoundaryProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ComponentErrorBoundary = ({ children }: ComponentErrorBoundaryProps) => {
|
export const ComponentErrorBoundary = ({ children }: ComponentErrorBoundaryProps) => {
|
||||||
return <ErrorBoundary FallbackComponent={ComponentErrorFallback}>{children}</ErrorBoundary>;
|
return (
|
||||||
|
<ErrorBoundary
|
||||||
|
FallbackComponent={ComponentErrorFallback}
|
||||||
|
onError={(error, errorInfo) => {
|
||||||
|
logFn.error('Component error boundary caught an error', {
|
||||||
|
category: LogCategory.OTHER,
|
||||||
|
meta: {
|
||||||
|
componentStack: errorInfo?.componentStack,
|
||||||
|
message: error?.message,
|
||||||
|
name: error?.name,
|
||||||
|
stack: error?.stack,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ErrorBoundary } from 'react-error-boundary';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { ServerSelector } from '/@/renderer/features/sidebar/components/server-selector';
|
import { ServerSelector } from '/@/renderer/features/sidebar/components/server-selector';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { Box } from '/@/shared/components/box/box';
|
import { Box } from '/@/shared/components/box/box';
|
||||||
import { Button } from '/@/shared/components/button/button';
|
import { Button } from '/@/shared/components/button/button';
|
||||||
import { Center } from '/@/shared/components/center/center';
|
import { Center } from '/@/shared/components/center/center';
|
||||||
@@ -85,9 +86,15 @@ export const PageErrorBoundary = ({ children }: PageErrorBoundaryProps) => {
|
|||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
FallbackComponent={PageErrorFallback}
|
FallbackComponent={PageErrorFallback}
|
||||||
onError={(error, errorInfo) => {
|
onError={(error, errorInfo) => {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
logFn.error('Page error boundary caught an error', {
|
||||||
console.error('Page error boundary caught an error:', error, errorInfo);
|
category: LogCategory.OTHER,
|
||||||
}
|
meta: {
|
||||||
|
componentStack: errorInfo?.componentStack,
|
||||||
|
message: error?.message,
|
||||||
|
name: error?.name,
|
||||||
|
stack: error?.stack,
|
||||||
|
},
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
onReset={() => {}}
|
onReset={() => {}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ErrorBoundary } from 'react-error-boundary';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { ServerSelector } from '/@/renderer/features/sidebar/components/server-selector';
|
import { ServerSelector } from '/@/renderer/features/sidebar/components/server-selector';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { Box } from '/@/shared/components/box/box';
|
import { Box } from '/@/shared/components/box/box';
|
||||||
import { Button } from '/@/shared/components/button/button';
|
import { Button } from '/@/shared/components/button/button';
|
||||||
import { Center } from '/@/shared/components/center/center';
|
import { Center } from '/@/shared/components/center/center';
|
||||||
@@ -91,9 +92,15 @@ export const RouterErrorBoundary = ({ children }: RouterErrorBoundaryProps) => {
|
|||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
FallbackComponent={RouterErrorFallback}
|
FallbackComponent={RouterErrorFallback}
|
||||||
onError={(error, errorInfo) => {
|
onError={(error, errorInfo) => {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
logFn.error('Router error boundary caught an error', {
|
||||||
console.error('Root error boundary caught an error:', error, errorInfo);
|
category: LogCategory.OTHER,
|
||||||
}
|
meta: {
|
||||||
|
componentStack: errorInfo?.componentStack,
|
||||||
|
message: error?.message,
|
||||||
|
name: error?.name,
|
||||||
|
stack: error?.stack,
|
||||||
|
},
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
onReset={() => {}}
|
onReset={() => {}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
restoreFavoriteQueryData,
|
restoreFavoriteQueryData,
|
||||||
} from '/@/renderer/features/shared/mutations/favorite-optimistic-updates';
|
} from '/@/renderer/features/shared/mutations/favorite-optimistic-updates';
|
||||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { toast } from '/@/shared/components/toast/toast';
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
import { FavoriteArgs, FavoriteResponse, LibraryItem } from '/@/shared/types/domain-types';
|
import { FavoriteArgs, FavoriteResponse, LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
@@ -33,6 +34,15 @@ export const useCreateFavorite = (args: MutationHookArgs) => {
|
|||||||
},
|
},
|
||||||
mutationKey: createFavoriteMutationKey,
|
mutationKey: createFavoriteMutationKey,
|
||||||
onError: (_error, variables, context) => {
|
onError: (_error, variables, context) => {
|
||||||
|
logFn.error('Create favorite failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
id: variables.query.id,
|
||||||
|
message: _error?.message,
|
||||||
|
serverId: variables.apiClientProps.serverId,
|
||||||
|
type: variables.query.type,
|
||||||
|
},
|
||||||
|
});
|
||||||
if (context) {
|
if (context) {
|
||||||
restoreFavoriteQueryData(queryClient, context);
|
restoreFavoriteQueryData(queryClient, context);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
restoreFavoriteQueryData,
|
restoreFavoriteQueryData,
|
||||||
} from '/@/renderer/features/shared/mutations/favorite-optimistic-updates';
|
} from '/@/renderer/features/shared/mutations/favorite-optimistic-updates';
|
||||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { toast } from '/@/shared/components/toast/toast';
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
import { FavoriteArgs, FavoriteResponse, LibraryItem } from '/@/shared/types/domain-types';
|
import { FavoriteArgs, FavoriteResponse, LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
@@ -33,6 +34,15 @@ export const useDeleteFavorite = (args: MutationHookArgs) => {
|
|||||||
},
|
},
|
||||||
mutationKey: deleteFavoriteMutationKey,
|
mutationKey: deleteFavoriteMutationKey,
|
||||||
onError: (_error, _variables, context) => {
|
onError: (_error, _variables, context) => {
|
||||||
|
logFn.error('Delete favorite failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
id: _variables.query.id,
|
||||||
|
message: _error?.message,
|
||||||
|
serverId: _variables.apiClientProps.serverId,
|
||||||
|
type: _variables.query.type,
|
||||||
|
},
|
||||||
|
});
|
||||||
if (context) {
|
if (context) {
|
||||||
restoreFavoriteQueryData(queryClient, context);
|
restoreFavoriteQueryData(queryClient, context);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
restoreRatingQueryData,
|
restoreRatingQueryData,
|
||||||
} from '/@/renderer/features/shared/mutations/rating-optimistic-updates';
|
} from '/@/renderer/features/shared/mutations/rating-optimistic-updates';
|
||||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { toast } from '/@/shared/components/toast/toast';
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
import { LibraryItem, RatingResponse, SetRatingArgs } from '/@/shared/types/domain-types';
|
import { LibraryItem, RatingResponse, SetRatingArgs } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
@@ -30,6 +31,16 @@ export const useSetRatingMutation = (args: MutationHookArgs) => {
|
|||||||
},
|
},
|
||||||
mutationKey: setRatingMutationKey,
|
mutationKey: setRatingMutationKey,
|
||||||
onError: (_error, _variables, context) => {
|
onError: (_error, _variables, context) => {
|
||||||
|
logFn.error('Set rating failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
id: _variables.query.id,
|
||||||
|
message: _error?.message,
|
||||||
|
rating: _variables.query.rating,
|
||||||
|
serverId: _variables.apiClientProps.serverId,
|
||||||
|
type: _variables.query.type,
|
||||||
|
},
|
||||||
|
});
|
||||||
if (context) {
|
if (context) {
|
||||||
restoreRatingQueryData(queryClient, context);
|
restoreRatingQueryData(queryClient, context);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { AxiosError } from 'axios';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { AnyLibraryItems, ShareItemArgs, ShareItemResponse } from '/@/shared/types/domain-types';
|
import { AnyLibraryItems, ShareItemArgs, ShareItemResponse } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export const useShareItem = (args: MutationHookArgs) => {
|
export const useShareItem = (args: MutationHookArgs) => {
|
||||||
@@ -20,6 +21,17 @@ export const useShareItem = (args: MutationHookArgs) => {
|
|||||||
apiClientProps: { serverId: args.apiClientProps.serverId },
|
apiClientProps: { serverId: args.apiClientProps.serverId },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onError: (error, variables) => {
|
||||||
|
logFn.error('Share item failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
itemType: variables.body?.resourceType,
|
||||||
|
message: error?.message,
|
||||||
|
serverId: variables.apiClientProps.serverId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
options?.onError?.(error);
|
||||||
|
},
|
||||||
retry: false,
|
retry: false,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
|
|
||||||
const CHECK_FOR_UPDATES_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
const CHECK_FOR_UPDATES_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
||||||
|
|
||||||
const utils = isElectron() ? window.api?.utils : null;
|
const utils = isElectron() ? window.api?.utils : null;
|
||||||
@@ -21,7 +23,17 @@ export const useCheckForUpdates = () => {
|
|||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
enabled: isEnabled,
|
enabled: isEnabled,
|
||||||
queryFn: () => utils?.checkForUpdates?.(),
|
queryFn: async () => {
|
||||||
|
const result = await utils?.checkForUpdates?.();
|
||||||
|
logFn.info('Check for updates completed', {
|
||||||
|
category: LogCategory.SYSTEM,
|
||||||
|
meta: {
|
||||||
|
updateAvailable: result?.updateAvailable ?? false,
|
||||||
|
version: result?.version,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
},
|
||||||
queryKey: ['app-check-for-updates'],
|
queryKey: ['app-check-for-updates'],
|
||||||
refetchInterval: CHECK_FOR_UPDATES_INTERVAL_MS,
|
refetchInterval: CHECK_FOR_UPDATES_INTERVAL_MS,
|
||||||
refetchIntervalInBackground: true,
|
refetchIntervalInBackground: true,
|
||||||
|
|||||||
@@ -345,7 +345,16 @@ export const useServerAuthenticated = () => {
|
|||||||
|
|
||||||
const debouncedAuth = debounce(
|
const debouncedAuth = debounce(
|
||||||
(serverWithAuth: NonNullable<ReturnType<typeof getServerById>>) => {
|
(serverWithAuth: NonNullable<ReturnType<typeof getServerById>>) => {
|
||||||
authenticateServer(serverWithAuth).catch(console.error);
|
authenticateServer(serverWithAuth).catch((err) => {
|
||||||
|
logFn.error('Server authentication failed (debounced)', {
|
||||||
|
category: LogCategory.SYSTEM,
|
||||||
|
meta: {
|
||||||
|
message: (err as Error)?.message,
|
||||||
|
serverId: serverWithAuth.id,
|
||||||
|
serverName: serverWithAuth.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
300,
|
300,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,12 +8,19 @@ import type {
|
|||||||
|
|
||||||
import { QueryCache, QueryClient } from '@tanstack/react-query';
|
import { QueryCache, QueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { toast } from '/@/shared/components/toast/toast';
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
|
|
||||||
const queryCache = new QueryCache({
|
const queryCache = new QueryCache({
|
||||||
onError: (error: any, query) => {
|
onError: (error: any, query) => {
|
||||||
|
logFn.error('Query failed', {
|
||||||
|
category: LogCategory.API,
|
||||||
|
meta: {
|
||||||
|
message: error?.message,
|
||||||
|
queryKey: query.queryKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
if (query.state.data !== undefined) {
|
if (query.state.data !== undefined) {
|
||||||
console.error(error);
|
|
||||||
toast.show({ message: `${error.message}`, type: 'error' });
|
toast.show({ message: `${error.message}`, type: 'error' });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import packageJson from '../../package.json';
|
import packageJson from '../../package.json';
|
||||||
|
|
||||||
import { formatHrDateTime } from '/@/renderer/utils/format';
|
import { formatHrDateTime } from '/@/renderer/utils/format';
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { Button } from '/@/shared/components/button/button';
|
import { Button } from '/@/shared/components/button/button';
|
||||||
import { Center } from '/@/shared/components/center/center';
|
import { Center } from '/@/shared/components/center/center';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
@@ -70,10 +71,22 @@ const ReleaseNotesContent = ({ onDismiss, version }: ReleaseNotesContentProps) =
|
|||||||
// Fetch list of recent releases for the selector
|
// Fetch list of recent releases for the selector
|
||||||
const { data: releasesList = [] } = useQuery({
|
const { data: releasesList = [] } = useQuery({
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await axios.get<GitHubRelease[]>(GITHUB_RELEASES_URL, {
|
try {
|
||||||
params: { per_page: RELEASES_TO_FETCH },
|
const response = await axios.get<GitHubRelease[]>(GITHUB_RELEASES_URL, {
|
||||||
});
|
params: { per_page: RELEASES_TO_FETCH },
|
||||||
return response.data;
|
});
|
||||||
|
logFn.info('Release notes fetched', {
|
||||||
|
category: LogCategory.GENERAL,
|
||||||
|
meta: { count: response.data?.length ?? 0 },
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
logFn.error('Release notes fetch failed', {
|
||||||
|
category: LogCategory.GENERAL,
|
||||||
|
meta: { message: (error as Error)?.message },
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
queryKey: ['github-releases-list'],
|
queryKey: ['github-releases-list'],
|
||||||
retry: 2,
|
retry: 2,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { immer } from 'zustand/middleware/immer';
|
|||||||
import { shallow } from 'zustand/shallow';
|
import { shallow } from 'zustand/shallow';
|
||||||
import { createWithEqualityFn } from 'zustand/traditional';
|
import { createWithEqualityFn } from 'zustand/traditional';
|
||||||
|
|
||||||
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||||
import { ServerListItem, ServerListItemWithCredential } from '/@/shared/types/domain-types';
|
import { ServerListItem, ServerListItemWithCredential } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export interface AuthSlice extends AuthState {
|
export interface AuthSlice extends AuthState {
|
||||||
@@ -30,6 +31,16 @@ export const useAuthStore = createWithEqualityFn<AuthSlice>()(
|
|||||||
immer((set, get) => ({
|
immer((set, get) => ({
|
||||||
actions: {
|
actions: {
|
||||||
addServer: (args) => {
|
addServer: (args) => {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
logFn.debug('Auth store: add server', {
|
||||||
|
category: LogCategory.SYSTEM,
|
||||||
|
meta: {
|
||||||
|
serverId: args.id,
|
||||||
|
serverName: args.name,
|
||||||
|
serverType: args.type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.serverList[args.id] = args;
|
state.serverList[args.id] = args;
|
||||||
});
|
});
|
||||||
@@ -49,6 +60,15 @@ export const useAuthStore = createWithEqualityFn<AuthSlice>()(
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
setCurrentServer: (server) => {
|
setCurrentServer: (server) => {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
logFn.debug('Auth store: set current server', {
|
||||||
|
category: LogCategory.SYSTEM,
|
||||||
|
meta: {
|
||||||
|
serverId: server?.id ?? null,
|
||||||
|
serverName: server?.name ?? null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.currentServer = server;
|
state.currentServer = server;
|
||||||
});
|
});
|
||||||
@@ -65,6 +85,12 @@ export const useAuthStore = createWithEqualityFn<AuthSlice>()(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateServer: (id: string, args: Partial<ServerListItemWithCredential>) => {
|
updateServer: (id: string, args: Partial<ServerListItemWithCredential>) => {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
logFn.debug('Auth store: update server', {
|
||||||
|
category: LogCategory.SYSTEM,
|
||||||
|
meta: { keys: Object.keys(args || {}), serverId: id },
|
||||||
|
});
|
||||||
|
}
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const updatedServer = {
|
const updatedServer = {
|
||||||
...state.serverList[id],
|
...state.serverList[id],
|
||||||
|
|||||||
Reference in New Issue
Block a user