feat(mobile): env editing, domains, scaling, notifications (#61)

Companion app was read-mostly — quick fixes still needed a workstation.

- service/variables: full env editor — add/edit/delete rows, secret
  toggle, masked-secret preservation (untouched secrets resubmit the
  "********" placeholder so stored ciphertext survives the PUT).
- service/domains: list with default/TLS status, add, remove,
  set-default.
- service/settings: replicas stepper (0–16), container port, scale-to-
  zero toggle + idle minutes — saves via PUT /services/:id.
- (tabs)/notifications: alerts tab with unread badge on the tab bar,
  mark-read per item and mark-all.
- Service detail gains a Manage section linking all three screens.

State initializes at mount inside a child component rather than an
effect — post-save refetches no longer clobber in-flight edits, and
the react-hooks/set-state-in-effect lint stays clean.

Verified: tsc --noEmit, expo lint, expo export (android bundle,
1378 modules — router graph compiles).

Authored By: TDvorak <[email protected]>
This commit is contained in:
Tomáš Dvořák authored and GitHub committed 2026-10-09 22:33:06 +02:00
1 parent 3cc84553f4
commit 604319ee06
8 files changed
+758

No files matched your search

+20
View File
@@ -1,9 +1,19 @@
import React from 'react';
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import { useQuery } from '@tanstack/react-query';
import { useApi } from '../../session';
import { colors } from '../../theme';
export default function TabsLayout() {
const api = useApi();
const notifs = useQuery({
queryKey: ['notifications'],
queryFn: () => api.notifications(),
refetchInterval: 60_000,
});
const unread = notifs.data?.unread ?? 0;
return (
<Tabs
screenOptions={{
@@ -41,6 +51,16 @@ export default function TabsLayout() {
),
}}
/>
<Tabs.Screen
name="notifications"
options={{
title: 'Alerts',
tabBarBadge: unread > 0 ? unread : undefined,
tabBarIcon: ({ color, size }) => (
<Ionicons name="notifications-outline" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="settings"
options={{