mirror of
https://github.com/unraid/api.git
synced 2026-01-04 23:50:37 -06:00
fix: duplicate notification subscription & toast
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
"script": "./dist/main.js",
|
||||
"cwd": "/usr/local/unraid-api",
|
||||
"interpreter": "bun",
|
||||
"interpreter_args": "--smol",
|
||||
"exec_mode": "fork",
|
||||
"wait_ready": true,
|
||||
"listen_timeout": 15000,
|
||||
|
||||
44
api/justfile
44
api/justfile
@@ -16,6 +16,50 @@ setup:
|
||||
@deploy:
|
||||
./scripts/deploy-dev.sh
|
||||
|
||||
alias r:= restart
|
||||
|
||||
# restarts the api on {target_server}
|
||||
restart target_server="" :
|
||||
#!/usr/bin/env bash
|
||||
# Path to store the last used server name
|
||||
state_file="$HOME/.deploy_state"
|
||||
|
||||
# Read the last used server name from the state file
|
||||
if [[ -f "$state_file" ]]; then
|
||||
last_server_name=$(cat "$state_file")
|
||||
else
|
||||
last_server_name=""
|
||||
fi
|
||||
|
||||
# Read the server name from the command-line argument or use the last used server name as the default
|
||||
server_name=$([ "{{ target_server }}" = "" ] && echo "$last_server_name" || echo ""{{ target_server }}"")
|
||||
|
||||
# Check if the server name is provided
|
||||
if [[ -z "$server_name" ]]; then
|
||||
echo "Please provide the SSH server name."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Save the current server name to the state file
|
||||
echo "$server_name" > "$state_file"
|
||||
# Run unraid-api restart on remote host
|
||||
ssh root@"${server_name}" "LOG_LEVEL=trace unraid-api restart"
|
||||
|
||||
# build & deploy
|
||||
bd: build deploy
|
||||
|
||||
# plays an os-specific bell
|
||||
[no-cd]
|
||||
chime:
|
||||
#!/usr/bin/env bash
|
||||
# Play built-in sound based on the operating system
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS
|
||||
afplay /System/Library/Sounds/Glass.aiff
|
||||
elif [[ "$OSTYPE" == "linux-gnu" ]]; then
|
||||
# Linux
|
||||
paplay /usr/share/sounds/freedesktop/stereo/complete.oga
|
||||
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
|
||||
# Windows
|
||||
powershell.exe -c "(New-Object Media.SoundPlayer 'C:\Windows\Media\Windows Default.wav').PlaySync()"
|
||||
fi
|
||||
|
||||
@@ -48,21 +48,6 @@ exit_code=$?
|
||||
# Chown the directory
|
||||
ssh root@"${server_name}" "chown -R root:root /usr/local/unraid-api"
|
||||
|
||||
# Run unraid-api restart on remote host
|
||||
ssh root@"${server_name}" "INTROSPECTION=true LOG_LEVEL=trace unraid-api restart"
|
||||
|
||||
# Play built-in sound based on the operating system
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS
|
||||
afplay /System/Library/Sounds/Glass.aiff
|
||||
elif [[ "$OSTYPE" == "linux-gnu" ]]; then
|
||||
# Linux
|
||||
paplay /usr/share/sounds/freedesktop/stereo/complete.oga
|
||||
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
|
||||
# Windows
|
||||
powershell.exe -c "(New-Object Media.SoundPlayer 'C:\Windows\Media\Windows Default.wav').PlaySync()"
|
||||
fi
|
||||
|
||||
# Exit with the rsync command's exit code
|
||||
exit $exit_code
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ export const viteNodeApp = async () => {
|
||||
},
|
||||
{ wait: 9999 }
|
||||
);
|
||||
await new Promise(() => {});
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
logger.error(error, 'API-ERROR');
|
||||
|
||||
@@ -86,8 +86,11 @@ export class GraphqlAuthGuard
|
||||
|
||||
// parse cookies from raw headers on initial web socket connection request
|
||||
if (fullContext.connectionParams) {
|
||||
console.log(JSON.stringify(fullContext.req, null, 2));
|
||||
const rawHeaders = fullContext.req.extra.request.rawHeaders;
|
||||
const headerIndex = rawHeaders.findIndex((headerOrValue) => headerOrValue === 'Cookie');
|
||||
const headerIndex = rawHeaders.findIndex(
|
||||
(headerOrValue) => headerOrValue.toLowerCase() === 'cookie'
|
||||
);
|
||||
const cookieString = rawHeaders[headerIndex + 1];
|
||||
request.cookies = parseCookies(cookieString);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DeveloperCommand } from '@app/unraid-api/cli/developer/developer.comman
|
||||
import { DeveloperQuestions } from '@app/unraid-api/cli/developer/developer.questions';
|
||||
import { LogService } from '@app/unraid-api/cli/log.service';
|
||||
import { LogsCommand } from '@app/unraid-api/cli/logs.command';
|
||||
import { PM2Service } from '@app/unraid-api/cli/pm2.service';
|
||||
import { ReportCommand } from '@app/unraid-api/cli/report.command';
|
||||
import { RestartCommand } from '@app/unraid-api/cli/restart.command';
|
||||
import { AddSSOUserCommand } from '@app/unraid-api/cli/sso/add-sso-user.command';
|
||||
@@ -31,6 +32,7 @@ import { VersionCommand } from '@app/unraid-api/cli/version.command';
|
||||
RemoveSSOUserQuestionSet,
|
||||
ListSSOUserCommand,
|
||||
LogService,
|
||||
PM2Service,
|
||||
StartCommand,
|
||||
StopCommand,
|
||||
RestartCommand,
|
||||
|
||||
47
api/src/unraid-api/cli/pm2.service.ts
Normal file
47
api/src/unraid-api/cli/pm2.service.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import type { Options, Result } from 'execa';
|
||||
import { execa } from 'execa';
|
||||
|
||||
import { LogService } from '@app/unraid-api/cli/log.service';
|
||||
|
||||
type CmdContext = {
|
||||
tag: string;
|
||||
env?: Record<string, string>;
|
||||
/** Default: false.
|
||||
*
|
||||
* When true, results will not be automatically handled and logged.
|
||||
* The caller must handle desired effects.
|
||||
*/
|
||||
raw?: boolean;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class PM2Service {
|
||||
constructor(private readonly logger: LogService) {}
|
||||
|
||||
/**
|
||||
* Executes a PM2 command with the provided arguments and environment variables.
|
||||
*
|
||||
* @param context - An object containing a tag for logging purposes and optional environment variables (merging with current env).
|
||||
* @param args - Arguments to pass to the PM2 command. Each arguement is escaped.
|
||||
* @returns A promise that resolves to a Result object containing the command's output.
|
||||
* Logs debug information on success and error details on failure.
|
||||
*/
|
||||
async run(context: CmdContext, ...args: string[]) {
|
||||
const { tag, env, raw = false } = context;
|
||||
const runCommand = () => execa('bun', ['x', 'pm2', ...args], { env } satisfies Options);
|
||||
if (raw) {
|
||||
return runCommand();
|
||||
}
|
||||
return runCommand()
|
||||
.then((result) => {
|
||||
this.logger.debug(result.stdout);
|
||||
return result;
|
||||
})
|
||||
.catch((result: Result) => {
|
||||
this.logger.error(`PM2 error occurred from tag "${tag}": ${result.stdout}\n`);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import type { LogLevel } from '@app/core/log';
|
||||
import { ECOSYSTEM_PATH, PM2_PATH } from '@app/consts';
|
||||
import { levels } from '@app/core/log';
|
||||
import { LogService } from '@app/unraid-api/cli/log.service';
|
||||
import { PM2Service } from '@app/unraid-api/cli/pm2.service';
|
||||
|
||||
interface StartCommandOptions {
|
||||
'log-level'?: string;
|
||||
@@ -12,38 +13,38 @@ interface StartCommandOptions {
|
||||
|
||||
@Command({ name: 'start' })
|
||||
export class StartCommand extends CommandRunner {
|
||||
constructor(private readonly logger: LogService) {
|
||||
constructor(
|
||||
private readonly logger: LogService,
|
||||
private readonly pm2: PM2Service
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async cleanupPM2State() {
|
||||
await execa(PM2_PATH, ['stop', ECOSYSTEM_PATH])
|
||||
.then(({ stdout }) => this.logger.debug(stdout))
|
||||
.catch(({ stderr }) => this.logger.error('PM2 Stop Error: ' + stderr));
|
||||
await execa(PM2_PATH, ['delete', ECOSYSTEM_PATH])
|
||||
.then(({ stdout }) => this.logger.debug(stdout))
|
||||
.catch(({ stderr }) => this.logger.error('PM2 Delete API Error: ' + stderr));
|
||||
|
||||
// Update PM2
|
||||
await execa(PM2_PATH, ['update'])
|
||||
.then(({ stdout }) => this.logger.debug(stdout))
|
||||
.catch(({ stderr }) => this.logger.error('PM2 Update Error: ' + stderr));
|
||||
await this.pm2.run({ tag: 'PM2 Stop' }, 'stop', ECOSYSTEM_PATH);
|
||||
await this.pm2.run({ tag: 'PM2 Delete' }, 'delete', ECOSYSTEM_PATH);
|
||||
await this.pm2.run({ tag: 'PM2 Update' }, 'update');
|
||||
}
|
||||
|
||||
async run(_: string[], options: StartCommandOptions): Promise<void> {
|
||||
this.logger.info('Starting the Unraid API');
|
||||
await this.cleanupPM2State();
|
||||
const envLog = options['log-level'] ? `LOG_LEVEL=${options['log-level']}` : '';
|
||||
const { stderr, stdout } = await execa(`${envLog} ${PM2_PATH}`.trim(), [
|
||||
|
||||
const env: Record<string, string> = {};
|
||||
if (options['log-level']) {
|
||||
env.LOG_LEVEL = options['log-level'];
|
||||
}
|
||||
const { stderr, stdout } = await this.pm2.run(
|
||||
{ tag: 'PM2 Start', env, raw: true },
|
||||
'start',
|
||||
ECOSYSTEM_PATH,
|
||||
'--update-env',
|
||||
]);
|
||||
'--update-env'
|
||||
);
|
||||
if (stdout) {
|
||||
this.logger.log(stdout);
|
||||
this.logger.log(stdout.toString());
|
||||
}
|
||||
if (stderr) {
|
||||
this.logger.error(stderr);
|
||||
this.logger.error(stderr.toString());
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(0);
|
||||
|
||||
@@ -103,10 +103,12 @@ export class NotificationsService {
|
||||
const notification = await this.loadNotificationFile(path, NotificationType[type]);
|
||||
this.increment(notification.importance, NotificationsService.overview[type.toLowerCase()]);
|
||||
|
||||
this.publishOverview();
|
||||
pubsub.publish(PUBSUB_CHANNEL.NOTIFICATION_ADDED, {
|
||||
notificationAdded: notification,
|
||||
});
|
||||
if (type === NotificationType.UNREAD) {
|
||||
this.publishOverview();
|
||||
pubsub.publish(PUBSUB_CHANNEL.NOTIFICATION_ADDED, {
|
||||
notificationAdded: notification,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,6 +42,7 @@ const { onResult: onNotificationAdded } = useSubscription(notificationAddedSubsc
|
||||
onNotificationAdded(({ data }) => {
|
||||
if (!data) return;
|
||||
const notif = useFragment(NOTIFICATION_FRAGMENT, data.notificationAdded);
|
||||
if (notif.type !== NotificationType.Unread) return;
|
||||
|
||||
// probably smart to leave this log outside the if-block for the initial release
|
||||
console.log('incoming notification', notif);
|
||||
|
||||
Reference in New Issue
Block a user