Browse Source

Fix toggle switch styling and table flickering

- Redesign toggle-switch-large CSS: proper sizing (72x40px), centered
  knob (32x32px), smooth cubic-bezier animation, no border (use
  inset shadow instead)
- Fix table flickering: add silent parameter to loadDevices(), use
  silent=true for polling updates to avoid showing loading spinner

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
root 2 weeks ago
parent
commit
ad341a9c07
3 changed files with 383 additions and 20 deletions
  1. 229 0
      DEV_CONTAINER_README.md
  2. 27 20
      frontend/src/views/superadmin/DevicesView.vue
  3. 127 0
      scripts/deploy.sh

+ 229 - 0
DEV_CONTAINER_README.md

@@ -0,0 +1,229 @@
+# MyBeacon Development Container
+
+## LXC Container (Debian 13)
+
+**Access:**
+- SSH: `ssh -p 2223 user@ms.e-bash.ru`
+- Frontend: http://ms.e-bash.ru:2280
+- Backend API: http://ms.e-bash.ru:2280/api/v1/
+- API Docs: http://ms.e-bash.ru:2280/docs
+
+## Stack
+
+- **Backend**: Python 3.13, FastAPI, Poetry
+- **Frontend**: Vue 3, Vite
+- **Databases**: PostgreSQL 17, ClickHouse, Redis
+- **Web**: Nginx (reverse proxy)
+
+## Quick Start
+
+### Start/Stop Services
+
+```bash
+# Backend control
+./backend.sh start      # Start
+./backend.sh stop       # Stop
+./backend.sh restart    # Restart
+./backend.sh logs       # View logs
+./backend.sh status     # Check status
+
+# Frontend is served as static files via Nginx
+# To rebuild: cd frontend && npm run build
+```
+
+### Check Services Status
+
+```bash
+sudo systemctl status postgresql
+sudo systemctl status clickhouse-server
+sudo systemctl status redis
+sudo systemctl status nginx
+```
+
+## Credentials
+
+### Superadmin
+- Email: `superadmin@mybeacon.com`
+- Password: `superadmin123`
+
+### Databases
+
+**PostgreSQL:**
+```bash
+sudo -u postgres psql -d mybeacon
+```
+
+**ClickHouse:**
+```bash
+clickhouse-client --database mybeacon
+```
+
+**Redis:**
+```bash
+redis-cli
+```
+
+## Project Structure
+
+```
+~/mybeacon-backend/
+├── backend/              # Python FastAPI backend
+│   ├── app/             # Source code
+│   │   ├── api/         # API endpoints
+│   │   ├── models/      # SQLAlchemy models
+│   │   ├── core/        # Core settings, security, database
+│   │   └── main.py      # App entry point
+│   ├── alembic/         # Database migrations
+│   ├── pyproject.toml   # Python dependencies
+│   └── .env             # Config (SECRET_KEY, DATABASE_URL)
+├── frontend/            # Vue 3 frontend
+│   ├── src/             # Source code
+│   │   ├── api/         # API clients
+│   │   ├── views/       # Page components
+│   │   ├── router/      # Vue Router
+│   │   └── i18n/        # Translations (RU/EN)
+│   ├── dist/            # Production build (served by nginx)
+│   └── package.json     # Node dependencies
+├── backend.sh           # Backend control script
+└── scripts/             # Deployment scripts
+    └── deploy.sh        # Deploy from local machine
+```
+
+## Development Workflow
+
+### Local Development
+
+1. **Clone the repo:**
+   ```bash
+   git clone https://h2.e-bash.ru/root/mybeacon-backend.git
+   cd mybeacon-backend
+   ```
+
+2. **Work on your changes locally**
+
+3. **Deploy to dev container:**
+   ```bash
+   ./scripts/deploy.sh
+   # or
+   ./scripts/deploy.sh --backend-only   # Only restart backend
+   ./scripts/deploy.sh --skip-build     # Skip frontend build
+   ```
+
+### Backend Development
+
+```bash
+cd backend
+
+# Install dependencies
+poetry install
+
+# Run migrations
+poetry run alembic upgrade head
+poetry run alembic revision --autogenerate -m "description"
+
+# Run dev server (with auto-reload)
+poetry run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
+
+# Format code
+poetry run black app/
+poetry run ruff app/
+```
+
+### Frontend Development
+
+```bash
+cd frontend
+
+# Install dependencies
+npm install
+
+# Run dev server (with HMR)
+npm run dev -- --host 0.0.0.0
+
+# Build for production
+npm run build
+
+# Preview production build
+npm run preview
+```
+
+## API Endpoints
+
+### Authentication
+- `POST /api/v1/auth/login` - Login (get JWT tokens)
+- `POST /api/v1/auth/refresh` - Refresh access token
+- `GET /api/v1/auth/me` - Current user info
+
+### Superadmin
+- `GET/POST /api/v1/superadmin/devices` - Manage all devices
+- `GET/POST /api/v1/superadmin/organizations` - Manage organizations
+- `GET/POST /api/v1/superadmin/users` - Manage users
+- `GET/POST /api/v1/superadmin/settings/auto-registration` - Toggle device registration
+
+### Device API (for hardware)
+- `POST /api/v1/registration` - Register new device
+- `GET /api/v1/config?device_id=MAC` - Get device config
+- `POST /api/v1/ble` - Upload BLE events
+- `POST /api/v1/wifi` - Upload WiFi events
+
+## Troubleshooting
+
+### Backend won't start
+```bash
+./backend.sh logs              # Check logs
+sudo -u postgres psql -d mybeacon  # Check DB connection
+pkill -f uvicorn               # Kill zombie processes
+```
+
+### Frontend changes not visible
+```bash
+cd frontend
+npm run build                  # Rebuild frontend
+# Nginx serves static files from dist/
+```
+
+### Check ports
+```bash
+sudo netstat -tlnp | grep -E '8000|5432|8123|6379|80'
+```
+
+### View logs
+```bash
+./backend.sh logs              # Backend logs
+sudo tail -f /var/log/nginx/error.log  # Nginx logs
+```
+
+## Nginx Configuration
+
+Nginx is configured as reverse proxy:
+- `/` → Frontend static files from `frontend/dist/`
+- `/api/` → Backend at `localhost:8000`
+- `/docs` → Swagger UI
+- `/redoc` → ReDoc
+
+Config: `/etc/nginx/sites-enabled/mybeacon`
+
+## Git Workflow
+
+```bash
+# On local machine
+git add .
+git commit -m "Your message"
+git push origin master
+
+# Deploy
+./scripts/deploy.sh
+
+# Or manually on server
+ssh -p 2223 user@ms.e-bash.ru
+cd ~/mybeacon-backend
+git pull
+./backend.sh restart
+```
+
+## TODO
+
+- [ ] Add HTTPS (certbot)
+- [ ] Configure systemd for auto-start
+- [ ] Add logrotate for logs
+- [ ] ClickHouse integration for events

+ 27 - 20
frontend/src/views/superadmin/DevicesView.vue

@@ -604,8 +604,11 @@ function sortBy(column) {
   }
 }
 
-async function loadDevices() {
-  loading.value = true
+async function loadDevices(silent = false) {
+  // Only show loading spinner on initial load, not on polling updates
+  if (!silent) {
+    loading.value = true
+  }
   error.value = null
   try {
     const params = {}
@@ -1078,10 +1081,10 @@ onMounted(() => {
   loadAutoRegistrationStatus()
   startRegistrationTimer()
 
-  // Real-time polling every 10 seconds
+  // Real-time polling every 10 seconds (silent to avoid table flickering)
   pollingInterval = setInterval(() => {
     if (!modalVisible.value) {
-      loadDevices()
+      loadDevices(true)
     }
   }, 10000)
 })
@@ -1145,13 +1148,15 @@ onBeforeUnmount(() => {
 .toggle-switch-large {
   position: relative;
   display: inline-block;
-  width: 80px;
-  height: 44px;
+  width: 72px;
+  height: 40px;
+  flex-shrink: 0;
 }
 .toggle-switch-large input {
   opacity: 0;
   width: 0;
   height: 0;
+  position: absolute;
 }
 .toggle-switch-large .toggle-slider {
   position: absolute;
@@ -1161,32 +1166,33 @@ onBeforeUnmount(() => {
   right: 0;
   bottom: 0;
   background-color: #cbd5e0;
-  transition: 0.4s;
-  border-radius: 44px;
-  border: 2px solid #a0aec0;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  border-radius: 40px;
+  box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
 }
 .toggle-switch-large .toggle-slider:before {
   position: absolute;
   content: '';
-  height: 36px;
-  width: 36px;
-  left: 2px;
-  top: 50%;
-  transform: translateY(-50%);
+  height: 32px;
+  width: 32px;
+  left: 4px;
+  top: 4px;
   background-color: white;
-  transition: 0.4s;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
   border-radius: 50%;
-  box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
 }
 .toggle-switch-large input:checked + .toggle-slider {
   background-color: #48bb78;
-  border-color: #38a169;
 }
 .toggle-switch-large input:checked + .toggle-slider:before {
-  transform: translateY(-50%) translateX(36px);
+  transform: translateX(32px);
+}
+.toggle-switch-large input:focus + .toggle-slider {
+  box-shadow: inset 0 2px 4px rgba(0,0,0,0.1), 0 0 0 3px rgba(72, 187, 120, 0.3);
 }
 .toggle-switch-large input:disabled + .toggle-slider {
-  opacity: 0.6;
+  opacity: 0.5;
   cursor: not-allowed;
 }
 .toggle-switch-large .spinner {
@@ -1194,8 +1200,9 @@ onBeforeUnmount(() => {
   left: 50%;
   top: 50%;
   transform: translate(-50%, -50%);
-  font-size: 20px;
+  font-size: 18px;
   animation: spin 1s linear infinite;
+  z-index: 10;
 }
 
 .content { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); }

+ 127 - 0
scripts/deploy.sh

@@ -0,0 +1,127 @@
+#!/bin/bash
+#
+# MyBeacon - Deploy to Development Container
+#
+# Usage: ./scripts/deploy.sh [options]
+#
+# Options:
+#   --skip-build    Skip frontend build (only restart backend)
+#   --backend-only  Only restart backend, don't touch frontend
+#   --help          Show this help
+#
+
+set -e
+
+# Colors
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m'
+
+# Configuration
+REMOTE_HOST="ms.e-bash.ru"
+REMOTE_PORT="2223"
+REMOTE_USER="user"
+REMOTE_PATH="~/mybeacon-backend"
+
+# Get script directory (project root)
+SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$SCRIPT_DIR"
+
+# Parse arguments
+SKIP_BUILD=false
+BACKEND_ONLY=false
+
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --skip-build)
+            SKIP_BUILD=true
+            shift
+            ;;
+        --backend-only)
+            BACKEND_ONLY=true
+            shift
+            ;;
+        --help)
+            echo "Usage: $0 [options]"
+            echo ""
+            echo "Options:"
+            echo "  --skip-build    Skip frontend build (only restart backend)"
+            echo "  --backend-only  Only restart backend, don't touch frontend"
+            echo "  --help          Show this help"
+            echo ""
+            echo "Server: ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PORT}"
+            exit 0
+            ;;
+        *)
+            echo -e "${RED}Unknown option: $1${NC}"
+            exit 1
+            ;;
+    esac
+done
+
+echo -e "${BLUE}╔════════════════════════════════════╗${NC}"
+echo -e "${BLUE}║   MyBeacon - Deploy to Dev Server  ║${NC}"
+echo -e "${BLUE}╚════════════════════════════════════╝${NC}"
+echo ""
+
+# Function for SSH commands
+ssh_cmd() {
+    ssh -p "$REMOTE_PORT" "${REMOTE_USER}@${REMOTE_HOST}" "$@"
+}
+
+# Step 1: Check for uncommitted changes
+echo -e "${BLUE}[1/5]${NC} Checking local git status..."
+if ! git diff --quiet HEAD; then
+    echo -e "${YELLOW}Warning: You have uncommitted changes!${NC}"
+    git status --short
+    echo ""
+    read -p "Continue anyway? (y/N) " -n 1 -r
+    echo ""
+    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+        echo -e "${RED}Aborted.${NC}"
+        exit 1
+    fi
+fi
+echo -e "${GREEN}✓ Git status OK${NC}"
+echo ""
+
+# Step 2: Push local changes
+echo -e "${BLUE}[2/5]${NC} Pushing to remote repository..."
+git push origin master || {
+    echo -e "${YELLOW}Warning: Nothing to push (already up to date)${NC}"
+}
+echo -e "${GREEN}✓ Push complete${NC}"
+echo ""
+
+# Step 3: Pull on server
+echo -e "${BLUE}[3/5]${NC} Pulling changes on server..."
+ssh_cmd "cd ${REMOTE_PATH} && git pull"
+echo -e "${GREEN}✓ Pull complete${NC}"
+echo ""
+
+# Step 4: Build frontend (if not skipped)
+if [ "$BACKEND_ONLY" = false ] && [ "$SKIP_BUILD" = false ]; then
+    echo -e "${BLUE}[4/5]${NC} Building frontend..."
+    ssh_cmd "cd ${REMOTE_PATH}/frontend && npm run build"
+    echo -e "${GREEN}✓ Frontend build complete${NC}"
+else
+    echo -e "${BLUE}[4/5]${NC} ${YELLOW}Skipping frontend build${NC}"
+fi
+echo ""
+
+# Step 5: Restart backend
+echo -e "${BLUE}[5/5]${NC} Restarting backend..."
+ssh_cmd "cd ${REMOTE_PATH} && bash backend.sh restart"
+echo -e "${GREEN}✓ Backend restarted${NC}"
+echo ""
+
+echo -e "${GREEN}════════════════════════════════════${NC}"
+echo -e "${GREEN}Deploy complete!${NC}"
+echo ""
+echo -e "Frontend: ${YELLOW}http://${REMOTE_HOST}:2280${NC}"
+echo -e "Backend:  ${YELLOW}http://${REMOTE_HOST}:2280/api${NC}"
+echo -e "API Docs: ${YELLOW}http://${REMOTE_HOST}:2280/docs${NC}"
+echo ""
+echo -e "View logs: ${YELLOW}ssh -p${REMOTE_PORT} ${REMOTE_USER}@${REMOTE_HOST} 'tail -f /tmp/backend.log'${NC}"