diff --git a/public/service-worker.js b/public/service-worker.js index 62336ee..93b0259 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,8 +1,8 @@ const isLocalhost = self.location.hostname === "localhost" || self.location.hostname === "127.0.0.1"; if (!isLocalhost) { - const STATIC_CACHE = "construction-delivery-static-v57"; - const RUNTIME_CACHE = "construction-delivery-runtime-v57"; + const STATIC_CACHE = "construction-delivery-static-v58"; + const RUNTIME_CACHE = "construction-delivery-runtime-v58"; const APP_SHELL_URLS = ["/", "/index.html", "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"]; self.addEventListener("install", (event) => { diff --git a/scripts/backup-pgdump-s3.sh b/scripts/backup-pgdump-s3.sh new file mode 100755 index 0000000..44ccaff --- /dev/null +++ b/scripts/backup-pgdump-s3.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Supersam full DB backup (pg_dump) to S3 (Beget Cloud Storage) +# Dumps the complete supabase postgres database and uploads to S3. +# Retention: keeps last 30 days. +# +# Cron: 0 */12 * * * /opt/supersam/scripts/backup-pgdump-s3.sh >> /var/log/supersam-backup-pgdump.log 2>&1 + +set -euo pipefail + +# ── Config ──────────────────────────────────────────────────────────────── +AWS_ACCESS_KEY_ID="YG4MQNKAPNL65200MBUY" +AWS_SECRET_ACCESS_KEY="8mXkFM2VRQ3pN1Nx4mhmJ2jrZoB5YTPUa4CaZh43" +S3_ENDPOINT="https://s3.ru1.storage.beget.cloud" +S3_BUCKET="02f162ff4a18-supersam-s3" +S3_PREFIX="backups" + +DB_CONTAINER="supabase-db" +DB_USER="supabase_admin" +DB_NAME="postgres" +DB_PASS="4fe80bb21c7c3d17a8d8b226adf7a479" + +RETENTION_DAYS=30 + +# ── Runtime ─────────────────────────────────────────────────────────────── +TIMESTAMP=$(date +%Y-%m-%d_%H%M) +DUMP_FILE="/tmp/supersam-dump-${TIMESTAMP}.sql.gz" +S3_PATH="s3://${S3_BUCKET}/${S3_PREFIX}/pgdump/supersam-db-${TIMESTAMP}.sql.gz" + +export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY + +echo "[$(date)] Starting pg_dump backup..." + +# ── Dump ────────────────────────────────────────────────────────────────── +echo "[$(date)] Dumping database..." +docker exec -e PGPASSWORD="${DB_PASS}" "${DB_CONTAINER}" \ + pg_dump -U "${DB_USER}" -d "${DB_NAME}" --no-owner --no-acl --clean --if-exists \ + | gzip -9 > "${DUMP_FILE}" + +DUMP_SIZE=$(du -h "${DUMP_FILE}" | cut -f1) +echo "[$(date)] Dump created: ${DUMP_FILE} (${DUMP_SIZE})" + +# ── Upload via mc ───────────────────────────────────────────────────────── +echo "[$(date)] Uploading to S3..." +mc cp "${DUMP_FILE}" "beget/${S3_BUCKET}/${S3_PREFIX}/pgdump/supersam-db-${TIMESTAMP}.sql.gz" --quiet 2>&1 + +echo "[$(date)] Uploaded to ${S3_PATH}" + +# ── Cleanup local ───────────────────────────────────────────────────────── +rm -f "${DUMP_FILE}" +echo "[$(date)] Local cleanup done" + +# ── Retention: delete S3 pgdump backups older than RETENTION_DAYS ───────── +echo "[$(date)] Cleaning S3 pgdump backups older than ${RETENTION_DAYS} days..." +CUTOFF=$(date -d "-${RETENTION_DAYS} days" +%Y-%m-%d) +mc ls "beget/${S3_BUCKET}/${S3_PREFIX}/pgdump/" 2>/dev/null \ + | awk '{print $NF}' \ + | while read -r fname; do + file_date=$(echo "$fname" | grep -oP '\d{4}-\d{2}-\d{2}' || echo "") + if [ -n "$file_date" ] && [ "$file_date" \< "$CUTOFF" ]; then + mc rm "beget/${S3_BUCKET}/${S3_PREFIX}/pgdump/${fname}" --quiet 2>/dev/null + echo "[$(date)] Deleted old backup: ${fname}" + fi + done + +echo "[$(date)] Backup complete." \ No newline at end of file diff --git a/scripts/sms_run_with_db.sh b/scripts/sms_run_with_db.sh new file mode 100755 index 0000000..19ec9ce --- /dev/null +++ b/scripts/sms_run_with_db.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# Resolve supabase-db container IP dynamically +DB_IP=$(docker inspect supabase-db --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 2>/dev/null) +if [ -z "$DB_IP" ]; then + echo "ERROR: Cannot resolve supabase-db IP" >&2 + exit 1 +fi +export DB_HOST=$DB_IP +export DB_PORT=5432 +export DB_NAME=postgres +export DB_USER=supabase_admin +export DB_PASS=4fe80bb21c7c3d17a8d8b226adf7a479 +exec python3 "$@" diff --git a/src/AppShell.jsx b/src/AppShell.jsx new file mode 100644 index 0000000..53a162d --- /dev/null +++ b/src/AppShell.jsx @@ -0,0 +1,207 @@ +import React from "react"; +import { useNavigate } from "react-router-dom"; +import { ROLE_LABELS } from "../constants/roles"; +import { Badge } from "../components/UI/Badge"; +import { Button } from "../components/UI/Button"; +import { Panel } from "../components/UI/Panel"; +import { ThemeToggle } from "../components/UI/ThemeToggle"; +import { PwaInstallButton } from "../components/UI/PwaInstallButton"; +import { NotificationBell } from "../components/notifications/NotificationBell"; +import { NotificationSettings } from "../components/notifications/NotificationSettings"; + +export const AppShell = ({ + user, + onInstallApp, + isInstalled, + isInstallAvailable, + onSignOut, + onOpenGuide, + isGuideOpen = false, + navItems, + activeSection, + onSectionChange, + sectionMeta, + notifications = [], + unreadCount = 0, + onMarkNotificationRead, + onMarkAllNotificationsRead, + children, +}) => { + const shouldShowMobileNav = !isGuideOpen && navItems.length > 1; + const [showNotifSettings, setShowNotifSettings] = React.useState(false); + + if (showNotifSettings) { + return ( +
+ Панель +
++ Рабочая область +
++ {user.name} · {ROLE_LABELS[user.role] || user.role} +
++ Рабочая область +
++ {sectionMeta.description} +
+ ) : null} ++ Перетаскивайте секции за ручку слева, чтобы изменить порядок отображения. +
++ Поиск по группе, клиенту, телефону и дате доставки. +
+{category.description}
+Настройки
+Предпросмотр
+| Дата | +Статус | +
|---|---|
| 02.07.2026 | +В работе | +
+ Текст в карточке — описание доставки или заказа. +
++ Основной текст интерфейса. +
++ мелкая подпись · временная отметка +
++ Настройки сохраняются на этом устройстве +
++ Перетаскивайте секции за ручку слева, чтобы изменить порядок отображения. +
Панель @@ -81,6 +83,9 @@ export const AppShell = ({ {isGuideOpen ? "К рабочей области" : "Справка"} ) : null} + @@ -88,9 +93,9 @@ export const AppShell = ({ {/* Main content area */} -
@@ -117,6 +122,9 @@ export const AppShell = ({
) : null}