feat: עיצוב מחדש של מסך הגדרות הניהול + captain-definition#23
Open
27180781 wants to merge 53 commits into
Open
feat: עיצוב מחדש של מסך הגדרות הניהול + captain-definition#2327180781 wants to merge 53 commits into
27180781 wants to merge 53 commits into
Conversation
Replace the generic key/value table with a structured form: settings are grouped by category (general, auth, ads, webhook, notifications, FCM), each with Hebrew labels and descriptions. Booleans are toggles, regex rules have separate pattern/replace fields, and a paste-JSON helper auto-fills FCM service account fields. A collapsible "advanced" section preserves manual key/value entry for unknown settings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a dedicated admin tab "שילוב פרסומות ממגנט" where admins paste an HTML/JS embed snippet from the Magnet ad platform. Ads render between chat messages according to configurable rules: either every N messages (with optional minimum time gap) or every N seconds (with optional minimum new-messages gap). Each ad slot is lazy-loaded with IntersectionObserver — the external embed only fires when the user scrolls near it, and re-fires on every re-mount so each viewer (and each scroll) gets a live ad. If the embed produces no DOM content within 5 seconds, the slot collapses silently. Backend exposes a new public GET /api/ads/magnet endpoint and persists the seven magnet_* keys in the existing settings:list store. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a publisher API key field and a stats viewer in the Magnet admin tab. A new admin-protected endpoint GET /api/admin/magnet/stats proxies the request to Magnet's publisher-stats function so the API key never reaches the browser. The stats panel shows clicks and earnings (today / week / month) in two columns with currency formatting, displays the site domain, and has a refresh button with a last-updated timestamp. Errors from Magnet (400 invalid key, 404 unapproved site) are surfaced in Hebrew. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add defensive fallbacks so a regression in the Magnet ad-injection logic can never prevent regular chat messages from rendering: - rebuildItems() now wraps buildItems() in try/catch and falls back to plain message items if it throws or returns an empty list while there are messages. - Template renders directly from messages[] when items[] is empty, bypassing the ad-injection layer entirely as a safety net. - ngOnInit catches a rejected loadSettings() promise so it cannot block the subsequent rebuildItems call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous approach replaced the @for over messages with a @for over a new ChatItem[] union type. That introduced a subtle regression where messages stopped rendering for some users. Restore the original @for (message of messages; track message) loop and inject the magnet ad slot as a sibling <nb-list-item> right after each message that should have an ad — driven by an adSlotsAfter Set<number> of message IDs. Service no longer builds an items array; it returns the Set directly via computeAdSlots(). The chat component owns adSlotsAfter and recomputes it on every message-list mutation. If computation throws, the Set stays empty and messages render unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dering Despite restoring the @for messages template loop, deployed builds were still rendering an empty chat list with no messages, no loading spinner, and no "no messages" placeholder — a state inconsistent with what the template should produce. To unblock users, fully revert chat.component.ts and chat.component.html to the pre-magnet snapshot. The magnet ads service, slot component, admin tab, and backend endpoints remain in place; ad injection into the chat list will be re-introduced separately once the regression is understood. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step 1 of incremental re-introduction: add the MagnetAdsService injection, the adSlotsAfter Set, the rebuildItems() helper, and the ngOnInit loadSettings() call. The template is left unchanged from the known-working revert. If the chat still renders, we know the regression came from the template @if injection rather than the service wiring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step 2 of incremental re-introduction: add the @if (adSlotsAfter.has(...)) template block, but render a plain <div> placeholder instead of the real <app-magnet-ad-slot> component. If messages still render with this in place, the regression was tied to the magnet-ad-slot component (DI, ngAfterViewInit, IntersectionObserver, etc.) rather than to the @if template structure itself. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step 3 of incremental re-introduction: instead of adding a sibling <nb-list-item> after the message item, place the ad placeholder inside the same <nb-list-item> as the message. This avoids creating a second projected list-item child of <nb-list> via @if, which appears to be what blocks rendering — Nebular's <nb-list> may rely on a content-child query that does not survive a conditional second item. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Now that the regression is understood (creating a sibling <nb-list-item> via @if inside @for breaks Nebular's <nb-list> rendering), wire the real <app-magnet-ad-slot> component back in — but place it inside the existing <nb-list-item> rather than as a sibling. This keeps every iteration of @for producing exactly one <nb-list-item>, which is what <nb-list> expects. Also re-add MagnetAdSlotComponent to ChatComponent's imports. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap the magnet ad slot in the same visual structure as a regular admin message: channel logo on the side, channel name + 'פרסומת' label above, and the embed itself rendered inside the same .message-card bubble used for chat messages. Inject ChatService into the slot component to read channelInfo for the logo and name. The collapsed state now toggles a CSS class on the wrapper (display: none) instead of rebuilding the DOM, which keeps the #host ViewChild stable across renders. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a new "אנליטיקס וטראקינג" category in the admin settings UI with a textarea field for pasting any HTML/JS analytics snippet (Google Analytics, GTM, Meta Pixel, etc.). The backend stores it under the analytics_head settings key and serveSpaFile injects it into the served index.html immediately before the closing </head> tag, so the snippet runs on every page load. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update SET.md with the changes introduced over this work session: - The new visual settings UI with categorized cards. - The analytics_head setting and its head injection. - The full Magnet ad integration (frequency rules, lazy loading, ad bubble rendering, stats panel) and the architectural note about why ads are nested inside the existing <nb-list-item> rather than as siblings. - The new /api/ads/magnet and /api/admin/magnet/stats endpoints. - A complete refreshed settings table including all magnet_* keys and analytics_head. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rewrite privileges.go with new GlobalRole/ChannelRole system (SuperAdmin, Owner, Moderator, Writer) - Add channels.go with ChannelData, ChannelFeatures, and channel CRUD handlers - Add channelMiddleware to inject channel into request context - Channel owners can manage their users; super admin controls everything https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
- Replace old Privileges map with GlobalRole and ChannelRoles fields - Update getUser to populate GlobalRole and ChannelRoles from privilegesUsers - Update registeringEmail to accept slug for per-channel email tracking - Remove checkPrivilege (replaced by isSuperAdmin/hasChannelRole) https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
- db.go: all DB functions now channel-scoped (slug parameter), added channel CRUD
(dbCreateChannel, dbGetChannel, dbListChannels, dbDeleteChannel, dbSetChannelFeatures)
Settings split: per-channel (channel:{slug}:settings) + global (global:settings)
- messages.go: slug from context, SSE subscribes to events:{slug}
- settings.go: per-channel getSettings/setSettings + getGlobalSettings/setGlobalSettings
- statistics.go: per-channel SSE counters via sync.Map
- reactions.go: per-channel emojis loaded from DB per request
- report.go, scheduled.go: channel-scoped DB calls
- notifications.go: FCM stays global, subscriptions are per-channel
- ads.go, webhook.go: load per-channel config via getChannelConfig()
- api.go: API key validated from per-channel settings
- files.go: file URL includes channel slug, max size from per-channel config
- channelInfo.go: features sourced from channel context
- main.go: full routing rewrite - /api/channel/{slug}/... and /api/super-admin/...
https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
- ChannelFeatures: add MagnetLockedByAdmin bool (set by super admin only) - db.go: add GlobalMagnetConfig struct + dbGetGlobalMagnetConfig/dbSetGlobalMagnetConfig stored at global:magnet:config - ads.go: getMagnetAdsSettings now checks if channel is locked (LockAll, LockedChannels list, or MagnetLockedByAdmin flag) and returns global config instead of channel config - ads.go: add getGlobalMagnetConfig/setGlobalMagnetConfig handlers for super admin - ads.go: syncMagnetLockFlags goroutine syncs MagnetLockedByAdmin on all channels when super admin saves global config - getMagnetStats now reads API key from global magnet config - main.go: add GET/POST /api/super-admin/magnet/config routes https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
…s Magnet) - ChannelFeatures: add AdsLockedByAdmin bool - db.go: add GlobalAdsConfig struct + dbGetGlobalAdsConfig/dbSetGlobalAdsConfig stored at global:ads:config (src, width, lockAll, lockedChannels) - ads.go: getAdsSettings checks if channel is locked and returns global src/width - ads.go: add getGlobalAdsConfig/setGlobalAdsConfig handlers + syncAdsLockFlags goroutine - main.go: add GET/POST /api/super-admin/ads/config routes https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
- user.model.ts: add globalRole, channelRoles, publicName fields - super-admin.service.ts: all super admin API calls - super-admin.guard.ts: route guard (globalRole === super_admin only) - super-admin/channels: channels list, channel features, channel users components - super-admin/global-ads: global ads config component (partial) https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
- super-admin-panel.component: main shell with sidebar menu (ערוצים, פרסומות iframe, פרסומות מגנט, משתמשים, הגדרות גלובליות, סטטיסטיקות) - channels-list: table with create/edit-features/manage-users/delete actions - channel-features: all 12 feature toggles with Hebrew labels (incl. lock indicators) - channel-users: add/remove/change-role per channel, save button - global-ads: src, width, lockAll toggle, locked channels tag input - global-magnet: full magnet config + frequency settings + lock rules - global-users: read-only overview of all users with role chips - global-settings: key-value FCM/VAPID settings editor - statistics: reset peak connections + magnet stats display - app.routes.ts: add /super-admin route with SuperAdminGuard - channel-header: add "פאנל מנהל-על" link for super admins - channel.component: show input footer for super_admin + channel role holders https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
- storage.go: R2 client init (aws-sdk-go-v2/s3), upload, exists-check, download helpers
Reads env vars: R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY,
R2_BUCKET_NAME, R2_PUBLIC_URL (optional)
- files.go: file metadata moved to Redis (key: file:{id}), YAML fallback for legacy files
If R2 configured: uploads to R2, serves via redirect (public URL) or proxy
If R2 not configured: local disk at /app/files/ (backward compatible)
Deduplication by SHA-256 hash preserved for both R2 and local
- main.go: call initR2() on startup
- sample.env: document R2 env vars
- go.mod/go.sum: add aws-sdk-go-v2 dependencies
https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
…ression - Backend: storage quota tracking with per-channel and global defaults - Backend: auto-cleanup oldest files when quota is near full - Backend: TinyPNG API integration — compresses PNG/JPEG/WebP before upload - Frontend (admin): channel storage panel with usage bar, warnings, auto-cleanup toggle - Frontend (admin): TinyPNG API key field in settings (storage category) - Frontend (super-admin): global storage quota config panel - Frontend (super-admin): per-channel storage view with quota override and usage bar - Frontend (super-admin): "אחסון" button in channels list to open per-channel storage - Super admin service: added getGlobalStorageConfig/setGlobalStorageConfig/getChannelStorage/setChannelStorage https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
- Public landing page at '/' with hero, how-it-works, features, and request form - Route '/' is now the landing page; '/channel' is the auth-guarded channel app - Visitors fill out a form (name, email, desired slug, description) - Backend stores requests in Redis, exposed via GET /api/super-admin/channel-requests - Super admin can approve (auto-creates channel + assigns owner) or reject requests - Approval shows slug, owner email, and the channel link to copy and send manually - Login redirects super_admin → /super-admin, others → /channel - Logout navigates back to the landing page - Super admin panel: new 'בקשות לערוצים' menu item with inline approve/reject table https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
Security (data isolation): - serveFile: verify meta.ChannelSlug matches URL slug — blocks cross-channel file access - addNewPost (/import): validate slug against slugRegex to prevent key injection Performance (50+ concurrent channels): - Redis pool: PoolSize=100, MinIdleConns=10, retries, timeouts (was default 10) - dbDeleteChannel: replace O(keyspace) SCAN with targeted pipeline using known key names + message IDs from sorted set — O(messages) instead of O(total keys) - Scheduled messages: maintain sorted set "scheduled:due_channels" (score=next due time) so the goroutine only wakes channels with pending messages, not all channels every minute - Lua getMessageRange: add max_scan_rounds=20 guard to prevent unbounded loops on sparse data Rate limiting: - File uploads: 30/min per channel (burst=10) via golang.org/x/time/rate returns HTTP 429 when exceeded https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
Redis Streams (horizontal scaling):
- Replace pub/sub with Redis Streams for SSE events
* publishEvent() uses XADD (MaxLen~1000, auto-trim)
* getEvents() uses XREAD BLOCK with 5s timeout loop
* Supports Last-Event-ID header: clients reconnect without missing events
* Multiple backend instances can all serve SSE from the same stream
- Stream key: channel:{slug}:events (consistent naming convention)
- Stream keys added to dbDeleteChannel cleanup list
Redis Universal Client (Cluster/Sentinel support):
- rdb changed from *redis.Client to redis.UniversalClient
- NewUniversalClient: single addr = normal, multiple REDIS_ADDRS = cluster,
REDIS_MASTER set = Sentinel — zero code changes needed to scale up
Pre-signed R2 URLs (backend off the file-serving path):
- r2PresignURL() generates 1-hour signed URLs via s3.NewPresignClient
- serveFile: public bucket → CDN redirect, private bucket → pre-signed URL,
only falls back to backend proxy if presigning fails
HTTP ETags for message caching:
- touchLastModified() stores nano-timestamp on every message write/edit/delete
- getMessages returns ETag header; returns 304 Not Modified when client is current
- Eliminates redundant Lua script executions for unchanged channels
Per-user upload rate limiting:
- Rate limiter key is now userEmail:channelSlug instead of just slug
- Prevents one user from exhausting the channel's upload budget
- 30 uploads/min per user per channel (burst 10)
https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
- Add SlugService: central store for the active channel slug
- Add /channel/:slug route; ChannelComponent resolves slug from URL param
or redirects to /channel/<first-slug> from user's channelRoles
- AuthGuard saves state.url to localStorage before redirecting to /login
- LoginComponent: load userInfo after OAuth callback, restore returnUrl
on redirect; super_admin goes to /super-admin, others go to /
- Fix all frontend API calls to include /channel/{slug}/:
chat.service: info, messages, events, emojis, reactions, report
admin.service: new, edit, delete, upload, users, settings, emojis,
reports, scheduled-messages, statistics
ads.service: ads/settings
notifications.service: notifications-config, notifications-subscribe
magnet-ads.service: ads/magnet
- Fix ChannelComponent to guard child render behind slugReady flag
- Fix channel-header: show admin menu via channelRoles instead of old
privileges field
- Fix chat.component: scheduled messages + delete visibility use
channelRoles instead of legacy privileges
- Fix magnet-ads.component: stats call uses correct /super-admin route
- AdminService.clearCache() resets schedulingMessages on channel switch
https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
1. Channel param subscription: replace snapshot with paramMap.subscribe so navigating /channel/foo → /channel/bar re-initializes the component without destroying it (Angular reuses the instance for same-route nav). Also implement OnDestroy to unsubscribe and avoid memory leaks. 2. No-channel state: when the user has no channelRoles the component now sets noChannel=true and renders a Hebrew error message instead of a blank screen. 3. Login checkUserInfo timing: remove the try/finally pattern that reset checkUserInfo synchronously before the async login().then() chain completed. Now the flag is managed explicitly at each code path so the loading indicator reflects actual in-flight state. https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
Problems fixed: 1. No user feedback on disconnect — added a sticky orange Hebrew banner "אין חיבור לאינטרנט" that appears immediately on window:offline and disappears on window:online. 2. keepAlive forced-reconnect lost messages — the old code created a new EventSource (no Last-Event-ID) after 60 s; missed messages were gone. Now: (a) threshold reduced 60 s → 35 s (backend heartbeat is 25 s so one missed beat triggers reconnect), (b) loadMissedMessages() is called after every forced reconnect to HTTP-fetch messages newer than maxId. 3. SSE onopen now detects reconnect (sseEverConnected flag) and calls loadMissedMessages() so the browser's natural auto-reconnect also syncs the gap, not just the keepAlive path. 4. window:online listener: hides banner, shows success toast "החיבור חודש", re-initialises SSE, and calls loadMissedMessages(). 5. window:offline listener: shows the banner immediately without waiting for the 35 s heartbeat timeout. 6. onerror handler cleaned up — browser handles SSE retry automatically with Last-Event-ID; logging the raw error was the only previous action. https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
Problems fixed: 1. Initial-load flash-then-jump: the list was visible during the 200ms between first render (showing newest messages) and scrollToId (jumping to last-unread). Fixed with isVisible / .chat-hidden: content stays invisible until scroll position is resolved, then appears already in the right place. setLastReadMessage is now called AFTER the jump so a page refresh still brings the user back to the unread position. 2. Duplicate messages after reconnect: loadMissedMessages (HTTP) and the browser SSE auto-reconnect (Last-Event-ID) could both deliver the same messages. Fixed with an existing-ID Set filter in loadMissedMessages and a same-ID guard in the SSE new-message case. 3. Scroll anchor in loadMissedMessages: adding many messages at the visual bottom could shift the viewport. Fixed by capturing the topmost visible element's bounding rect before the unshift and calling window.scrollBy to compensate the delta. 4. thereNewMessages badge shown unnecessarily: if the user was already at the bottom when missed messages arrived the badge now stays hidden and the view scrolls down automatically instead. 5. SSE new-message thereNewMessages: only shows badge when user is not at bottom (consistent with loadMissedMessages behaviour). https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
…sh, scroll anchor jump - channel.component.ts: add await Promise.resolve() between slugReady=false and slugReady=true so Angular CD actually destroys ChatComponent on channel switch; without it the same-tick toggle is collapsed and child state (isVisible, messages, lastReadMessageId) persists across slugs - chat.component.ts: guard Math.max() with messages.length check to avoid -Infinity startId when loadMessages() is called with an empty array - chat.component.ts: remove manual scroll-anchor/delta compensation in loadMissedMessages(); the browser's overflow-anchor CSS handles viewport preservation automatically for flex-column-reverse, and the manual scrollBy could cause visible jumps https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
…ility
Frontend:
- admin.service: replace PrivilegeUser (old boolean flags) with ChannelUser
{email, role} to match the backend's actual channel-user API shape; fix
setChannelUsers payload key from { list } to { users } (backend was silently
ignoring all updates)
- privileg-dashboard: rewrite to use role selector (moderator/writer) instead
of admin/moderator/writer checkboxes; fix isOwner guard using channelRoles
instead of privileges (buttons were permanently disabled); fix nullUser
reference bug (spread into new object instead of aliasing)
- channel-header: store contextMenuService subscription and unsubscribe in
ngOnDestroy — each channel switch was adding another persistent listener;
fix logout() double-navigation where router.navigate(['/']) ran after the
401 catch already navigated to /login
- channel.component: call magnetAds.clearCache() on channel switch so the
next channel loads its own ad settings
- magnet-ads.service: add clearCache() method
Backend:
- auth.go: check idtoken.NewValidator error before using the validator — a nil
validator would panic on the next line
- privileges.go / channels.go / main.go: convert initializePrivilegeUsers()
from void+panic to error return; request handlers now log and continue
instead of crashing the server on a transient Redis error; startup still
panics as before
https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
…tidy - golang:1.24 → golang:1.25 (transitive dep now requires >=1.25) - Remove go mod tidy from Dockerfile — go.sum is committed and correct, running tidy at build time downloads everything unnecessarily - Split backend COPY into two steps: go.mod/go.sum first + go mod download, then source files. Docker caches the download layer when only .go files change, so repeated builds skip the multi-minute dependency download https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
Merge conflict left two r.Route("/api/super-admin") blocks; chi panics
on duplicate path registration. Moved channel-request admin routes into
the existing super-admin block.
https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
After Google login, redirect to /channel directly instead of / to avoid rendering the landing page. Also suppress the landing page DOM until the auth check resolves so logged-in users navigating to / see nothing before the redirect fires. https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
The icon font is imported from jsDelivr CDN in styles.scss; the local npm package only installed ~60 MB of SVG files that were causing "no space left on device" failures on the Caprover build server. https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
nb-card and nb-menu were rendered without nb-layout, so Nebular's CSS custom properties were never initialised — components appeared completely unstyled. Replace the card-based root with a proper nb-layout/nb-sidebar structure that mirrors how the channel view is built. https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
Without name attributes, Angular's ngModel may not update the component property, causing the validation check to see empty values even when the user has typed into the fields. Also surface the actual backend error message in the toastr so slug-format errors are visible. https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
Instead of a plain text message, users without any channel role see a proper Nebular-styled form pre-filled with their name/email, along with a logout button in the header. https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
The slug regex requires lowercase only. Pre-fill converts the desired slug to lowercase and replaces invalid chars with hyphens. Typing also forces lowercase. Also surface the actual backend error message in the toast. https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
- New channel setting 'enter_sends_message': Enter sends, Ctrl+Enter inserts a newline (Shift+Enter still inserts a newline regardless) - Pasting an image from clipboard auto-uploads it and embeds the link, same as attaching a file via the paperclip button https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
The boolean value was being saved as '1' but checked against 'true'. Also fixes reactivity: setting is now stored on AdminService and updated immediately when settings are saved, so no page reload is needed. https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
Channel admins now see only an on/off toggle for push notifications. All FCM credentials (VAPID, Firebase SDK config, service account JSON) are configured once in the super-admin global settings panel. Backend reads on_notification per-channel so each channel can opt in/out independently while sharing the same Firebase project credentials. https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
…ken fields Channel admin settings now only contain fields that actually work per-channel. Removed fields that were silently ignored by the backend: - require_auth, require_auth_for_view_files, count_views (controlled by super-admin Features) - custom_title, analytics_head (were read from global config, not per-channel) - contact_us (managed via edit channel info, not settings) Added custom_title and analytics_head to super-admin global settings with structured form fields alongside the existing FCM section. https://claude.ai/code/session_015yWNYaVjk7ozyguKLA8vvx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
regex-replace(שדות pattern/replace נפרדים), הדבקת JSON של Service Account שמפצל אוטומטית לכל שדותfcm_json_*, ומקטע "הגדרות מתקדמות" מתקפל בתחתית שמאפשר עדיין הוספת key/value חופשי (גם מציג הגדרות לא מוכרות שכבר קיימות).[{key, value}]בעת שמירה (בוליאנים נשמרים כ-'1', regex כ-pattern#replace).captain-definitionלהגדרת פריסה ב-CapRover.Files changed
frontend/src/app/components/admin/settings/settings.schema.ts(חדש) — schema עם קטגוריות, סוגי שדות, ותיאורים בעברית.frontend/src/app/components/admin/settings/settings.component.ts— נכתב מחדש לטעון/לשמור לפי ה-schema.frontend/src/app/components/admin/settings/settings.component.html— UI חדש לחלוטין.frontend/src/app/components/admin/settings/settings.component.scss— סגנונות חדשים.captain-definition— קובץ פריסה.Test plan
1/לא קיים).regex-replaceולוודא שהם נשמרים בפורמטpattern#replaceופועלים בפרסום הודעות.fcm_json_*מתמלאים.🤖 Generated with Claude Code