mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
feat(notifications): native support for pushover
This commit is contained in:
@@ -64,6 +64,12 @@ const defaultValuesForType = {
|
|||||||
topic: "",
|
topic: "",
|
||||||
priority: "default" as const,
|
priority: "default" as const,
|
||||||
},
|
},
|
||||||
|
pushover: {
|
||||||
|
type: "pushover" as const,
|
||||||
|
userKey: "",
|
||||||
|
apiToken: "",
|
||||||
|
priority: 0,
|
||||||
|
},
|
||||||
custom: {
|
custom: {
|
||||||
type: "custom" as const,
|
type: "custom" as const,
|
||||||
shoutrrrUrl: "",
|
shoutrrrUrl: "",
|
||||||
@@ -141,6 +147,7 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue
|
|||||||
<SelectItem value="discord">Discord</SelectItem>
|
<SelectItem value="discord">Discord</SelectItem>
|
||||||
<SelectItem value="gotify">Gotify</SelectItem>
|
<SelectItem value="gotify">Gotify</SelectItem>
|
||||||
<SelectItem value="ntfy">Ntfy</SelectItem>
|
<SelectItem value="ntfy">Ntfy</SelectItem>
|
||||||
|
<SelectItem value="pushover">Pushover</SelectItem>
|
||||||
<SelectItem value="custom">Custom (Shoutrrr URL)</SelectItem>
|
<SelectItem value="custom">Custom (Shoutrrr URL)</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
@@ -490,6 +497,80 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{watchedType === "pushover" && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="userKey"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>User Key</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} placeholder="uQiRzpo4DXghDmr9QzzfQu27cmVRsG" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Your Pushover user key from the dashboard.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="apiToken"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>API Token</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} type="password" placeholder="••••••••" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Application API token from your Pushover application.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="devices"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Devices (Optional)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} placeholder="iphone,android" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Comma-separated list of device names. Leave empty for all devices.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="priority"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Priority</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={(value) => field.onChange(Number(value))}
|
||||||
|
defaultValue={String(field.value)}
|
||||||
|
value={String(field.value)}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select priority" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="-1">Low (-1)</SelectItem>
|
||||||
|
<SelectItem value="0">Normal (0)</SelectItem>
|
||||||
|
<SelectItem value="1">High (1)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormDescription>Message priority level.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{watchedType === "custom" && (
|
{watchedType === "custom" && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -513,7 +594,7 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue
|
|||||||
>
|
>
|
||||||
Shoutrrr documentation
|
Shoutrrr documentation
|
||||||
</a>
|
</a>
|
||||||
for supported services and URL formats.
|
for supported services and URL formats.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ export default function Notifications({ loaderData }: Route.ComponentProps) {
|
|||||||
<SelectItem value="discord">Discord</SelectItem>
|
<SelectItem value="discord">Discord</SelectItem>
|
||||||
<SelectItem value="gotify">Gotify</SelectItem>
|
<SelectItem value="gotify">Gotify</SelectItem>
|
||||||
<SelectItem value="ntfy">Ntfy</SelectItem>
|
<SelectItem value="ntfy">Ntfy</SelectItem>
|
||||||
|
<SelectItem value="pushover">Pushover</SelectItem>
|
||||||
<SelectItem value="custom">Custom</SelectItem>
|
<SelectItem value="custom">Custom</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const NOTIFICATION_TYPES = {
|
|||||||
discord: "discord",
|
discord: "discord",
|
||||||
gotify: "gotify",
|
gotify: "gotify",
|
||||||
ntfy: "ntfy",
|
ntfy: "ntfy",
|
||||||
|
pushover: "pushover",
|
||||||
custom: "custom",
|
custom: "custom",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -52,6 +53,14 @@ export const ntfyNotificationConfigSchema = type({
|
|||||||
priority: "'max' | 'high' | 'default' | 'low' | 'min'",
|
priority: "'max' | 'high' | 'default' | 'low' | 'min'",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const pushoverNotificationConfigSchema = type({
|
||||||
|
type: "'pushover'",
|
||||||
|
userKey: "string",
|
||||||
|
apiToken: "string",
|
||||||
|
devices: "string?",
|
||||||
|
priority: "-1 | 0 | 1",
|
||||||
|
});
|
||||||
|
|
||||||
export const customNotificationConfigSchema = type({
|
export const customNotificationConfigSchema = type({
|
||||||
type: "'custom'",
|
type: "'custom'",
|
||||||
shoutrrrUrl: "string",
|
shoutrrrUrl: "string",
|
||||||
@@ -62,6 +71,7 @@ export const notificationConfigSchema = emailNotificationConfigSchema
|
|||||||
.or(discordNotificationConfigSchema)
|
.or(discordNotificationConfigSchema)
|
||||||
.or(gotifyNotificationConfigSchema)
|
.or(gotifyNotificationConfigSchema)
|
||||||
.or(ntfyNotificationConfigSchema)
|
.or(ntfyNotificationConfigSchema)
|
||||||
|
.or(pushoverNotificationConfigSchema)
|
||||||
.or(customNotificationConfigSchema);
|
.or(customNotificationConfigSchema);
|
||||||
|
|
||||||
export type NotificationConfig = typeof notificationConfigSchema.infer;
|
export type NotificationConfig = typeof notificationConfigSchema.infer;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { buildSlackShoutrrrUrl } from "./slack";
|
|||||||
import { buildDiscordShoutrrrUrl } from "./discord";
|
import { buildDiscordShoutrrrUrl } from "./discord";
|
||||||
import { buildGotifyShoutrrrUrl } from "./gotify";
|
import { buildGotifyShoutrrrUrl } from "./gotify";
|
||||||
import { buildNtfyShoutrrrUrl } from "./ntfy";
|
import { buildNtfyShoutrrrUrl } from "./ntfy";
|
||||||
|
import { buildPushoverShoutrrrUrl } from "./pushover";
|
||||||
import { buildCustomShoutrrrUrl } from "./custom";
|
import { buildCustomShoutrrrUrl } from "./custom";
|
||||||
|
|
||||||
export function buildShoutrrrUrl(config: NotificationConfig): string {
|
export function buildShoutrrrUrl(config: NotificationConfig): string {
|
||||||
@@ -18,6 +19,8 @@ export function buildShoutrrrUrl(config: NotificationConfig): string {
|
|||||||
return buildGotifyShoutrrrUrl(config);
|
return buildGotifyShoutrrrUrl(config);
|
||||||
case "ntfy":
|
case "ntfy":
|
||||||
return buildNtfyShoutrrrUrl(config);
|
return buildNtfyShoutrrrUrl(config);
|
||||||
|
case "pushover":
|
||||||
|
return buildPushoverShoutrrrUrl(config);
|
||||||
case "custom":
|
case "custom":
|
||||||
return buildCustomShoutrrrUrl(config);
|
return buildCustomShoutrrrUrl(config);
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
24
app/server/modules/notifications/builders/pushover.ts
Normal file
24
app/server/modules/notifications/builders/pushover.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { NotificationConfig } from "~/schemas/notifications";
|
||||||
|
|
||||||
|
export function buildPushoverShoutrrrUrl(
|
||||||
|
config: Extract<NotificationConfig, { type: "pushover" }>,
|
||||||
|
): string {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (config.devices) {
|
||||||
|
params.append("devices", config.devices);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.priority !== undefined) {
|
||||||
|
params.append("priority", config.priority.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = params.toString();
|
||||||
|
let shoutrrrUrl = `pushover://shoutrrr:${config.apiToken}@${config.userKey}/`;
|
||||||
|
|
||||||
|
if (queryString) {
|
||||||
|
shoutrrrUrl += `?${queryString}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shoutrrrUrl;
|
||||||
|
}
|
||||||
@@ -60,6 +60,11 @@ async function encryptSensitiveFields(config: NotificationConfig): Promise<Notif
|
|||||||
...config,
|
...config,
|
||||||
token: config.token ? await cryptoUtils.encrypt(config.token) : undefined,
|
token: config.token ? await cryptoUtils.encrypt(config.token) : undefined,
|
||||||
};
|
};
|
||||||
|
case "pushover":
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
apiToken: await cryptoUtils.encrypt(config.apiToken),
|
||||||
|
};
|
||||||
case "custom":
|
case "custom":
|
||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
@@ -97,6 +102,11 @@ async function decryptSensitiveFields(config: NotificationConfig): Promise<Notif
|
|||||||
...config,
|
...config,
|
||||||
token: config.token ? await cryptoUtils.decrypt(config.token) : undefined,
|
token: config.token ? await cryptoUtils.decrypt(config.token) : undefined,
|
||||||
};
|
};
|
||||||
|
case "pushover":
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
apiToken: await cryptoUtils.decrypt(config.apiToken),
|
||||||
|
};
|
||||||
case "custom":
|
case "custom":
|
||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
|
|||||||
Reference in New Issue
Block a user