RBAC: Phase 1-3, Total Salary fix, employee creation fix, permission groups, backup script

Made-with: Cursor
This commit is contained in:
Talal Sharabi
2026-03-04 19:31:08 +04:00
parent 6034f774ed
commit 8edeaf10f5
46 changed files with 2751 additions and 598 deletions

29
scripts/backup-staging.sh Executable file
View 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
View 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);
});