RBAC: Phase 1-3, Total Salary fix, employee creation fix, permission groups, backup script
Made-with: Cursor
This commit is contained in:
29
scripts/backup-staging.sh
Executable file
29
scripts/backup-staging.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# Backup staging database to /root/z_crm/backups/ on the server.
|
||||
# Usage: SSHPASS=yourpassword ./scripts/backup-staging.sh
|
||||
# Or with SSH keys: ./scripts/backup-staging.sh
|
||||
#
|
||||
# Creates backups/backup_YYYYMMDD_HHMMSS.sql on the staging server.
|
||||
|
||||
set -e
|
||||
|
||||
STAGING_HOST="${STAGING_HOST:-root@37.60.249.71}"
|
||||
BACKUP_DIR="/root/z_crm/backups"
|
||||
BACKUP_FILE="backup_$(date +%Y%m%d_%H%M%S).sql"
|
||||
|
||||
echo "Backing up staging database..."
|
||||
echo "Host: $STAGING_HOST"
|
||||
echo "Target: $BACKUP_DIR/$BACKUP_FILE"
|
||||
echo ""
|
||||
|
||||
CMD="mkdir -p $BACKUP_DIR && cd /root/z_crm && docker compose exec -T postgres pg_dump -U postgres mind14_crm > $BACKUP_DIR/$BACKUP_FILE"
|
||||
|
||||
if [ -n "$SSHPASS" ]; then
|
||||
sshpass -e ssh -o StrictHostKeyChecking=no "$STAGING_HOST" "$CMD"
|
||||
else
|
||||
ssh -o StrictHostKeyChecking=no "$STAGING_HOST" "$CMD"
|
||||
fi
|
||||
|
||||
echo "Backup complete."
|
||||
echo "File on server: $BACKUP_DIR/$BACKUP_FILE"
|
||||
echo "To restore: docker compose exec -T postgres psql -U postgres mind14_crm < $BACKUP_DIR/$BACKUP_FILE"
|
||||
76
scripts/capture-page.mjs
Normal file
76
scripts/capture-page.mjs
Normal file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Capture page screenshot using Playwright.
|
||||
* Logs in and captures the dashboard (or specified path).
|
||||
*
|
||||
* Usage: node scripts/capture-page.mjs [path]
|
||||
* path: optional, e.g. /dashboard, /admin, /contacts (default: /dashboard)
|
||||
*
|
||||
* Requires: npm install -D @playwright/test && npx playwright install chromium
|
||||
*/
|
||||
|
||||
import { chromium } from 'playwright';
|
||||
import { writeFileSync, mkdirSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const BASE_URL = process.env.CAPTURE_BASE_URL || 'https://zerp.atmata-group.com';
|
||||
const LOGIN_EMAIL = process.env.CAPTURE_EMAIL || 'admin@system.local';
|
||||
const LOGIN_PASSWORD = process.env.CAPTURE_PASSWORD || 'Admin@123';
|
||||
const OUTPUT_DIR = join(__dirname, '..', 'assets');
|
||||
const DEFAULT_PATH = process.argv[2] || '/dashboard';
|
||||
|
||||
async function capture() {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1920, height: 1080 },
|
||||
ignoreHTTPSErrors: true,
|
||||
});
|
||||
|
||||
try {
|
||||
const page = await context.newPage();
|
||||
|
||||
// Navigate to login
|
||||
console.log(`Navigating to ${BASE_URL}/login...`);
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: 'networkidle', timeout: 30000 });
|
||||
|
||||
// Login
|
||||
console.log('Logging in...');
|
||||
await page.fill('input[type="email"], input[name="email"]', LOGIN_EMAIL);
|
||||
await page.fill('input[type="password"], input[name="password"]', LOGIN_PASSWORD);
|
||||
await page.click('button[type="submit"], button:has-text("تسجيل الدخول"), button:has-text("Login")');
|
||||
|
||||
// Wait for redirect to dashboard
|
||||
await page.waitForURL(/\/(dashboard|admin|$)/, { timeout: 15000 }).catch(() => {});
|
||||
|
||||
// Navigate to target path if not already there
|
||||
const targetUrl = `${BASE_URL}${DEFAULT_PATH.startsWith('/') ? '' : '/'}${DEFAULT_PATH}`;
|
||||
if (!page.url().includes(DEFAULT_PATH)) {
|
||||
console.log(`Navigating to ${targetUrl}...`);
|
||||
await page.goto(targetUrl, { waitUntil: 'networkidle', timeout: 15000 });
|
||||
}
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(2000); // Let any async content render
|
||||
|
||||
mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
const filename = `capture-${DEFAULT_PATH.replace(/\//g, '-') || 'dashboard'}-${Date.now()}.png`;
|
||||
const filepath = join(OUTPUT_DIR, filename);
|
||||
|
||||
await page.screenshot({ path: filepath, fullPage: true });
|
||||
console.log(`Screenshot saved: ${filepath}`);
|
||||
|
||||
// Also save a fixed name for easy reference
|
||||
const latestPath = join(OUTPUT_DIR, 'capture-latest.png');
|
||||
await page.screenshot({ path: latestPath, fullPage: true });
|
||||
console.log(`Latest screenshot: ${latestPath}`);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
capture().catch((err) => {
|
||||
console.error('Capture failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user