Calendar: shared store reliability + event notifications
KC-5: Consolidate calendar access into CalendarStore.shared so Today, Calendar, Onboarding, and Settings share one permission state and event cache. requestAccess() debounces and reads authoritative status; turning on "Show Calendar Events" while unauthorized now prompts. New Settings → Data → Calendar row shows Connected/Connect/Denied. KC-6: Add calendar-event notifications. CalendarStore.upcomingEventNotifs snapshots visible events (next 7 days); NotificationManager schedules at start time plus each event alarm (all-day at 9am), capped at ~20 to stay under iOS's 64-notification limit. Wired into reschedule(). Log KC-5 and KC-6 in ISSUES.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -140,3 +140,78 @@ Files: `TaskContextMenu.swift`, `TaskActivityAttributes.swift`,
|
||||
pass (orange accent + timer) and can be refined.
|
||||
- Uses the iOS 16.1 `Activity.request(...contentState:...)` API (deprecation
|
||||
warning on 16.2+, still functional).
|
||||
|
||||
---
|
||||
|
||||
## KC-5 — Calendar "Connect" toggle unreliable / inconsistent across screens
|
||||
|
||||
**Status:** In Progress (implemented & build-verified; full reliability win is device-only)
|
||||
**Reported by:** User
|
||||
**Area:** Calendar / Permissions
|
||||
|
||||
### Description
|
||||
The iPhone Calendar "Connect" sometimes needed repeated taps, onboarding
|
||||
sometimes didn't reflect a granted calendar, and there was no place to see/manage
|
||||
connection status from Settings.
|
||||
|
||||
### Root cause
|
||||
Three independent calendar-permission states that never synced: `TodayView`,
|
||||
`CalendarView` each had their own `CalendarStore` + `EKEventStore`, and
|
||||
`OnboardingView` had its own raw `EKEventStore`. Granting in one left the others
|
||||
showing stale cached status until they happened to re-read it.
|
||||
|
||||
### Fix
|
||||
- `CalendarStore.shared` singleton — one source of truth used by Today, Calendar,
|
||||
Onboarding, and Settings. Connect once, every screen reflects it.
|
||||
- `requestAccess()` now debounces in-flight requests and reads the **authoritative**
|
||||
`EKEventStore.authorizationStatus` instead of trusting the callback bool;
|
||||
already-decided states re-sync via `refreshStatus()`.
|
||||
- Turning on "Show Calendar Events" while unauthorized now triggers the prompt.
|
||||
- New **Settings → Data → Calendar** row showing ● Connected / Connect / Denied,
|
||||
opening the connect sheet.
|
||||
|
||||
Files: `CalendarView.swift`, `TodayView.swift`, `OnboardingView.swift`,
|
||||
`SettingsView.swift`.
|
||||
|
||||
### Verification
|
||||
Builds cleanly. Cross-screen consistency + the Settings status row are testable in
|
||||
the Simulator; the request-reliability fix is best confirmed on a device.
|
||||
|
||||
---
|
||||
|
||||
## KC-6 — No notifications for calendar events
|
||||
|
||||
**Status:** In Progress (implemented & build-verified; fires on a real device with calendar access)
|
||||
**Reported by:** User
|
||||
**Area:** Calendar / Notifications
|
||||
|
||||
### Description
|
||||
Task reminders fired, but calendar events never produced a KisaniCal
|
||||
notification — events with no iOS alert (e.g. subscribed F1/TV feeds) notified
|
||||
the user of nothing.
|
||||
|
||||
### Root cause
|
||||
`NotificationManager` only scheduled workout + per-task notifications. There was
|
||||
no calendar-event path at all (`EKEvent`/`CalendarStore` were never involved).
|
||||
|
||||
### Fix
|
||||
- `CalendarStore.upcomingEventNotifs(days:)` snapshots visible events for the next
|
||||
7 days (respects enabled flag + hidden calendars) with start time + alarm offsets.
|
||||
- `NotificationManager.scheduleCalendarEvents(...)` schedules a notification at the
|
||||
event start ("on time") plus at each alarm the user set; all-day events fire at
|
||||
9am morning-of. Capped at ~20 to stay under iOS's 64 pending-notification limit;
|
||||
recurring instances kept unique via start-time in the id.
|
||||
- Wired into `reschedule(...)` (runs on foreground / scene-active / task changes).
|
||||
|
||||
Files: `CalendarView.swift`, `NotificationManager.swift`.
|
||||
|
||||
### How to test (device, calendar access granted)
|
||||
1. Create an event a few minutes out with **no alert** on a visible calendar.
|
||||
2. Background the app (triggers a reschedule).
|
||||
3. Notification fires at the event's start time.
|
||||
|
||||
### Known limitations / decisions
|
||||
- "All visible events" was chosen, so personal events that already have an iOS
|
||||
alert may double (system + KisaniCal). Feed events (F1/TV) are the main win.
|
||||
- Tapping a calendar notification opens the Today view (no calendar deeplink).
|
||||
- The 7-day window re-scans on each foreground; no background refresh beyond that.
|
||||
|
||||
@@ -79,6 +79,7 @@ final class NotificationManager: NSObject, ObservableObject, @unchecked Sendable
|
||||
let progress = workoutVM.progress
|
||||
let recorded = workoutVM.isTodayRecorded
|
||||
let tasks = taskVM.tasks
|
||||
let calEvents = CalendarStore.shared.upcomingEventNotifs(days: 7)
|
||||
|
||||
center.getNotificationSettings { [weak self] settings in
|
||||
guard let self else { return }
|
||||
@@ -88,10 +89,67 @@ final class NotificationManager: NSObject, ObservableObject, @unchecked Sendable
|
||||
self.scheduleWorkout(schedule: schedule, programs: programs, progress: progress, recordedToday: recorded)
|
||||
self.scheduleWorkoutConfirm(schedule: schedule, programs: programs)
|
||||
self.scheduleTasks(snapshot: tasks)
|
||||
self.scheduleCalendarEvents(snapshot: calEvents)
|
||||
self.updateBadge(snapshot: tasks)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Calendar event notifications
|
||||
|
||||
/// Notifies for visible calendar events: at start time ("on time") plus each
|
||||
/// alarm the user set on the event. Capped to stay under iOS's 64-notification
|
||||
/// limit. Recurring instances stay unique via their start time in the id.
|
||||
private func scheduleCalendarEvents(snapshot: [CalEventNotif]) {
|
||||
center.getPendingNotificationRequests { [weak self] pending in
|
||||
guard let self else { return }
|
||||
let old = pending.filter { $0.identifier.hasPrefix("kisani.calevent.") }.map { $0.identifier }
|
||||
self.center.removePendingNotificationRequests(withIdentifiers: old)
|
||||
|
||||
let now = Date()
|
||||
let cal = Calendar.current
|
||||
var budget = 20 // leave headroom under the 64 pending-notification ceiling
|
||||
|
||||
for ev in snapshot where budget > 0 {
|
||||
var fireTimes: [Date] = []
|
||||
if ev.isAllDay {
|
||||
var c = cal.dateComponents([.year, .month, .day], from: ev.start)
|
||||
c.hour = 9; c.minute = 0
|
||||
if let d = cal.date(from: c) { fireTimes.append(d) } // 9am morning-of
|
||||
} else {
|
||||
fireTimes.append(ev.start) // on time
|
||||
for off in ev.alarmOffsets { fireTimes.append(ev.start.addingTimeInterval(off)) }
|
||||
}
|
||||
|
||||
let times = Array(Set(fireTimes)).filter { $0 > now }.sorted()
|
||||
for (i, t) in times.enumerated() where budget > 0 {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = ev.title
|
||||
content.body = self.calEventBody(fire: t, start: ev.start, isAllDay: ev.isAllDay)
|
||||
content.sound = .default
|
||||
content.userInfo = ["deeplink": "calendar"]
|
||||
let comps = cal.dateComponents([.year, .month, .day, .hour, .minute], from: t)
|
||||
let stamp = Int(ev.start.timeIntervalSince1970)
|
||||
self.center.add(UNNotificationRequest(
|
||||
identifier: "kisani.calevent.\(ev.id).\(stamp).\(i)",
|
||||
content: content,
|
||||
trigger: UNCalendarNotificationTrigger(dateMatching: comps, repeats: false)
|
||||
))
|
||||
budget -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func calEventBody(fire: Date, start: Date, isAllDay: Bool) -> String {
|
||||
if isAllDay { return "Today" }
|
||||
let delta = start.timeIntervalSince(fire)
|
||||
if delta <= 60 { return "Starting now" }
|
||||
let mins = Int((delta / 60).rounded())
|
||||
if mins < 60 { return "Starts in \(mins) min" }
|
||||
let hrs = mins / 60
|
||||
return "Starts in \(hrs)h\(mins % 60 == 0 ? "" : " \(mins % 60)m")"
|
||||
}
|
||||
|
||||
// MARK: - Badge
|
||||
|
||||
private func updateBadge(snapshot: [TaskItem]) {
|
||||
|
||||
@@ -2,6 +2,15 @@ import SwiftUI
|
||||
import EventKit
|
||||
import EventKitUI
|
||||
|
||||
// Plain snapshot of a calendar event for scheduling notifications off the main actor.
|
||||
struct CalEventNotif {
|
||||
let id: String
|
||||
let title: String
|
||||
let start: Date
|
||||
let isAllDay: Bool
|
||||
let alarmOffsets: [TimeInterval] // seconds relative to start (negative = before)
|
||||
}
|
||||
|
||||
// MARK: - Calendar Store
|
||||
@MainActor
|
||||
final class CalendarStore: ObservableObject {
|
||||
@@ -133,6 +142,33 @@ final class CalendarStore: ObservableObject {
|
||||
return authStatus == .authorized
|
||||
}
|
||||
|
||||
/// Upcoming visible events over the next `days`, as plain snapshots for the
|
||||
/// notification scheduler. Honors the calendar-enabled flag and hidden calendars.
|
||||
func upcomingEventNotifs(days: Int) -> [CalEventNotif] {
|
||||
guard isAuthorized, localCalendarsEnabled else { return [] }
|
||||
let now = Date()
|
||||
guard let end = Calendar.current.date(byAdding: .day, value: days, to: now) else { return [] }
|
||||
let cals = ekStore.calendars(for: .event).filter { !hiddenCalendarIDs.contains($0.calendarIdentifier) }
|
||||
guard !cals.isEmpty else { return [] }
|
||||
let pred = ekStore.predicateForEvents(withStart: now, end: end, calendars: cals)
|
||||
return ekStore.events(matching: pred)
|
||||
.sorted { ($0.startDate ?? .distantFuture) < ($1.startDate ?? .distantFuture) }
|
||||
.compactMap { ev in
|
||||
guard let start = ev.startDate else { return nil }
|
||||
let offsets: [TimeInterval] = (ev.alarms ?? []).map { alarm in
|
||||
if let abs = alarm.absoluteDate { return abs.timeIntervalSince(start) }
|
||||
return alarm.relativeOffset
|
||||
}
|
||||
return CalEventNotif(
|
||||
id: ev.eventIdentifier ?? UUID().uuidString,
|
||||
title: ev.title ?? "Event",
|
||||
start: start,
|
||||
isAllDay: ev.isAllDay,
|
||||
alarmOffsets: offsets
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Exposed so the edit sheet can hand it to EKEventEditViewController.
|
||||
var eventStore: EKEventStore { ekStore }
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ struct SettingsView: View {
|
||||
@EnvironmentObject var workoutVM: WorkoutViewModel
|
||||
@EnvironmentObject var taskVM: TaskViewModel
|
||||
@ObservedObject private var auth = AuthManager.shared
|
||||
@ObservedObject private var calStore = CalendarStore.shared
|
||||
|
||||
// Persisted settings
|
||||
@AppStorage("appearanceMode") private var appearanceMode = 0
|
||||
@@ -30,6 +31,7 @@ struct SettingsView: View {
|
||||
@State private var showWidgets = false
|
||||
@State private var showGeneral = false
|
||||
@State private var showImport = false
|
||||
@State private var showCalendar = false
|
||||
@State private var showBackup = false
|
||||
@State private var showWorkoutSettings = false
|
||||
@State private var showRestTimer = false
|
||||
@@ -107,6 +109,7 @@ struct SettingsView: View {
|
||||
|
||||
// ── DATA ──
|
||||
SttSection(label: "Data") {
|
||||
SttNavRow(icon: "calendar", bg: AppColors.accentSoft, fg: AppColors.accent, label: "Calendar", value: calStore.isAuthorized ? "● Connected" : (calStore.isDenied ? "Denied" : "Connect"), valueColor: calStore.isAuthorized ? AppColors.green : nil) { showCalendar = true }
|
||||
SttNavRow(icon: "arrow.up.right", bg: AppColors.greenSoft, fg: AppColors.green, label: "Connected Sources", value: googleCalSync || iCloudCalSync ? "Connected" : nil) { showImport = true }
|
||||
SttNavRow(icon: "cloud", bg: AppColors.blueSoft, fg: AppColors.blue, label: "Library Sync", value: iCloudSync ? "● Synced" : "Off", valueColor: iCloudSync ? AppColors.green : nil, isLast: true) { showBackup = true }
|
||||
}
|
||||
@@ -163,12 +166,14 @@ struct SettingsView: View {
|
||||
.sheet(isPresented: $showWidgets) { WidgetsSheet().presentationDetents([.height(320)]).presentationDragIndicator(.visible) }
|
||||
.sheet(isPresented: $showGeneral) { GeneralSheet(showCompleted: $showCompleted, mondayFirst: $firstDayMonday).presentationDetents([.height(260)]).presentationDragIndicator(.visible) }
|
||||
.sheet(isPresented: $showImport) { ImportSheet(googleCal: $googleCalSync, iCloudCal: $iCloudCalSync).presentationDetents([.height(280)]).presentationDragIndicator(.visible) }
|
||||
.sheet(isPresented: $showCalendar) { CalendarConnectSheet(calStore: calStore).presentationDetents([.large]).presentationDragIndicator(.visible) }
|
||||
.sheet(isPresented: $showBackup) { BackupSheet(iCloudSync: $iCloudSync).presentationDetents([.height(280)]).presentationDragIndicator(.visible) }
|
||||
.sheet(isPresented: $showWorkoutSettings) { WorkoutSettingsSheet(unit: $weightUnit, sets: $defaultSets, reps: $defaultReps).environmentObject(workoutVM).presentationDetents([.large]).presentationDragIndicator(.visible) }
|
||||
.sheet(isPresented: $showRestTimer) { RestTimerSheet(seconds: $workoutVM.restTimerSeconds, enabled: $workoutVM.restTimerEnabled).presentationDetents([.height(360)]).presentationDragIndicator(.visible) }
|
||||
.sheet(isPresented: $showHelp) { HelpSheet().presentationDetents([.height(300)]).presentationDragIndicator(.visible) }
|
||||
.sheet(isPresented: $showFollow) { FollowSheet().presentationDetents([.height(280)]).presentationDragIndicator(.visible) }
|
||||
.sheet(isPresented: $showAbout) { AboutSheet().presentationDetents([.height(340)]).presentationDragIndicator(.visible) }
|
||||
.onAppear { calStore.refreshStatus() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
48
SERVICES.md
Normal file
48
SERVICES.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Rackpeek Services
|
||||
|
||||
Inventory of reverse-proxy hosts published via Rackpeek (<http://10.10.1.70:18080/services/list>).
|
||||
Format: **Service** — `IP:port` — URL. All `Public`, `Custom Certificate` unless noted; `?` = name unconfirmed.
|
||||
|
||||
- **Rackpeek** — `10.10.1.70:18080` — http://10.10.1.70:18080
|
||||
- **Backup** (Proxmox Backup Server) — `10.10.1.11:8007` — http://10.10.1.11:8007
|
||||
- **Proxmox VE** — `10.10.1.44:8006` — http://10.10.1.44:8006
|
||||
- **Uptime Kuma (Canal+)** — `10.10.1.50:3001` — http://10.10.1.50:3001
|
||||
- **Uptime Kuma (Airtel)** — `10.10.1.51:3001` — http://10.10.1.51:3001
|
||||
- **Uptime Kuma** — `10.10.1.20:3001` — http://10.10.1.20:3001
|
||||
- **Nginx Proxy Manager** — `10.10.1.5:81` — http://10.10.1.5:81
|
||||
- **Portainer** — `10.10.1.70:9000` — http://10.10.1.70:9000
|
||||
- **Netdata** — `10.10.1.20:19999` — http://10.10.1.20:19999
|
||||
- **Homarr** — `10.10.1.20:7575` — http://10.10.1.20:7575
|
||||
- **Plex** — `10.10.1.70:32400` — http://10.10.1.70:32400 _(HTTP Only)_
|
||||
- **Radarr** — `10.10.1.70:7878` — http://10.10.1.70:7878
|
||||
- **Sonarr** — `10.10.1.70:8989` — http://10.10.1.70:8989
|
||||
- **Prowlarr** — `10.10.1.70:9696` — http://10.10.1.70:9696
|
||||
- **Bazarr** — `10.10.1.70:6767` — http://10.10.1.70:6767
|
||||
- **Overseerr/Jellyseerr ?** — `10.10.1.70:5055` — http://10.10.1.70:5055
|
||||
- **Synology DSM ?** — `10.10.1.20:5000` — http://10.10.1.20:5000
|
||||
- **?** — `10.10.1.78:8181` — http://10.10.1.78:8181
|
||||
- **?** — `10.10.1.6:8181` — http://10.10.1.6:8181
|
||||
- **?** — `10.10.1.21:8181` — http://10.10.1.21:8181
|
||||
- **?** — `10.10.1.70:8181` — http://10.10.1.70:8181
|
||||
- **?** — `10.10.1.7:8181` — http://10.10.1.7:8181
|
||||
- **?** — `10.10.1.5:8181` — http://10.10.1.5:8181
|
||||
- **?** — `10.10.1.21:80` — http://10.10.1.21:80
|
||||
- **?** — `10.10.1.20:80` — http://10.10.1.20:80
|
||||
- **?** — `10.10.1.13:80` — http://10.10.1.13:80
|
||||
- **?** — `10.10.1.78:80` — http://10.10.1.78:80
|
||||
- **?** — `10.10.1.7:8080` — http://10.10.1.7:8080
|
||||
- **?** — `10.10.1.70:8080` — http://10.10.1.70:8080
|
||||
- **?** — `10.10.1.21:8080` — http://10.10.1.21:8080
|
||||
- **?** — `10.10.1.70:8888` — http://10.10.1.70:8888
|
||||
- **?** — `10.10.1.20:8888` — http://10.10.1.20:8888
|
||||
- **?** — `10.10.1.21:8888` — http://10.10.1.21:8888
|
||||
- **?** — `10.10.1.70:3030` — http://10.10.1.70:3030
|
||||
- **?** — `10.10.1.70:9080` — http://10.10.1.70:9080
|
||||
- **?** — `10.10.1.21:3002` — http://10.10.1.21:3002
|
||||
- **?** — `10.10.1.7:8281` — http://10.10.1.7:8281 _(HTTP Only)_
|
||||
- **?** — `10.10.1.70:6246` — http://10.10.1.70:6246 _(HTTP Only)_
|
||||
- **?** — `10.10.1.21:18789` — http://10.10.1.21:18789
|
||||
- **?** — `10.10.1.70:8212` — http://10.10.1.70:8212
|
||||
- **?** — `10.10.1.10:7655` — http://10.10.1.10:7655
|
||||
- **?** — `10.10.1.70:7476` — http://10.10.1.70:7476 _(HTTP Only)_
|
||||
- **?** — `10.10.1.20:9412` — http://10.10.1.20:9412
|
||||
@@ -7,11 +7,67 @@
|
||||
|
||||
# Network Layout
|
||||
|
||||
| Device | IP | Purpose |
|
||||
|--------------|------------|-----------------|
|
||||
| Kuma-Canal | 10.10.1.50 | Monitor Canal+ |
|
||||
| Kuma-Airtel | 10.10.1.51 | Monitor Airtel |
|
||||
| Omada Gateway| 10.10.1.1 | Policy Routing |
|
||||
| Device | IP | Purpose |
|
||||
|--------------|----------------------|----------------------------|
|
||||
| Kuma-Canal | 10.10.1.50 | Monitor Canal+ |
|
||||
| Kuma-Airtel | 10.10.1.51 | Monitor Airtel |
|
||||
| Omada Gateway| 10.10.1.1 | Policy Routing |
|
||||
| Rackpeek | 10.10.1.70:18080 | Rack/services dashboard |
|
||||
|
||||
Rackpeek services list: <http://10.10.1.70:18080/services/list>
|
||||
|
||||
---
|
||||
|
||||
# Proxy Hosts / Services
|
||||
|
||||
Reverse-proxy hosts published via Rackpeek. All `Public` access, all `Online` as of last sync.
|
||||
Service names are the proxy source (`.sanctum` suffix omitted). Entries marked `?` are inferred
|
||||
from the port or still need confirmation.
|
||||
|
||||
| Service | Destination | SSL | Access | Status | Created |
|
||||
|------------------------|------------------------|--------------------|--------|--------|-------------------------------|
|
||||
| ? | http://10.10.1.78:8181 | Custom Certificate | Public | Online | Nov 20, 2025 at 3:47:23 AM |
|
||||
| Uptime Kuma (Airtel) | http://10.10.1.51:3001 | Custom Certificate | Public | Online | Nov 25, 2025 at 12:18:08 AM |
|
||||
| ? | http://10.10.1.6:8181 | Custom Certificate | Public | Online | Nov 20, 2025 at 3:48:33 AM |
|
||||
| Backup | http://10.10.1.11:8007 | Custom Certificate | Public | Online | Dec 4, 2025 at 1:09:02 PM |
|
||||
| Bazarr | http://10.10.1.70:6767 | Custom Certificate | Public | Online | Nov 25, 2025 at 2:54:35 AM |
|
||||
| ? | http://10.10.1.21:8181 | Custom Certificate | Public | Online | Nov 20, 2025 at 3:49:44 AM |
|
||||
| Uptime Kuma (Canal+) | http://10.10.1.50:3001 | Custom Certificate | Public | Online | Nov 25, 2025 at 12:16:37 AM |
|
||||
| ? | http://10.10.1.21:80 | Custom Certificate | Public | Online | Nov 20, 2025 at 3:51:05 AM |
|
||||
| ? | http://10.10.1.20:80 | Custom Certificate | Public | Online | Nov 20, 2025 at 3:51:50 AM |
|
||||
| Synology DSM ? | http://10.10.1.20:5000 | Custom Certificate | Public | Online | Nov 25, 2025 at 1:19:01 AM |
|
||||
| ? | http://10.10.1.7:8080 | Custom Certificate | Public | Online | Nov 20, 2025 at 3:53:28 AM |
|
||||
| ? | http://10.10.1.70:3030 | Custom Certificate | Public | Online | Nov 20, 2025 at 3:56:57 AM |
|
||||
| Homarr | http://10.10.1.20:7575 | Custom Certificate | Public | Online | Dec 4, 2025 at 1:05:58 PM |
|
||||
| ? | http://10.10.1.70:9080 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:37:33 AM |
|
||||
| ? | http://10.10.1.70:8181 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:41:13 AM |
|
||||
| ? | http://10.10.1.21:3002 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:43:06 AM |
|
||||
| ? | http://10.10.1.70:8080 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:43:56 AM |
|
||||
| ? | http://10.10.1.7:8181 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:45:03 AM |
|
||||
| ? | http://10.10.1.7:8281 | HTTP Only | Public | Online | Nov 25, 2025 at 12:07:57 AM |
|
||||
| Uptime Kuma | http://10.10.1.20:3001 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:50:20 AM |
|
||||
| ? | http://10.10.1.70:6246 | HTTP Only | Public | Online | Nov 24, 2025 at 1:33:47 PM |
|
||||
| Netdata | http://10.10.1.20:19999| Custom Certificate | Public | Online | Nov 30, 2025 at 10:25:35 PM |
|
||||
| ? | http://10.10.1.21:18789| Custom Certificate | Public | Online | Feb 18, 2026 at 7:30:40 PM |
|
||||
| Overseerr/Jellyseerr ? | http://10.10.1.70:5055 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:51:13 AM |
|
||||
| ? | http://10.10.1.70:8212 | Custom Certificate | Public | Online | Nov 25, 2025 at 2:00:57 AM |
|
||||
| Plex | http://10.10.1.70:32400| HTTP Only | Public | Online | Nov 25, 2025 at 3:04:23 AM |
|
||||
| Portainer | http://10.10.1.70:9000 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:52:39 AM |
|
||||
| Prowlarr | http://10.10.1.70:9696 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:53:47 AM |
|
||||
| Proxmox VE | http://10.10.1.44:8006 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:54:29 AM |
|
||||
| Nginx Proxy Manager | http://10.10.1.5:81 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:55:02 AM |
|
||||
| ? | http://10.10.1.10:7655 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:56:01 AM |
|
||||
| ? | http://10.10.1.70:8888 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:56:35 AM |
|
||||
| Radarr | http://10.10.1.70:7878 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:57:22 AM |
|
||||
| ? | http://10.10.1.5:8181 | Custom Certificate | Public | Online | Nov 21, 2025 at 2:58:12 AM |
|
||||
| ? | http://10.10.1.13:80 | Custom Certificate | Public | Online | Nov 21, 2025 at 3:00:30 AM |
|
||||
| ? | http://10.10.1.78:80 | Custom Certificate | Public | Online | Nov 21, 2025 at 3:01:36 AM |
|
||||
| Sonarr | http://10.10.1.70:8989 | Custom Certificate | Public | Online | Nov 21, 2025 at 3:02:24 AM |
|
||||
| ? | http://10.10.1.20:8888 | Custom Certificate | Public | Online | Nov 21, 2025 at 3:03:28 AM |
|
||||
| ? | http://10.10.1.21:8888 | Custom Certificate | Public | Online | Nov 21, 2025 at 3:04:31 AM |
|
||||
| ? | http://10.10.1.21:8080 | Custom Certificate | Public | Online | Dec 4, 2025 at 1:03:19 PM |
|
||||
| ? | http://10.10.1.70:7476 | HTTP Only | Public | Online | Feb 6, 2026 at 7:53:14 PM |
|
||||
| ? | http://10.10.1.20:9412 | Custom Certificate | Public | Online | Nov 25, 2025 at 1:16:55 AM |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user