mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-13 06:35:24 +03:00
Implement saving of notification tokens
This commit is contained in:
parent
fbc35a36b1
commit
d40f177b77
@ -37,5 +37,3 @@ chromadb == 0.5.0
|
|||||||
google-generativeai == 0.6.*
|
google-generativeai == 0.6.*
|
||||||
ollama == 0.2.*
|
ollama == 0.2.*
|
||||||
openai == 1.30.*
|
openai == 1.30.*
|
||||||
# Notifications
|
|
||||||
firebase_admin == 6.5.0
|
|
||||||
@ -19,6 +19,7 @@ from frigate.api.auth import AuthBp, get_jwt_secret, limiter
|
|||||||
from frigate.api.event import EventBp
|
from frigate.api.event import EventBp
|
||||||
from frigate.api.export import ExportBp
|
from frigate.api.export import ExportBp
|
||||||
from frigate.api.media import MediaBp
|
from frigate.api.media import MediaBp
|
||||||
|
from frigate.api.notification import NotificationBp
|
||||||
from frigate.api.preview import PreviewBp
|
from frigate.api.preview import PreviewBp
|
||||||
from frigate.api.review import ReviewBp
|
from frigate.api.review import ReviewBp
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
@ -48,6 +49,7 @@ bp.register_blueprint(MediaBp)
|
|||||||
bp.register_blueprint(PreviewBp)
|
bp.register_blueprint(PreviewBp)
|
||||||
bp.register_blueprint(ReviewBp)
|
bp.register_blueprint(ReviewBp)
|
||||||
bp.register_blueprint(AuthBp)
|
bp.register_blueprint(AuthBp)
|
||||||
|
bp.register_blueprint(NotificationBp)
|
||||||
|
|
||||||
|
|
||||||
def create_app(
|
def create_app(
|
||||||
|
|||||||
34
frigate/api/notification.py
Normal file
34
frigate/api/notification.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"""Notification apis."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from flask import (
|
||||||
|
Blueprint,
|
||||||
|
jsonify,
|
||||||
|
request,
|
||||||
|
)
|
||||||
|
from peewee import DoesNotExist
|
||||||
|
|
||||||
|
from frigate.models import User
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
NotificationBp = Blueprint("notifications", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@NotificationBp.route("/notifications/register", methods=["POST"])
|
||||||
|
def register_notifications():
|
||||||
|
username = request.headers.get("remote-user", type=str) or "admin"
|
||||||
|
json: dict[str, any] = request.get_json(silent=True) or {}
|
||||||
|
token = json["token"]
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
return jsonify({"success": False, "message": "Token must be provided."}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
User.update(notification_tokens=User.notification_tokens.append(token)).where(
|
||||||
|
User.username == username
|
||||||
|
).execute()
|
||||||
|
return jsonify({"success": True, "message": "Successfully saved token."}), 200
|
||||||
|
except DoesNotExist:
|
||||||
|
return jsonify({"success": False, "message": "Could not find user."}), 404
|
||||||
@ -23,9 +23,9 @@ from frigate.api.app import create_app
|
|||||||
from frigate.api.auth import hash_password
|
from frigate.api.auth import hash_password
|
||||||
from frigate.comms.config_updater import ConfigPublisher
|
from frigate.comms.config_updater import ConfigPublisher
|
||||||
from frigate.comms.dispatcher import Communicator, Dispatcher
|
from frigate.comms.dispatcher import Communicator, Dispatcher
|
||||||
from frigate.comms.firebase import FirebaseClient
|
|
||||||
from frigate.comms.inter_process import InterProcessCommunicator
|
from frigate.comms.inter_process import InterProcessCommunicator
|
||||||
from frigate.comms.mqtt import MqttClient
|
from frigate.comms.mqtt import MqttClient
|
||||||
|
from frigate.comms.webpush import WebPushClient
|
||||||
from frigate.comms.ws import WebSocketClient
|
from frigate.comms.ws import WebSocketClient
|
||||||
from frigate.comms.zmq_proxy import ZmqProxy
|
from frigate.comms.zmq_proxy import ZmqProxy
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
@ -403,7 +403,7 @@ class FrigateApp:
|
|||||||
comms.append(MqttClient(self.config))
|
comms.append(MqttClient(self.config))
|
||||||
|
|
||||||
if self.config.notifications.enabled:
|
if self.config.notifications.enabled:
|
||||||
comms.append(FirebaseClient(self.config, self.stop_event))
|
comms.append(WebPushClient(self.config))
|
||||||
|
|
||||||
comms.append(WebSocketClient(self.config))
|
comms.append(WebSocketClient(self.config))
|
||||||
comms.append(self.inter_process_communicator)
|
comms.append(self.inter_process_communicator)
|
||||||
|
|||||||
@ -1,97 +0,0 @@
|
|||||||
"""Handle sending notifications for Frigate via Firebase."""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import threading
|
|
||||||
from multiprocessing.synchronize import Event as MpEvent
|
|
||||||
from typing import Any, Callable
|
|
||||||
|
|
||||||
import firebase_admin
|
|
||||||
from firebase_admin import credentials, messaging
|
|
||||||
|
|
||||||
from frigate.comms.dispatcher import Communicator
|
|
||||||
from frigate.config import FrigateConfig
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class FirebaseClient(Communicator): # type: ignore[misc]
|
|
||||||
"""Frigate wrapper for firebase client."""
|
|
||||||
|
|
||||||
def __init__(self, config: FrigateConfig, stop_event: MpEvent) -> None:
|
|
||||||
self.messenger = FirebaseMessenger(config, stop_event)
|
|
||||||
self.messenger.start()
|
|
||||||
|
|
||||||
def subscribe(self, receiver: Callable) -> None:
|
|
||||||
"""Wrapper for allowing dispatcher to subscribe."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def publish(self, topic: str, payload: Any, retain: bool = False) -> None:
|
|
||||||
"""Wrapper for publishing when client is in valid state."""
|
|
||||||
if topic == "reviews":
|
|
||||||
self.messenger.send_message(json.loads(payload))
|
|
||||||
|
|
||||||
def stop(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class FirebaseMessenger(threading.Thread):
|
|
||||||
def __init__(self, config: FrigateConfig, stop_event: MpEvent) -> None:
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
self.name = "firebase_messenger"
|
|
||||||
self.config = config
|
|
||||||
self.stop_event = stop_event
|
|
||||||
|
|
||||||
def send_message(self, payload: dict[str, any]) -> None:
|
|
||||||
# Only notify for alerts
|
|
||||||
if payload["after"]["severity"] != "alert":
|
|
||||||
return
|
|
||||||
|
|
||||||
state = payload["type"]
|
|
||||||
|
|
||||||
# Don't notify if message is an update and important fields don't have an update
|
|
||||||
if (
|
|
||||||
state == "update"
|
|
||||||
and len(payload["before"]["data"]["objects"])
|
|
||||||
== len(payload["after"]["data"]["objects"])
|
|
||||||
and len(payload["before"]["data"]["zones"])
|
|
||||||
== len(payload["after"]["data"]["zones"])
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
reviewId = payload["after"]["id"]
|
|
||||||
sorted_objects: set[str] = set()
|
|
||||||
|
|
||||||
for obj in payload["after"]["data"]["objects"]:
|
|
||||||
if "-verified" not in obj:
|
|
||||||
sorted_objects.add(obj)
|
|
||||||
|
|
||||||
sorted_objects.update(payload["after"]["data"]["sub_labels"])
|
|
||||||
|
|
||||||
message = messaging.MulticastMessage(
|
|
||||||
notification=messaging.Notification(
|
|
||||||
title=f"{', '.join(sorted_objects).replace('_', ' ').title()}{' was' if state == 'end' else ''} detected in {', '.join(payload['after']['data']['zones']).replace('_', ' ').title()}",
|
|
||||||
body=f"Detected on {payload['after']['camera'].replace('_', ' ').title()}",
|
|
||||||
),
|
|
||||||
webpush=messaging.WebpushConfig(
|
|
||||||
fcm_options=messaging.WebpushFCMOptions(
|
|
||||||
link=f"{self.config.notifications.base_url}/review?id={reviewId}"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
data={"id": reviewId, "imageUrl": f'{self.config.notifications.base_url}{payload["after"]["thumb_path"].replace("/media/frigate", "")}'},
|
|
||||||
tokens=[
|
|
||||||
"cNNicZp6S92qn4kAVJnzd7:APA91bGv-MvDmNoZ2xqJTkPyCTmyv2WG0tfwIqWUuNtq3SXlpQJpdPCCjTEehOLDa0Yphv__KdxOQYEfaFvYfTW2qQevX-tSnRCVa_sJazQ_rfTervpo_zBVJD1T5GfYaY6kr41Wr_fP"
|
|
||||||
],
|
|
||||||
)
|
|
||||||
messaging.send_multicast(message)
|
|
||||||
|
|
||||||
def run(self) -> None:
|
|
||||||
try:
|
|
||||||
firebase_admin.get_app()
|
|
||||||
except ValueError:
|
|
||||||
cred = credentials.Certificate("/config/firebase-priv-key.json")
|
|
||||||
firebase_admin.initialize_app(credential=cred)
|
|
||||||
|
|
||||||
while self.stop_event.wait(0.1):
|
|
||||||
# TODO check for a delete invalid tokens
|
|
||||||
pass
|
|
||||||
70
frigate/comms/webpush.py
Normal file
70
frigate/comms/webpush.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
"""Handle sending notifications for Frigate via Firebase."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
from frigate.comms.dispatcher import Communicator
|
||||||
|
from frigate.config import FrigateConfig
|
||||||
|
from frigate.models import User
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class WebPushClient(Communicator): # type: ignore[misc]
|
||||||
|
"""Frigate wrapper for firebase client."""
|
||||||
|
|
||||||
|
def __init__(self, config: FrigateConfig) -> None:
|
||||||
|
self.config = config
|
||||||
|
# TODO check for VAPID key
|
||||||
|
|
||||||
|
self.tokens = []
|
||||||
|
self.invalid_tokens = []
|
||||||
|
|
||||||
|
users: list[User] = User.select(User.notification_tokens).dicts().iterator()
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
self.tokens.extend(user["notification_tokens"])
|
||||||
|
|
||||||
|
def subscribe(self, receiver: Callable) -> None:
|
||||||
|
"""Wrapper for allowing dispatcher to subscribe."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def publish(self, topic: str, payload: Any, retain: bool = False) -> None:
|
||||||
|
"""Wrapper for publishing when client is in valid state."""
|
||||||
|
if topic == "reviews":
|
||||||
|
self.send_message(json.loads(payload))
|
||||||
|
|
||||||
|
def send_message(self, payload: dict[str, any]) -> None:
|
||||||
|
# Only notify for alerts
|
||||||
|
if payload["after"]["severity"] != "alert":
|
||||||
|
return
|
||||||
|
|
||||||
|
state = payload["type"]
|
||||||
|
|
||||||
|
# Don't notify if message is an update and important fields don't have an update
|
||||||
|
if (
|
||||||
|
state == "update"
|
||||||
|
and len(payload["before"]["data"]["objects"])
|
||||||
|
== len(payload["after"]["data"]["objects"])
|
||||||
|
and len(payload["before"]["data"]["zones"])
|
||||||
|
== len(payload["after"]["data"]["zones"])
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
reviewId = payload["after"]["id"]
|
||||||
|
sorted_objects: set[str] = set()
|
||||||
|
|
||||||
|
for obj in payload["after"]["data"]["objects"]:
|
||||||
|
if "-verified" not in obj:
|
||||||
|
sorted_objects.add(obj)
|
||||||
|
|
||||||
|
sorted_objects.update(payload["after"]["data"]["sub_labels"])
|
||||||
|
|
||||||
|
title = f"{', '.join(sorted_objects).replace('_', ' ').title()}{' was' if state == 'end' else ''} detected in {', '.join(payload['after']['data']['zones']).replace('_', ' ').title()}"
|
||||||
|
message = f"Detected on {payload['after']['camera'].replace('_', ' ').title()}"
|
||||||
|
direct_url = f"{self.config.notifications.base_url}/review?id={reviewId}"
|
||||||
|
image = f'{self.config.notifications.base_url}{payload["after"]["thumb_path"].replace("/media/frigate", "")}'
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
pass
|
||||||
@ -171,7 +171,9 @@ class AuthConfig(FrigateBaseModel):
|
|||||||
|
|
||||||
class NotificationConfig(FrigateBaseModel):
|
class NotificationConfig(FrigateBaseModel):
|
||||||
enabled: bool = Field(default=False, title="Enable notifications")
|
enabled: bool = Field(default=False, title="Enable notifications")
|
||||||
base_url: Optional[str] = Field(default=None, title="Base url for notification link and image.")
|
base_url: Optional[str] = Field(
|
||||||
|
default=None, title="Base url for notification link and image."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StatsConfig(FrigateBaseModel):
|
class StatsConfig(FrigateBaseModel):
|
||||||
@ -1366,7 +1368,9 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
default_factory=dict, title="Frigate environment variables."
|
default_factory=dict, title="Frigate environment variables."
|
||||||
)
|
)
|
||||||
ui: UIConfig = Field(default_factory=UIConfig, title="UI configuration.")
|
ui: UIConfig = Field(default_factory=UIConfig, title="UI configuration.")
|
||||||
notifications: NotificationConfig = Field(default_factory=NotificationConfig, title="Notification Config")
|
notifications: NotificationConfig = Field(
|
||||||
|
default_factory=NotificationConfig, title="Notification Config"
|
||||||
|
)
|
||||||
telemetry: TelemetryConfig = Field(
|
telemetry: TelemetryConfig = Field(
|
||||||
default_factory=TelemetryConfig, title="Telemetry configuration."
|
default_factory=TelemetryConfig, title="Telemetry configuration."
|
||||||
)
|
)
|
||||||
|
|||||||
@ -32,7 +32,7 @@ SQL = pw.SQL
|
|||||||
def migrate(migrator, database, fake=False, **kwargs):
|
def migrate(migrator, database, fake=False, **kwargs):
|
||||||
migrator.add_fields(
|
migrator.add_fields(
|
||||||
User,
|
User,
|
||||||
notification_tokens=JSONField(default={}),
|
notification_tokens=JSONField(default=[]),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
835
web/package-lock.json
generated
835
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -42,7 +42,6 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"firebase": "^10.12.3",
|
|
||||||
"hls.js": "^1.5.13",
|
"hls.js": "^1.5.13",
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
"immer": "^10.1.1",
|
"immer": "^10.1.1",
|
||||||
|
|||||||
@ -1,36 +0,0 @@
|
|||||||
// Give the service worker access to Firebase Messaging.
|
|
||||||
// Note that you can only use Firebase Messaging here. Other Firebase libraries
|
|
||||||
// are not available in the service worker.
|
|
||||||
importScripts("https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js");
|
|
||||||
importScripts("https://www.gstatic.com/firebasejs/8.10.1/firebase-messaging.js");
|
|
||||||
|
|
||||||
// Initialize the Firebase app in the service worker by passing in
|
|
||||||
// your app's Firebase config object.
|
|
||||||
// https://firebase.google.com/docs/web/setup#config-object
|
|
||||||
const fbConfig = await (
|
|
||||||
await fetch(`${window.location.href}/firebase-config.json`)
|
|
||||||
).json();
|
|
||||||
|
|
||||||
firebase.initializeApp(fbConfig);
|
|
||||||
|
|
||||||
// Retrieve an instance of Firebase Messaging so that it can handle background
|
|
||||||
// messages.
|
|
||||||
const messaging = firebase.messaging();
|
|
||||||
|
|
||||||
messaging.onBackgroundMessage((payload) => {
|
|
||||||
console.log(
|
|
||||||
"[firebase-messaging-sw.js] Received background message ",
|
|
||||||
payload
|
|
||||||
);
|
|
||||||
// Customize notification here
|
|
||||||
const notificationOptions = {
|
|
||||||
body: payload.notification.body,
|
|
||||||
icon: payload.data.imageUrl,
|
|
||||||
tag: payload.data.id, // ensure that the notifications for same items are written over
|
|
||||||
};
|
|
||||||
|
|
||||||
self.registration.showNotification(
|
|
||||||
payload.notification.title,
|
|
||||||
notificationOptions
|
|
||||||
);
|
|
||||||
});
|
|
||||||
9
web/public/notifications-worker.ts
Normal file
9
web/public/notifications-worker.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Notifications Worker
|
||||||
|
|
||||||
|
self.addEventListener("push", function (event) {
|
||||||
|
if (event.data) {
|
||||||
|
console.log("This push event has data: ", event.data.text());
|
||||||
|
} else {
|
||||||
|
console.log("This push event has no data.");
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { getMessaging } from "firebase/messaging";
|
|
||||||
import { initializeApp } from "firebase/app";
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
|
||||||
|
|
||||||
export function useFirebaseApp() {
|
|
||||||
const [firebaseConfig, setFirebaseConfig] = useState();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!firebaseConfig) {
|
|
||||||
fetch(`${window.location.href}/firebase-config.json`).then(
|
|
||||||
async (resp) => {
|
|
||||||
setFirebaseConfig(await resp.json());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [firebaseConfig]);
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
if (!firebaseConfig) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const app = initializeApp(firebaseConfig);
|
|
||||||
app.automaticDataCollectionEnabled = false;
|
|
||||||
return app;
|
|
||||||
}, [firebaseConfig]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useFirebaseMessaging() {
|
|
||||||
return useMemo(() => getMessaging(), []);
|
|
||||||
}
|
|
||||||
@ -37,24 +37,37 @@ import MasksAndZonesView from "@/views/settings/MasksAndZonesView";
|
|||||||
import AuthenticationView from "@/views/settings/AuthenticationView";
|
import AuthenticationView from "@/views/settings/AuthenticationView";
|
||||||
import NotificationView from "@/views/settings/NotificationsSettingsView";
|
import NotificationView from "@/views/settings/NotificationsSettingsView";
|
||||||
|
|
||||||
export default function Settings() {
|
const allSettingsViews = [
|
||||||
const settingsViews = [
|
"general",
|
||||||
"general",
|
"camera settings",
|
||||||
"camera settings",
|
"masks / zones",
|
||||||
"masks / zones",
|
"motion tuner",
|
||||||
"motion tuner",
|
"debug",
|
||||||
"debug",
|
"users",
|
||||||
"users",
|
"notifications",
|
||||||
"notifications",
|
] as const;
|
||||||
] as const;
|
type SettingsType = (typeof allSettingsViews)[number];
|
||||||
|
|
||||||
type SettingsType = (typeof settingsViews)[number];
|
export default function Settings() {
|
||||||
const [page, setPage] = useState<SettingsType>("general");
|
const [page, setPage] = useState<SettingsType>("general");
|
||||||
const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100);
|
const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100);
|
||||||
const tabsRef = useRef<HTMLDivElement | null>(null);
|
const tabsRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
|
// available settings views
|
||||||
|
|
||||||
|
const settingsViews = useMemo(() => {
|
||||||
|
const views = [...allSettingsViews];
|
||||||
|
|
||||||
|
if (!("Notification" in window) || !window.isSecureContext) {
|
||||||
|
const index = views.indexOf("notifications");
|
||||||
|
views.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return views;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// TODO: confirm leave page
|
// TODO: confirm leave page
|
||||||
const [unsavedChanges, setUnsavedChanges] = useState(false);
|
const [unsavedChanges, setUnsavedChanges] = useState(false);
|
||||||
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
|
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
|
||||||
|
|||||||
@ -341,6 +341,10 @@ export interface FrigateConfig {
|
|||||||
user: string | null;
|
user: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
notifications: {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
objects: {
|
objects: {
|
||||||
filters: {
|
filters: {
|
||||||
[objectName: string]: {
|
[objectName: string]: {
|
||||||
@ -395,7 +399,7 @@ export interface FrigateConfig {
|
|||||||
|
|
||||||
semantic_search: {
|
semantic_search: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
snapshots: {
|
snapshots: {
|
||||||
bounding_box: boolean;
|
bounding_box: boolean;
|
||||||
|
|||||||
@ -1,27 +1,80 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useFirebaseApp, useFirebaseMessaging } from "@/hooks/use-firebase";
|
import Heading from "@/components/ui/heading";
|
||||||
import { getToken } from "firebase/messaging";
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
|
import axios from "axios";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
export default function NotificationView() {
|
export default function NotificationView() {
|
||||||
useFirebaseApp();
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
const firebaseMessaging = useFirebaseMessaging();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex size-full flex-col md:flex-row">
|
<>
|
||||||
<Button
|
<div className="flex size-full flex-col md:flex-row">
|
||||||
onClick={() => {
|
<Toaster position="top-center" closeButton={true} />
|
||||||
Notification.requestPermission().then((permission) => {
|
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0">
|
||||||
if (permission === "granted") {
|
<Heading as="h3" className="my-2">
|
||||||
getToken(firebaseMessaging, {
|
Notification Settings
|
||||||
vapidKey:
|
</Heading>
|
||||||
"BDd7XT7ElEhLApcxFvrBEs1H-6kfbmjTXhfxRIOXSWUIXOpffl_rlKHOe-qPjzp8Gyqv6tgrWX9-xwSTt2ImKPM",
|
|
||||||
}).then((token) => console.log(`the token is ${token}`));
|
<div className="mt-2 space-y-6">
|
||||||
}
|
<div className="space-y-3">
|
||||||
});
|
<div className="flex flex-row items-center justify-start gap-2">
|
||||||
}}
|
<Switch
|
||||||
>
|
id="auto-live"
|
||||||
Enable Notifications
|
checked={config?.notifications?.enabled}
|
||||||
</Button>
|
onCheckedChange={() => {}}
|
||||||
</div>
|
/>
|
||||||
|
<Label className="cursor-pointer" htmlFor="auto-live">
|
||||||
|
Notifications
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="my-2 text-sm text-muted-foreground">
|
||||||
|
<p>
|
||||||
|
Enable notifications for Frigate alerts. This requires Frigate
|
||||||
|
to be externally accessible.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{config?.notifications.enabled && (
|
||||||
|
<div className="mt-2 space-y-6">
|
||||||
|
<div className="space-y-3">
|
||||||
|
{
|
||||||
|
// TODO need to register the worker before enabling the notifications button
|
||||||
|
// TODO make the notifications button show enable / disable depending on current state
|
||||||
|
}
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
Notification.requestPermission().then((permission) => {
|
||||||
|
console.log("notification permissions are ", permission);
|
||||||
|
if (permission === "granted") {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register("notifications-worker.ts")
|
||||||
|
.then((registration) => {
|
||||||
|
registration.pushManager
|
||||||
|
.subscribe()
|
||||||
|
.then((pushSubscription) => {
|
||||||
|
console.log(pushSubscription.endpoint);
|
||||||
|
axios.post("notifications/register", {
|
||||||
|
sub: pushSubscription,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Enable Notifications
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user