├── .dockerignore ├── .github ├── FUNDING.yml ├── renovate.json └── workflows │ ├── codetests.yml │ ├── ghcr.yml │ └── release.yml ├── .gitignore ├── .golangci.yml ├── .markdownlint.jsonc ├── .vscode ├── .gitignore ├── extensions.json └── settings-recommended.json ├── LICENSE ├── Makefile ├── README.md ├── examples ├── MANUAL.md ├── README.md ├── compose.yml └── notifiarr.conf.example ├── frontend ├── .gitignore ├── .npmrc ├── .prettierrc ├── README.md ├── frontend.go ├── generate.sh ├── index.html ├── locales.go ├── package-lock.json ├── package.json ├── src │ ├── Index.svelte │ ├── Landing.svelte │ ├── Login.svelte │ ├── api │ │ ├── fetch.ts │ │ ├── generate.go │ │ ├── generate.sh │ │ ├── notifiarrConfig.ts │ │ └── profile.svelte.ts │ ├── assets │ │ ├── app.css │ │ ├── darkbg.svg │ │ ├── fonts │ │ │ └── Beleren2016SmallCaps-Bold.ttf │ │ ├── golift.png │ │ ├── lightbg.svg │ │ ├── logos │ │ │ ├── deluge.png │ │ │ ├── lidarr.png │ │ │ ├── mysql.png │ │ │ ├── notifiarr.png │ │ │ ├── nvidia.png │ │ │ ├── nzbget.png │ │ │ ├── plex.png │ │ │ ├── prowlarr.png │ │ │ ├── qbittorrent.png │ │ │ ├── radarr.png │ │ │ ├── readarr.png │ │ │ ├── rtorrent.png │ │ │ ├── sabnzb.png │ │ │ ├── sonarr.png │ │ │ ├── tautulli.png │ │ │ └── transmission.png │ │ └── notifiarr.svg │ ├── header │ │ ├── Index.svelte │ │ ├── Reload.svelte │ │ └── Shutdown.svelte │ ├── includes │ │ ├── CheckedInput.svelte │ │ ├── Fa.svelte │ │ ├── Footer.svelte │ │ ├── Header.svelte │ │ ├── Input.svelte │ │ ├── Instance.svelte │ │ ├── InstanceHeader.svelte │ │ ├── Instances.svelte │ │ ├── InstancesTab.svelte │ │ ├── Translate.svelte │ │ ├── formsTracker.svelte.ts │ │ ├── instanceValidator.ts │ │ ├── locale │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ ├── es.json │ │ │ ├── fi.json │ │ │ ├── fr.json │ │ │ ├── hu.json │ │ │ ├── index.svelte.ts │ │ │ ├── nl.json │ │ │ ├── pl.json │ │ │ ├── pt.json │ │ │ ├── sv.json │ │ │ └── zh_Hant.json │ │ ├── theme.svelte.ts │ │ └── util.ts │ ├── index.ts │ ├── navigation │ │ ├── Index.svelte │ │ ├── ProfileMenu.svelte │ │ ├── Section.svelte │ │ ├── Sidebar.svelte │ │ ├── nav.svelte.ts │ │ └── pages.ts │ ├── pages │ │ ├── commands │ │ │ ├── Index.svelte │ │ │ └── page.svelte.ts │ │ ├── configuration │ │ │ └── Index.svelte │ │ ├── downloadApps │ │ │ ├── Index.svelte │ │ │ └── page.svelte.ts │ │ ├── endpoints │ │ │ └── Index.svelte │ │ ├── fileWatcher │ │ │ ├── Index.svelte │ │ │ ├── TestRegex.svelte │ │ │ ├── Watcher.svelte │ │ │ └── page.svelte.ts │ │ ├── integrations │ │ │ └── Index.svelte │ │ ├── logFiles │ │ │ └── Index.svelte │ │ ├── mediaApps │ │ │ └── Index.svelte │ │ ├── metrics │ │ │ └── Index.svelte │ │ ├── monitoring │ │ │ └── Index.svelte │ │ ├── profile │ │ │ ├── Header.svelte │ │ │ └── Index.svelte │ │ ├── serviceChecks │ │ │ └── Index.svelte │ │ ├── siteTunnel │ │ │ ├── Index.svelte │ │ │ └── Stats.svelte │ │ ├── snapshotApps │ │ │ └── Index.svelte │ │ ├── starrApps │ │ │ ├── Index.svelte │ │ │ └── page.svelte.ts │ │ ├── system │ │ │ ├── BuildInfo.svelte │ │ │ ├── ClientInfo.svelte │ │ │ ├── Environment.svelte │ │ │ ├── Header.svelte │ │ │ ├── Index.svelte │ │ │ ├── Network.svelte │ │ │ ├── OS.svelte │ │ │ ├── StartupParams.svelte │ │ │ ├── Storage.svelte │ │ │ └── UserInfo.svelte │ │ └── triggers │ │ │ └── Index.svelte │ ├── svelte-shims.d.ts │ └── vite-env.d.ts ├── svelte.config.js ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── go.mod ├── go.sum ├── init ├── README.md ├── archlinux │ ├── PKGBUILD.template │ ├── README.md │ ├── SRCINFO.template │ └── aur-deploy.sh ├── bsd │ └── freebsd.rc.d ├── docker │ ├── Dockerfile.alpine │ ├── Dockerfile.cuda │ ├── Dockerfile.scratch │ ├── hooks │ │ ├── build │ │ ├── pre_build │ │ └── push │ └── makedocker.sh ├── linux │ ├── README.md │ ├── deb │ │ ├── etc │ │ │ └── apt │ │ │ │ └── apt.conf.d │ │ │ │ └── 999notifiarr │ │ └── usr │ │ │ └── share │ │ │ └── applications │ │ │ └── notifiarr.desktop │ └── rpm │ │ └── usr │ │ └── share │ │ └── applications │ │ └── notifiarr.desktop ├── macos │ ├── Notifiarr.app │ │ └── Contents │ │ │ ├── Info.plist │ │ │ └── Resources │ │ │ ├── Images │ │ │ └── notifiarr.png │ │ │ ├── Notifiarr.icns │ │ │ ├── README.md │ │ │ └── terminal-notifier.app │ │ │ └── Contents │ │ │ ├── Info.plist │ │ │ ├── MacOS │ │ │ └── terminal-notifier │ │ │ ├── PkgInfo │ │ │ ├── Resources │ │ │ └── en.lproj │ │ │ │ ├── InfoPlist.strings │ │ │ │ └── MainMenu.nib │ │ │ └── _CodeSignature │ │ │ └── CodeResources │ ├── background.png │ ├── makedmg.sh │ ├── notarize.json │ ├── notarize.sh │ └── sign.json ├── systemd │ ├── after-install.sh │ ├── before-install.sh │ ├── before-remove.sh │ └── notifiarr.service └── windows │ ├── README.md │ ├── application.ico │ ├── manifest.xml │ └── signexe.sh ├── main.go ├── pkg ├── apps │ ├── api.go │ ├── apppkg │ │ ├── README.md │ │ ├── plex │ │ │ ├── handlers.go │ │ │ ├── info.go │ │ │ ├── plex.go │ │ │ ├── sections.go │ │ │ ├── sessions.go │ │ │ ├── types.go │ │ │ └── webhook.go │ │ ├── sabnzbd │ │ │ └── sabnzbd.go │ │ └── tautulli │ │ │ └── tautulli.go │ ├── integrations.go │ ├── lidarr.go │ ├── prowlarr.go │ ├── radarr.go │ ├── readarr.go │ ├── roundtrip.go │ ├── setup.go │ └── sonarr.go ├── bindata │ ├── .gitignore │ ├── README.md │ ├── bindata.go │ ├── docs │ │ └── generate.go │ ├── files │ │ ├── css │ │ │ ├── .gitignore │ │ │ ├── custom.css │ │ │ ├── style.css │ │ │ └── style.less │ │ ├── images │ │ │ ├── favicon.ico │ │ │ ├── favicon.png │ │ │ ├── golift.png │ │ │ └── logo │ │ │ │ ├── deluge.png │ │ │ │ ├── lidarr.png │ │ │ │ ├── mysql.png │ │ │ │ ├── notifiarr.png │ │ │ │ ├── nvidia.png │ │ │ │ ├── nzbget.png │ │ │ │ ├── plex.png │ │ │ │ ├── prowlarr.png │ │ │ │ ├── qbittorrent.png │ │ │ │ ├── radarr.png │ │ │ │ ├── readarr.png │ │ │ │ ├── rtorrent.png │ │ │ │ ├── sabnzbd.png │ │ │ │ ├── sonarr.png │ │ │ │ ├── tautulli.png │ │ │ │ └── transmission.png │ │ ├── js │ │ │ ├── .gitignore │ │ │ ├── common.js │ │ │ ├── fileViewer.js │ │ │ ├── filebrowser.js │ │ │ ├── golists.js │ │ │ ├── navigation.js │ │ │ ├── services.js │ │ │ ├── triggers.js │ │ │ ├── tunnel.js │ │ │ └── websocket.js │ │ └── libraries │ │ │ ├── accordion │ │ │ ├── accordion.css.gz │ │ │ └── accordion.js.gz │ │ │ ├── balloon │ │ │ └── jquery.balloon.min.js.gz │ │ │ ├── bootstrap │ │ │ ├── bootstrap-social.css.gz │ │ │ ├── bootstrap.min.css.gz │ │ │ ├── bootstrap.min.js.gz │ │ │ ├── dataTables.bootstrap.min.css.gz │ │ │ └── dataTables.bootstrap.min.js.gz │ │ │ ├── filebrowser │ │ │ ├── icons.svg.gz │ │ │ ├── jquery.filebrowser.min.css.gz │ │ │ └── jquery.filebrowser.min.js.gz │ │ │ ├── fontawesome │ │ │ ├── LICENSE.txt.gz │ │ │ ├── css │ │ │ │ └── all.min.css.gz │ │ │ └── webfonts │ │ │ │ ├── fa-brands-400.eot.gz │ │ │ │ ├── fa-brands-400.ttf.gz │ │ │ │ ├── fa-brands-400.woff.gz │ │ │ │ ├── fa-brands-400.woff2.gz │ │ │ │ ├── fa-regular-400.eot.gz │ │ │ │ ├── fa-regular-400.svg.gz │ │ │ │ ├── fa-regular-400.ttf.gz │ │ │ │ ├── fa-regular-400.woff.gz │ │ │ │ ├── fa-regular-400.woff2.gz │ │ │ │ ├── fa-solid-900.eot.gz │ │ │ │ ├── fa-solid-900.ttf.gz │ │ │ │ ├── fa-solid-900.woff.gz │ │ │ │ └── fa-solid-900.woff2.gz │ │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot.gz │ │ │ ├── glyphicons-halflings-regular.svg.gz │ │ │ ├── glyphicons-halflings-regular.ttf.gz │ │ │ ├── glyphicons-halflings-regular.woff.gz │ │ │ └── glyphicons-halflings-regular.woff2.gz │ │ │ ├── jquery │ │ │ ├── images │ │ │ │ ├── ui-icons_444444_256x240.png │ │ │ │ ├── ui-icons_555555_256x240.png │ │ │ │ ├── ui-icons_777777_256x240.png │ │ │ │ └── ui-icons_ffffff_256x240.png │ │ │ ├── jquery-ui.min.css.gz │ │ │ ├── jquery.custom.js.gz │ │ │ ├── jquery.dataTables.min.js.gz │ │ │ ├── jquery.min.js.gz │ │ │ ├── jquery.ui.min.js.gz │ │ │ └── jquery.ui.touch.js.gz │ │ │ ├── select2 │ │ │ ├── select2.css.gz │ │ │ └── select2.min.js.gz │ │ │ ├── swagger │ │ │ ├── index.css.gz │ │ │ ├── swagger-ui-bundle.js.gz │ │ │ ├── swagger-ui-bundle.js.map.gz │ │ │ ├── swagger-ui-es-bundle-core.js.gz │ │ │ ├── swagger-ui-es-bundle-core.js.map.gz │ │ │ ├── swagger-ui-es-bundle.js.gz │ │ │ ├── swagger-ui-es-bundle.js.map.gz │ │ │ ├── swagger-ui-standalone-preset.js.gz │ │ │ ├── swagger-ui-standalone-preset.js.map.gz │ │ │ ├── swagger-ui.css.gz │ │ │ ├── swagger-ui.css.map.gz │ │ │ ├── swagger-ui.js.gz │ │ │ └── swagger-ui.js.map.gz │ │ │ └── toast │ │ │ ├── toast.css.gz │ │ │ └── toast.script.js.gz │ ├── other │ │ ├── fortunes.txt.gz │ │ └── io.golift.notifiarr.plist │ ├── templates │ │ ├── 404.html │ │ ├── ajax │ │ │ ├── cmdargs.html │ │ │ └── cmdstats.html │ │ ├── clientinfo.html │ │ ├── commands.html │ │ ├── config.html │ │ ├── downloaders │ │ │ ├── deluge.html │ │ │ ├── index.html │ │ │ ├── nzbget.html │ │ │ ├── qbit.html │ │ │ ├── rtorrent.html │ │ │ ├── sabnzbd.html │ │ │ └── transmission.html │ │ ├── filewatcher.html │ │ ├── includes │ │ │ ├── filetablelist.html │ │ │ ├── footer.html │ │ │ ├── header.html │ │ │ ├── intervaloptions.html │ │ │ └── system-header.html │ │ ├── index.html │ │ ├── integrations │ │ │ ├── deluge.html │ │ │ ├── index.html │ │ │ ├── lidarr.html │ │ │ ├── nzbget.html │ │ │ ├── plex.html │ │ │ ├── prowlarr.html │ │ │ ├── qbit.html │ │ │ ├── radarr.html │ │ │ ├── readarr.html │ │ │ ├── rtorrent.html │ │ │ ├── sabnzb.html │ │ │ ├── sonarr.html │ │ │ └── tautulli.html │ │ ├── landing.html │ │ ├── logfiles.html │ │ ├── login.html │ │ ├── media │ │ │ ├── index.html │ │ │ ├── plex.html │ │ │ └── tautulli.html │ │ ├── metrics.html │ │ ├── monitoring.html │ │ ├── processlist.html │ │ ├── profile.html │ │ ├── services.html │ │ ├── snapshot │ │ │ ├── index.html │ │ │ ├── mysql.html │ │ │ └── nvidia.html │ │ ├── starr │ │ │ ├── index.html │ │ │ ├── lidarr.html │ │ │ ├── prowlarr.html │ │ │ ├── radarr.html │ │ │ ├── readarr.html │ │ │ └── sonarr.html │ │ ├── swagger │ │ │ └── index.html │ │ ├── system-snapshot.html │ │ ├── system.html │ │ ├── triggers.html │ │ └── tunnel.html │ └── tools.go ├── checkapp │ ├── checkapp.go │ ├── commands.go │ ├── downloaders.go │ ├── media.go │ ├── services.go │ ├── snapshots.go │ └── starr.go ├── client │ ├── cli.go │ ├── gui_profile.go │ ├── handlers.go │ ├── handlers_gui.go │ ├── handlers_plex.go │ ├── handlers_websocket.go │ ├── html_templates.go │ ├── init.go │ ├── start.go │ ├── tray.go │ ├── tray_commands.go │ ├── tray_other.go │ ├── tunnel.go │ └── webserver.go ├── configfile │ ├── config.go │ ├── files.go │ ├── flags.go │ ├── helper.go │ ├── password.go │ ├── template.go │ └── words.go ├── cooldown │ ├── cooldown.go │ └── cooldown_test.go ├── logs │ ├── filemode.go │ ├── logfile_others.go │ ├── logfiles.go │ ├── logfiles_windows.go │ ├── logs.go │ ├── logs_linux.go │ ├── logs_others.go │ ├── logs_windows.go │ └── share │ │ └── share.go ├── mnd │ ├── constants.go │ ├── emoji.go │ ├── functions.go │ ├── metrics.go │ ├── types.go │ └── variables.go ├── private │ ├── .gitignore │ └── private.go ├── services │ ├── apps.go │ ├── check_ping.go │ ├── check_proc.go │ ├── check_proc_pre.go │ ├── checks.go │ ├── config.go │ ├── interface.go │ └── services.go ├── snapshot │ ├── diskio.go │ ├── diskusage.go │ ├── helpers_others.go │ ├── helpers_windows.go │ ├── ipmi.go │ ├── memory_freebsd.go │ ├── memory_linux.go │ ├── memory_others.go │ ├── memory_windows.go │ ├── mysql.go │ ├── nvidia.go │ ├── raid.go │ ├── smartctl.go │ ├── snapshot.go │ ├── synology.go │ ├── system.go │ ├── temperatures.go │ ├── users_others.go │ └── users_windows.go ├── triggers │ ├── autoupdate │ │ ├── autoupdate.go │ │ └── update.go │ ├── backups │ │ ├── backups.go │ │ ├── config.go │ │ ├── corruption.go │ │ └── corruption_build.go │ ├── cfsync │ │ ├── common.go │ │ ├── handler.go │ │ ├── lidarr.go │ │ ├── radarr.go │ │ └── sonarr.go │ ├── commands │ │ ├── builder.go │ │ ├── cmdconfig │ │ │ └── config.go │ │ ├── command.go │ │ └── setup.go │ ├── common │ │ ├── scheduler │ │ │ └── scheduler.go │ │ ├── timers.go │ │ └── triggers.go │ ├── crontimer │ │ └── custom.go │ ├── dashboard │ │ ├── dashboard.go │ │ ├── deluge.go │ │ ├── lidarr.go │ │ ├── nzbget.go │ │ ├── qbit.go │ │ ├── radarr.go │ │ ├── readarr.go │ │ ├── rtorrent.go │ │ ├── sabnzb.go │ │ ├── sonarr.go │ │ └── transmission.go │ ├── data │ │ └── store.go │ ├── emptytrash │ │ └── plex.go │ ├── endpoints │ │ ├── endpoints.go │ │ └── epconfig │ │ │ └── epconfig.go │ ├── fileupload │ │ └── upload.go │ ├── filewatch │ │ ├── filewatch.go │ │ └── logger.go │ ├── gaps │ │ └── gaps.go │ ├── handler.go │ ├── mdblist │ │ └── mdblist.go │ ├── plexcron │ │ ├── finished.go │ │ ├── playresume.go │ │ ├── plexcron.go │ │ └── sessions.go │ ├── snapcron │ │ └── snapcron.go │ ├── starrqueue │ │ ├── downloads.go │ │ ├── lidarr.go │ │ ├── radarr.go │ │ ├── readarr.go │ │ ├── setup.go │ │ ├── sonarr.go │ │ └── stuckitems.go │ └── triggers.go ├── ui │ ├── dlgs.go │ ├── dlgs_other.go │ ├── ui.go │ ├── ui_darwin.go │ ├── ui_freebsd.go │ ├── ui_linux.go │ ├── ui_other.go │ └── ui_windows.go ├── update │ ├── check.go │ ├── cleanup.go │ ├── signal.go │ ├── unstable.go │ └── update.go └── website │ ├── clientinfo │ ├── appinfo.go │ ├── clientinfo.go │ ├── handlers.go │ └── phpdate.go │ ├── hostinfo.go │ ├── savedstate.go │ ├── server.go │ ├── website.go │ └── website_routes.go ├── settings.sh └── userscripts ├── install-synology.sh ├── install.sh ├── unraid-notifiarr.sh └── unstable-syno.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | .??* 2 | .gitignore 3 | .golangci.yml 4 | .markdownlint.jsonc 5 | init 6 | examples 7 | .github 8 | .vscode 9 | userscripts 10 | README.md 11 | Makefile 12 | settings.sh 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Notifiarr] 2 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Change this line. 2 | /notifiarr 3 | /init/macos/*.app/Contents/MacOS/* 4 | # The rest is probably fine. 5 | /rsrc*.syso 6 | /aur 7 | /*.log 8 | /*.conf 9 | /cmd 10 | /*.gz 11 | /*.zip 12 | /*.upx 13 | /*.dmg 14 | /*.1 15 | /*.deb 16 | /*.rpm 17 | /*.txz 18 | /*.zst 19 | /*.sig 20 | /*.*.arm 21 | /*.*.exe 22 | /*.*.macos 23 | /*.*.linux 24 | /*.*.freebsd 25 | /*.rb 26 | /*.upx 27 | /*.app 28 | *.sha256 29 | /*.service 30 | /vendor 31 | .DS_Store 32 | *~ 33 | /package_build_* 34 | /release* 35 | MANUAL 36 | MANUAL.html 37 | README 38 | README.html 39 | /*_manual.html 40 | /homebrew-mugs 41 | .secret*files.tar 42 | *key* 43 | aur_deploy_key* 44 | .metadata.make 45 | /*-rendered.sh 46 | /gpg.signing.key 47 | /notifiarr.conf.example 48 | notifiarr.conf 49 | *.signing.* -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | linters: 3 | default: all 4 | disable: 5 | - exhaustruct # This will never work in this app. 6 | - dupl # Maybe one day we can reduce the duplicate code with generics. 7 | - nlreturn # Not needed because wsl is good enough; better actually. 8 | - godot # Does not work with annotations. 9 | - depguard # Not even sure why this is useful. We have too many deps to care. 10 | - funcorder 11 | - gochecknoglobals # we use global sparingly, but they are useful. 12 | - interfacebloat # I'll do what I want with my own interfaces. 13 | # fix these. 14 | - gosec 15 | - predeclared 16 | - recvcheck 17 | - revive 18 | - wsl 19 | - staticcheck 20 | exclusions: 21 | presets: 22 | - comments 23 | - std-error-handling 24 | - common-false-positives 25 | - legacy 26 | rules: 27 | # Exclude some linters from running on auto-generated files. 28 | - path: 'pkg/bindata/docs/*' 29 | linters: 30 | - lll 31 | - gochecknoinits 32 | - gochecknoglobals 33 | 34 | issues: 35 | max-issues-per-linter: 0 36 | max-same-issues: 0 37 | 38 | formatters: 39 | enable: 40 | - gci 41 | - gofmt 42 | - gofumpt 43 | - goimports 44 | -------------------------------------------------------------------------------- /.markdownlint.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "MD030": false, // lists may have spaces before them 3 | "MD041": false, // do not begin with a header 4 | "MD002": false, // first header does not need to be top level 5 | "MD034": false, // allow bare URLs because we put them in code blocks... 6 | "MD046": false, // do not worry about code block style, it gets confused 7 | "MD033": false, // what's wrong with inline html? 8 | "MD031": false, // fenced code blocks wrapped in blank lines look terrible in lists. 9 | "MD013": { 10 | "tables": false, 11 | "line_length": 250, 12 | "code_blocks": false 13 | }, 14 | "MD007": { 15 | "indent": 4 16 | } 17 | } -------------------------------------------------------------------------------- /.vscode/.gitignore: -------------------------------------------------------------------------------- 1 | settings.json 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "svelte.svelte-vscode", 4 | "esbenp.prettier-vscode", 5 | "ardenivanov.svelte-intellisense", 6 | "golang.go" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings-recommended.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "[svelte]": { 4 | "editor.defaultFormatter": "svelte.svelte-vscode" 5 | }, 6 | "[typescript]": { 7 | "editor.defaultFormatter": "esbenp.prettier-vscode" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2025 Go Lift - Building Strong Go Tools 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | To make a new default template, run this: 2 | 3 | ``` 4 | # Build the binary. 5 | make 6 | 7 | # Re-write Template 8 | ./notifiarr -c examples/notifiarr.conf.example --write example 9 | 10 | # Check it out 11 | git diff examples 12 | ``` 13 | -------------------------------------------------------------------------------- /examples/compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | # Replace or pass in APPDATA (below). 3 | notifiarr: 4 | container_name: notifiarr 5 | hostname: notifiarr 6 | image: golift/notifiarr 7 | restart: unless-stopped 8 | ports: 9 | - "5454:5454" 10 | volumes: 11 | - ${APPDATA}/notifiarr:/config 12 | - /var/run/utmp:/var/run/utmp 13 | - /etc/machine-id:/etc/machine-id 14 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS* 20 | ._* 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /frontend/.npmrc: -------------------------------------------------------------------------------- 1 | @fortawesome:registry=https://npm.fontawesome.com/ 2 | //npm.fontawesome.com/:_authToken=${FONTAWESOME_PACKAGE_TOKEN} 3 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "singleQuote": true, 4 | "printWidth": 90, 5 | "tabWidth": 2, 6 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }], 7 | "svelteStrictMode": true, 8 | "semi": false, 9 | "trailingComma": "all", 10 | "bracketSameLine": true, 11 | "arrowParens": "avoid", 12 | "objectWrap": "collapse" 13 | } 14 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Notifiarr Client Frontend 2 | 3 | -------------------------------------------------------------------------------- /frontend/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This is run by go generate from frontend.go. 4 | # It generates the locales.go file. 5 | # And builds the frontend using npm. 6 | 7 | set -e 8 | # Install dependencies. 9 | npm ci 10 | 11 | # Build the frontend 'dist' directory. 12 | npm run build 13 | 14 | # Get all the locales. Remove the folder prefix and the '.json' suffix. 15 | locales=$(ls -1 src/includes/locale/*.json | sed 's#src/includes/locale/\(.*\)\.json#\1#g') 16 | 17 | echo "Creating locales.go..." 18 | 19 | # Create the locales.go file. 20 | cat < locales.go 21 | package frontend 22 | 23 | /* This file is generated by generate.sh. Do not edit it directly. */ 24 | 25 | //nolint:gochecknoglobals 26 | var langs = []string{ 27 | EOF 28 | 29 | # Add each locale to the file. 30 | for locale in $locales; do 31 | # We use a real tab in this string because windows sucks. 32 | echo " \"$locale\"," >> locales.go 33 | done 34 | 35 | # Close the file. 36 | echo "}" >> locales.go 37 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /frontend/locales.go: -------------------------------------------------------------------------------- 1 | package frontend 2 | 3 | /* This file is generated by generate.sh. Do not edit it directly. */ 4 | 5 | //nolint:gochecknoglobals 6 | var langs = []string{ 7 | "de", 8 | "en", 9 | "es", 10 | "fi", 11 | "fr", 12 | "hu", 13 | "nl", 14 | "pl", 15 | "pt", 16 | "sv", 17 | "zh_Hant", 18 | } 19 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notifiarr", 3 | "private": true, 4 | "version": "0.9.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" 11 | }, 12 | "exports": { 13 | ".": { 14 | "svelte": "./dist/index.js" 15 | } 16 | }, 17 | "devDependencies": { 18 | "@sveltejs/vite-plugin-svelte": "^5.0.3", 19 | "@tsconfig/svelte": "^5.0.4", 20 | "@types/js-cookie": "^3.0.6", 21 | "js-cookie": "^3.0.5", 22 | "svelte": "^5.23.1", 23 | "svelte-check": "^4.1.5", 24 | "svelte-fa": "^4.0.4", 25 | "typescript": "~5.8.0", 26 | "vite": "^6.3.1" 27 | }, 28 | "dependencies": { 29 | "@fortawesome/free-brands-svg-icons": "^6.7.2", 30 | "@fortawesome/sharp-duotone-light-svg-icons": "^6.7.2", 31 | "@fortawesome/sharp-duotone-regular-svg-icons": "^6.7.2", 32 | "@fortawesome/sharp-duotone-solid-svg-icons": "^6.7.2", 33 | "@sveltejs/svelte-virtual-list": "^3.0.1", 34 | "@sveltestrap/sveltestrap": "^7.1.0", 35 | "@zerodevx/svelte-toast": "^0.9.6", 36 | "bootstrap": "^5.3.6", 37 | "prettier": "^3.5.3", 38 | "prettier-plugin-svelte": "^3.3.3", 39 | "svelte-i18n": "^4.0.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/Landing.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | 20 |
21 | 22 |

23 |

24 |
25 | -------------------------------------------------------------------------------- /frontend/src/api/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This vendors all the notifiarr dependencies so we can parse their docs. 3 | 4 | set -e 5 | # This variable is used by the generate.go file. 6 | export VENDOR_DIR=../../../vendor 7 | [ ! -d "${VENDOR_DIR}" ] || EXISTS="true" 8 | 9 | # Download the dependencies. 10 | echo "$(date "+%Y/%m/%d %H:%M:%S") ==> downloading dependencies" 11 | go mod vendor 12 | 13 | # Parse the docs and generate the typescript interfaces. 14 | echo "$(date "+%Y/%m/%d %H:%M:%S") ==> starting generator" 15 | go run . 16 | 17 | # Remove the vendor folder if it didn't exist before we started. 18 | [ "$EXISTS" = "true" ] || \ 19 | echo "$(date "+%Y/%m/%d %H:%M:%S") ==> removing vendor folder" && \ 20 | rm -rf "${VENDOR_DIR}" 21 | 22 | echo "$(date "+%Y/%m/%d %H:%M:%S") ==> done" 23 | -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Beleren2016SmallCaps-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/fonts/Beleren2016SmallCaps-Bold.ttf -------------------------------------------------------------------------------- /frontend/src/assets/golift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/golift.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/deluge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/deluge.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/lidarr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/lidarr.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/mysql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/mysql.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/notifiarr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/notifiarr.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/nvidia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/nvidia.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/nzbget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/nzbget.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/plex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/plex.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/prowlarr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/prowlarr.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/qbittorrent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/qbittorrent.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/radarr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/radarr.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/readarr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/readarr.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/rtorrent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/rtorrent.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/sabnzb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/sabnzb.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/sonarr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/sonarr.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/tautulli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/tautulli.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/transmission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/notifiarr/dc264e96fbd6f530f3a8eef6c0b2a57b3209d9c7/frontend/src/assets/logos/transmission.png -------------------------------------------------------------------------------- /frontend/src/header/Reload.svelte: -------------------------------------------------------------------------------- 1 | 41 | 42 | (e.preventDefault(), (isOpen = true))}> 43 | 44 | 45 | 46 | (isOpen = false)} theme={$theme}> 47 | {$_('phrases.ConfirmReload')} 48 | {#if reloading} 49 | {$_('phrases.Reloading')} 50 | {:else} 51 | {$_('phrases.ConfirmReloadBody')} 52 | 53 | 54 | 56 | 57 | {/if} 58 | 59 | -------------------------------------------------------------------------------- /frontend/src/header/Shutdown.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | (e.preventDefault(), (isOpen = true))}> 21 | 22 | 23 | 24 | (isOpen = false)} theme={$theme}> 25 | {$_('phrases.ConfirmShutdown')} 26 | {#if shutdown} 27 | 28 | {#await shutdown() then result} 29 | {#if result.ok} 30 | {$_('phrases.ShutdownSuccess')} 31 | {:else} 32 | {$_('phrases.FailedToShutdown', { values: { error: result.body } })} 33 | {/if} 34 | {/await} 35 | 36 | {:else} 37 | {$_('phrases.ConfirmShutdownBody')} 38 | 39 | 40 | 42 | 43 | {/if} 44 | 45 | -------------------------------------------------------------------------------- /frontend/src/includes/Fa.svelte: -------------------------------------------------------------------------------- 1 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /frontend/src/includes/Header.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 |

13 | {$_('navigation.titles.' + page.id)} 14 | {#if badge}{badge}{/if} 15 | 16 |

17 | {#if description != 'navigation.pageDescription.' + page.id} 18 | {@html description} 19 | {/if} 20 | {#if children} 21 |
22 | {@render children()} 23 | {/if} 24 |
25 | 26 | 43 | -------------------------------------------------------------------------------- /frontend/src/includes/InstanceHeader.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |

12 | {#if typeof flt.app.logo === 'string'} 13 | 14 | {:else} 15 |

26 | 27 | {#if $_(flt.app.id + '.description') !== flt.app.id + '.description'} 28 |

29 | {/if} 30 | 31 | 50 | -------------------------------------------------------------------------------- /frontend/src/includes/InstancesTab.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | (tab = flt.app.name.toLowerCase())}> 23 |
24 |
25 | {titles[flt.app.name]} 26 | {#if flt.instances.length > 0} 27 | {flt.instances.length} 28 | {/if} 29 | {#if flt.invalid} 30 | 31 | {:else if flt.formChanged || flt.removed.length > 0} 32 | 33 | {/if} 34 |
35 |
36 | 37 | 38 | {#snippet headerActive(index)} 39 | {index + 1}. {flt.original[index]?.name} 40 | {/snippet} 41 | {#snippet headerCollapsed(index)} 42 | {flt.original[index]?.url} 43 | {/snippet} 44 | 45 |
46 | 47 | 58 | -------------------------------------------------------------------------------- /frontend/src/includes/Translate.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | 24 | 32 | 33 | {#if $isReady === true && $isLoading === false} 34 | {@html $_(props.id, { values: { ...props } })} 35 | {/if} 36 | -------------------------------------------------------------------------------- /frontend/src/includes/instanceValidator.ts: -------------------------------------------------------------------------------- 1 | import { get } from 'svelte/store' 2 | import { _ } from './Translate.svelte' 3 | import type { Form } from './Instance.svelte' 4 | 5 | /** Standard form validator for an integrated instance (plex, sonarr, etc) 6 | * @param id - The id of the form field. (anything.here.url) 7 | * @param value - The value of the form field. (http://localhost:8080) 8 | * @param index - The index of the current instance the instances list. (0) 9 | * @param instances - The instances list to verify unique names against. 10 | * @returns The feedback for the instance. 11 | */ 12 | export const validate = ( 13 | id: string, 14 | value: any, 15 | index: number, 16 | instances: Form[] | Form, 17 | ): string => { 18 | if (!Array.isArray(instances)) instances = [instances] 19 | const key = id.split('.').pop() 20 | 21 | if (key == 'name') { 22 | let found = '' 23 | instances?.forEach((m, i) => { 24 | if (i !== index && m?.name === value) { 25 | found = get(_)('phrases.NameInUseByInstance', { values: { number: i + 1 } }) 26 | return 27 | } 28 | }) 29 | if (found) return found 30 | return value ? '' : get(_)('phrases.NameMustNotBeEmpty') 31 | } else if (key == 'url') { 32 | return value.startsWith('http://') || value.startsWith('https://') 33 | ? '' 34 | : get(_)('phrases.URLMustBeginWithHttp') 35 | } else if (key == 'host' && value === '') { 36 | return get(_)('phrases.HostMustNotBeEmpty') 37 | } else if (key == 'apiKey' && value.length < 32) { 38 | return get(_)('phrases.APIKeyMustBeCountCharacters', { values: { count: 32 } }) 39 | } else if (key == 'token' && value.length < 8) { 40 | return get(_)('phrases.TokenMustBeCountCharacters', { values: { count: 8 } }) 41 | } 42 | 43 | return '' 44 | } 45 | -------------------------------------------------------------------------------- /frontend/src/includes/locale/de.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /frontend/src/includes/locale/es.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /frontend/src/includes/locale/fi.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /frontend/src/includes/locale/fr.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /frontend/src/includes/locale/hu.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /frontend/src/includes/locale/nl.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /frontend/src/includes/locale/pl.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /frontend/src/includes/locale/pt.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /frontend/src/includes/locale/sv.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /frontend/src/includes/locale/zh_Hant.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /frontend/src/includes/theme.svelte.ts: -------------------------------------------------------------------------------- 1 | import { readable, type Readable, type Unsubscriber } from 'svelte/store' 2 | import Cookies from 'js-cookie' 3 | 4 | const themes = ['dark', 'light'] 5 | const cookieName = 'theme' 6 | 7 | /** We save theme in a cookie and a svelte store, so it persists across page loads. */ 8 | class ThemeClass { 9 | private theme: Readable 10 | public change: (value: string) => void = () => {} 11 | public list = themes 12 | public isDark = $state(true) 13 | 14 | constructor() { 15 | this.theme = readable('dark', set => { 16 | this.change = (theme: string) => ( 17 | set(theme), this.setVars(theme), Cookies.set(cookieName, theme) 18 | ) 19 | this.change(Cookies.get(cookieName) ?? 'dark') 20 | }) 21 | } 22 | 23 | public subscribe = (run: (value: string) => void): Unsubscriber => 24 | this.theme.subscribe(run) 25 | 26 | private setVars = (newTheme: string) => { 27 | this.isDark = newTheme.includes('dark') 28 | this.isDark 29 | ? window.document.body.classList.add('dark-mode') 30 | : window.document.body.classList.remove('dark-mode') 31 | } 32 | 33 | /** Use this to toggle the current theme. 34 | * This will go away once we have multiple themes. 35 | */ 36 | public toggle = (e: Event) => ( 37 | e.preventDefault(), theme.change(this.isDark ? 'light' : 'dark') 38 | ) 39 | } 40 | 41 | /** Use this to get or change the current theme or list all themes. */ 42 | export const theme = new ThemeClass() 43 | -------------------------------------------------------------------------------- /frontend/src/index.ts: -------------------------------------------------------------------------------- 1 | import { mount } from 'svelte' 2 | import Index from '/src/Index.svelte' 3 | import '/src/assets/app.css' 4 | import '/src/includes/locale/index.svelte.ts' 5 | import 'bootstrap/dist/css/bootstrap.min.css' 6 | 7 | export default mount(Index, { target: document.getElementById('app')! }) 8 | -------------------------------------------------------------------------------- /frontend/src/navigation/Section.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 34 | 35 | 47 | -------------------------------------------------------------------------------- /frontend/src/pages/commands/Index.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 30 | 31 |
32 | 33 | 34 | 35 |