feat(web,server): improve offline mode functionality

Enhance offline mode to fully support local-only operation:

Frontend changes:
- Hide OAuth login options when offline (SignIn.tsx)
  OAuth providers require external internet access and won't work
  offline, so they're now hidden when network is unavailable
- Coordinate inputs already available in LocationDialog for manual entry
  Users can enter lat/lng directly when map tiles don't load

Backend changes:
- Add better logging for webhook network failures (webhook.go)
  Webhooks already fail gracefully via async goroutines, now with
  clearer logging to indicate network may be offline

The app now functions fully offline for core features when both
frontend and backend are running on local network:
- Local username/password auth works (OAuth hidden)
- All memo operations work (webhooks logged but don't block)
- Maps allow coordinate input (tiles unavailable but functional)
- Geocoding falls back to coordinates (already implemented)
This commit is contained in:
Claude 2025-11-19 03:36:06 +00:00
parent 7717f84afe
commit 923a346eac
No known key found for this signature in database
2 changed files with 8 additions and 1 deletions

View File

@ -47,6 +47,11 @@ func Post(requestPayload *WebhookRequestPayload) error {
}
resp, err := client.Do(req)
if err != nil {
// Log network errors but don't fail the operation - webhooks are optional
slog.Warn("Failed to post webhook (network may be offline)",
slog.String("url", requestPayload.URL),
slog.String("activityType", requestPayload.ActivityType),
slog.Any("err", err))
return errors.Wrapf(err, "failed to post webhook to %s", requestPayload.URL)
}

View File

@ -9,6 +9,7 @@ import { Separator } from "@/components/ui/separator";
import { identityProviderServiceClient } from "@/grpcweb";
import { absolutifyLink } from "@/helpers/utils";
import useCurrentUser from "@/hooks/useCurrentUser";
import { useOfflineDetection } from "@/hooks/useOfflineDetection";
import { Routes } from "@/router";
import { instanceStore } from "@/store";
import { extractIdentityProviderIdFromName } from "@/store/common";
@ -19,6 +20,7 @@ import { storeOAuthState } from "@/utils/oauth";
const SignIn = observer(() => {
const t = useTranslate();
const currentUser = useCurrentUser();
const isOffline = useOfflineDetection();
const [identityProviderList, setIdentityProviderList] = useState<IdentityProvider[]>([]);
const instanceGeneralSetting = instanceStore.state.generalSetting;
@ -87,7 +89,7 @@ const SignIn = observer(() => {
</Link>
</p>
)}
{identityProviderList.length > 0 && (
{identityProviderList.length > 0 && !isOffline && (
<>
{!instanceGeneralSetting.disallowPasswordAuth && (
<div className="relative my-4 w-full">