diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..749607b --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,221 @@ +pipeline { + agent any + + environment { + SSH_CREDENTIALS = 'cloudtopiaa' + REMOTE_SERVER = 'ubuntu@160.187.166.39' + REMOTE_WORKSPACE = '/home/ubuntu' + PROJECT_NAME = 'codenuk-frontend-live' + DEPLOY_PATH = '/var/www/html/codenuk-frontend-live' + GIT_CREDENTIALS = 'git-cred' + REPO_URL = 'https://git.tech4biz.wiki/Tech4Biz-Services/codenuk-frontend-live.git' + NPM_PATH = '/home/ubuntu/.nvm/versions/node/v22.18.0/bin/npm' + NODE_PATH = '/home/ubuntu/.nvm/versions/node/v22.18.0/bin/node' + EMAIL_RECIPIENT = 'jassim.mohammed@tech4biz.io, chandini.pachigunta@tech4biz.org' + } + + options { + timeout(time: 30, unit: 'MINUTES') + retry(2) + timestamps() + buildDiscarder(logRotator(numToKeepStr: '10')) + } + + stages { + stage('Preparation') { + steps { + script { + echo "Starting ${PROJECT_NAME} deployment pipeline" + echo "Server: ${REMOTE_SERVER}" + echo "Deploy Path: ${DEPLOY_PATH}" + } + } + } + + stage('Git Operations on Remote Server') { + steps { + script { + sshagent(credentials: [SSH_CREDENTIALS]) { + withCredentials([usernamePassword(credentialsId: GIT_CREDENTIALS, usernameVariable: 'GIT_USER', passwordVariable: 'GIT_PASS')]) { + sh """ + ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} ' + set -e + + echo "Checking Git repo..." + if [ -d "${DEPLOY_PATH}/.git" ]; then + echo "Pulling latest code..." + cd ${DEPLOY_PATH} + + # Fix ownership issues + sudo chown -R ubuntu:ubuntu ${DEPLOY_PATH} + git config --global --add safe.directory ${DEPLOY_PATH} + + git reset --hard + git clean -fd + git config pull.rebase false + git pull https://${GIT_USER}:${GIT_PASS}@git.tech4biz.wiki/Tech4Biz-Services/codenuk-frontend-live.git main + else + echo "Cloning fresh repo..." + sudo rm -rf ${DEPLOY_PATH} + sudo mkdir -p /var/www/html + sudo git clone https://${GIT_USER}:${GIT_PASS}@git.tech4biz.wiki/Tech4Biz-Services/codenuk-frontend-live.git ${DEPLOY_PATH} + sudo chown -R ubuntu:ubuntu ${DEPLOY_PATH} + git config --global --add safe.directory ${DEPLOY_PATH} + fi + + cd ${DEPLOY_PATH} + echo "Commit: \$(git rev-parse HEAD)" + ' + """ + } + } + } + } + } + + stage('Verify Node.js Environment') { + steps { + script { + sshagent(credentials: [SSH_CREDENTIALS]) { + sh """ + ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} ' + set -e + export PATH="/home/ubuntu/.nvm/versions/node/v22.18.0/bin:\$PATH" + cd ${DEPLOY_PATH} + + echo "Node: \$(${NODE_PATH} -v)" + echo "NPM: \$(${NPM_PATH} -v)" + ${NPM_PATH} cache clean --force + ' + """ + } + } + } + } + + stage('Install Dependencies') { + steps { + sshagent(credentials: [SSH_CREDENTIALS]) { + sh """ + ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} ' + set -e + export PATH="/home/ubuntu/.nvm/versions/node/v22.18.0/bin:\$PATH" + cd ${DEPLOY_PATH} + echo "Installing dependencies..." + rm -rf node_modules package-lock.json + ${NPM_PATH} install --force + ' + """ + } + } + } + + stage('Build Application') { + steps { + sshagent(credentials: [SSH_CREDENTIALS]) { + sh """ + ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} ' + set -e + export PATH="/home/ubuntu/.nvm/versions/node/v22.18.0/bin:\$PATH" + cd ${DEPLOY_PATH} + echo "Building..." + ${NPM_PATH} run build + echo "Build directory contents:" + ls -la dist || ls -la build || echo "No build/dist directory found" + ' + """ + } + } + } + + stage('Deploy Static Files') { + steps { + sshagent(credentials: [SSH_CREDENTIALS]) { + sh """ + ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} ' + set -e + cd ${DEPLOY_PATH} + echo "Setting up static files for nginx..." + + # Ensure proper ownership and permissions + sudo chown -R www-data:www-data ${DEPLOY_PATH} + sudo find ${DEPLOY_PATH} -type d -exec chmod 755 {} \\; + sudo find ${DEPLOY_PATH} -type f -exec chmod 644 {} \\; + + echo "Static files prepared for nginx serving" + ' + """ + } + } + } + + stage('Restart Nginx') { + steps { + sshagent(credentials: [SSH_CREDENTIALS]) { + sh """ + ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} ' + set -e + echo "Testing nginx configuration..." + sudo nginx -t + + echo "Restarting nginx..." + sudo systemctl restart nginx + + echo "Checking nginx status..." + sudo systemctl status nginx --no-pager -l + ' + """ + } + } + } + + stage('Health Check') { + steps { + sshagent(credentials: [SSH_CREDENTIALS]) { + sh """ + ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} ' + set -e + echo "Verifying deployment..." + echo "Directory structure:" + ls -la ${DEPLOY_PATH} + + echo "Build files:" + ls -la ${DEPLOY_PATH}/dist || ls -la ${DEPLOY_PATH}/build || echo "No build directory found" + + echo "Nginx status:" + sudo systemctl is-active nginx + ' + """ + } + } + } + } + + post { + always { + cleanWs() + } + success { + mail to: "${EMAIL_RECIPIENT}", + subject: "✅ Jenkins - ${PROJECT_NAME} Deployment Successful", + body: """The deployment of '${PROJECT_NAME}' to ${REMOTE_SERVER} was successful. + +Build Number: ${BUILD_NUMBER} +URL: ${BUILD_URL} +Time: ${new Date()} + +The static files have been deployed and nginx has been restarted. +""" + } + failure { + mail to: "${EMAIL_RECIPIENT}", + subject: "❌ Jenkins - ${PROJECT_NAME} Deployment Failed", + body: """Deployment failed. Please review logs at: + +${BUILD_URL}console + +Time: ${new Date()} +""" + } + } +} \ No newline at end of file diff --git a/src/components/admin/admin-feature-selection.tsx b/src/components/admin/admin-feature-selection.tsx index b6d5548..f070a0a 100644 --- a/src/components/admin/admin-feature-selection.tsx +++ b/src/components/admin/admin-feature-selection.tsx @@ -137,7 +137,7 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio } } - const handleAddAIAnalyzed = async (payload: { name: string; description: string; complexity: 'low' | 'medium' | 'high' }) => { + const handleAddAIAnalyzed = async (payload: { name: string; description: string; complexity: 'low' | 'medium' | 'high'; logic_rules?: string[] }) => { // Check if template ID is valid UUID format const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i if (!uuidRegex.test(template.id)) { @@ -153,6 +153,7 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio complexity: payload.complexity, is_default: true, created_by_user: true, + logic_rules: payload.logic_rules, }) await load() } catch (error) { diff --git a/src/components/admin/admin-layout.tsx b/src/components/admin/admin-layout.tsx new file mode 100644 index 0000000..3beb5c5 --- /dev/null +++ b/src/components/admin/admin-layout.tsx @@ -0,0 +1,176 @@ +"use client" + +import { useState } from 'react' +import { AdminSidebar } from './admin-sidebar' +import { AdminTemplatesManager } from './admin-templates-manager' +import { AdminDashboard } from './admin-dashboard' +import { TemplateFeaturesManager } from './template-features-manager' +import { AdminNotificationsPanel } from './admin-notifications-panel' + +type AdminView = + | 'dashboard' + | 'templates' + | 'features' + | 'ai-features' + | 'users' + | 'notifications' + | 'analytics' + | 'settings' + | 'create-template' + | 'add-feature' + | 'ai-analyze' + +export function AdminLayout() { + const [currentView, setCurrentView] = useState('dashboard') + const [featuresDialogOpen, setFeaturesDialogOpen] = useState(false) + + const renderMainContent = () => { + switch (currentView) { + case 'dashboard': + return + + case 'templates': + case 'create-template': + return + + case 'features': + case 'add-feature': + const defaultTemplate = { + id: "default", + type: "default", + title: "Default Template", + description: "Default template for feature management", + complexity: "medium" as const, + approved: true, + usage_count: 0, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + is_custom: false, + status: "approved" as const + } + return ( + { + if (!open) { + setCurrentView('dashboard') + } + }} + /> + ) + + case 'ai-features': + case 'ai-analyze': + return ( +
+
+

AI Feature Analysis

+

AI-powered feature creation and optimization

+
+
+
🤖
+

AI Feature Creator

+

Analyze project requirements and generate optimized features

+
+
+

Smart Analysis

+

AI analyzes your project type and suggests relevant features

+
+
+

Essential Features

+

Creates essential features that integrate with your template

+
+
+

Auto-Optimization

+

Optimizes complexity and requirements automatically

+
+
+
+
+ ) + + case 'users': + return ( +
+
+

User Management

+

Manage user accounts and permissions

+
+
+
👥
+

User Administration

+

User management features coming soon

+
+
+ ) + + case 'notifications': + return {}} /> + + case 'analytics': + return ( +
+
+

Analytics Dashboard

+

Usage statistics and performance metrics

+
+
+
+

Total Templates

+
24
+

+3 this week

+
+
+

Active Users

+
156
+

+12 this week

+
+
+

Features Created

+
89
+

+7 this week

+
+
+

AI Analyses

+
42
+

+8 this week

+
+
+
+ ) + + case 'settings': + return ( +
+
+

System Settings

+

Configure system preferences and options

+
+
+
⚙️
+

Configuration Panel

+

System settings panel coming soon

+
+
+ ) + + default: + return + } + } + + return ( +
+ setCurrentView(view as AdminView)} + /> +
+
+ {renderMainContent()} +
+
+
+ ) +} diff --git a/src/components/admin/admin-sidebar.tsx b/src/components/admin/admin-sidebar.tsx new file mode 100644 index 0000000..ab5c53d --- /dev/null +++ b/src/components/admin/admin-sidebar.tsx @@ -0,0 +1,246 @@ +"use client" + +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { + LayoutDashboard, + FileText, + Settings, + Users, + Bell, + Plus, + Search, + Filter, + ChevronLeft, + ChevronRight, + Home, + Database, + Layers, + Zap, + BarChart3, + Globe, + Code +} from 'lucide-react' +import { cn } from '@/lib/utils' + +interface AdminSidebarProps { + currentView: string + onViewChange: (view: string) => void + className?: string +} + +export function AdminSidebar({ currentView, onViewChange, className }: AdminSidebarProps) { + const [isCollapsed, setIsCollapsed] = useState(false) + + const navigationItems = [ + { + id: 'dashboard', + label: 'Dashboard', + icon: LayoutDashboard, + description: 'Overview & Analytics', + badge: null + }, + { + id: 'templates', + label: 'Templates', + icon: FileText, + description: 'Manage Templates', + badge: null + }, + { + id: 'features', + label: 'Features', + icon: Layers, + description: 'Feature Management', + badge: null + }, + { + id: 'ai-features', + label: 'AI Features', + icon: Zap, + description: 'AI-Powered Creation', + badge: 'NEW' + }, + { + id: 'users', + label: 'Users', + icon: Users, + description: 'User Management', + badge: null + }, + { + id: 'notifications', + label: 'Notifications', + icon: Bell, + description: 'System Alerts', + badge: '3' + }, + { + id: 'analytics', + label: 'Analytics', + icon: BarChart3, + description: 'Usage Statistics', + badge: null + }, + { + id: 'settings', + label: 'Settings', + icon: Settings, + description: 'System Configuration', + badge: null + } + ] + + const quickActions = [ + { + id: 'create-template', + label: 'New Template', + icon: Plus, + action: () => onViewChange('create-template') + }, + { + id: 'add-feature', + label: 'Add Feature', + icon: Layers, + action: () => onViewChange('add-feature') + }, + { + id: 'ai-analyze', + label: 'AI Analyze', + icon: Zap, + action: () => onViewChange('ai-analyze') + } + ] + + return ( +
+ {/* Header */} +
+
+ {!isCollapsed && ( +
+

Admin Panel

+

CodeNuk Management

+
+ )} + +
+
+ + {/* Navigation */} +
+
+ {!isCollapsed && ( +
+ Navigation +
+ )} + +
+ + {/* Quick Actions */} + {!isCollapsed && ( +
+
+ Quick Actions +
+
+ {quickActions.map((action) => { + const Icon = action.icon + return ( + + ) + })} +
+
+ )} +
+ + {/* Footer */} +
+ {!isCollapsed ? ( +
+
+ System Status +
+
+ Online +
+
+
+ Backend: Connected • DB: Healthy +
+
+ ) : ( +
+
+
+ )} +
+
+ ) +} diff --git a/src/components/admin/admin-templates-list.tsx b/src/components/admin/admin-templates-list.tsx index a13e6e7..40e12c8 100644 --- a/src/components/admin/admin-templates-list.tsx +++ b/src/components/admin/admin-templates-list.tsx @@ -178,20 +178,35 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps // Get category icon const getCategoryIcon = (category: string) => { switch (category.toLowerCase()) { - case 'marketing': - return Zap - case 'software': - return Code - case 'seo': - return BarChart3 + case 'food delivery': + return ShoppingCart + case 'e-commerce': case 'ecommerce': return ShoppingCart - case 'portfolio': + case 'saas platform': + return Code + case 'mobile app': + return Code + case 'dashboard': + return BarChart3 + case 'crm system': return Briefcase - case 'business': - return Briefcase - case 'education': + case 'learning platform': return GraduationCap + case 'healthcare': + return AlertCircle + case 'real estate': + return Globe + case 'travel': + return Globe + case 'entertainment': + return Zap + case 'finance': + return BarChart3 + case 'social media': + return Zap + case 'marketplace': + return ShoppingCart default: return Globe } @@ -199,37 +214,69 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps // Get category stats for filters const getCategoryStats = () => { + // Start with "All Templates" const categoryStats = [ - { id: 'all', name: 'All Templates', count: templates.length, icon: Globe }, - { id: 'marketing', name: 'Marketing', count: 0, icon: Zap }, - { id: 'software', name: 'Software', count: 0, icon: Code }, - { id: 'seo', name: 'SEO', count: 0, icon: BarChart3 }, - { id: 'ecommerce', name: 'E-commerce', count: 0, icon: ShoppingCart }, - { id: 'portfolio', name: 'Portfolio', count: 0, icon: Briefcase }, - { id: 'business', name: 'Business', count: 0, icon: Briefcase }, - { id: 'education', name: 'Education', count: 0, icon: GraduationCap } + { id: 'all', name: 'All Templates', count: templates.length, icon: Globe } ] + // Add categories based on your actual categories array + categories.forEach(category => { + if (category !== 'Other') { // Skip 'Other' as it will be handled separately + categoryStats.push({ + id: category.toLowerCase().replace(/\s+/g, '-'), + name: category, + count: 0, + icon: getCategoryIcon(category) + }) + } + }) + + // Add 'Other' category at the end + categoryStats.push({ + id: 'other', + name: 'Other', + count: 0, + icon: Globe + }) + // Count templates by category templates.forEach(template => { - const categoryId = template.category?.toLowerCase() - const categoryItem = categoryStats.find(cat => cat.id === categoryId) - if (categoryItem) { - categoryItem.count++ + const templateCategory = template.category + if (templateCategory) { + const categoryItem = categoryStats.find(cat => + cat.name.toLowerCase() === templateCategory.toLowerCase() || + cat.id === templateCategory.toLowerCase().replace(/\s+/g, '-') + ) + if (categoryItem) { + categoryItem.count++ + } else { + // If category doesn't match any predefined category, add to 'Other' + const otherCategory = categoryStats.find(cat => cat.id === 'other') + if (otherCategory) { + otherCategory.count++ + } + } } }) return categoryStats } - // Filter templates based on search + // Filter templates based on search and category const filteredTemplates = templates.filter(template => { const matchesSearch = !searchQuery || template.title?.toLowerCase().includes(searchQuery.toLowerCase()) || template.description?.toLowerCase().includes(searchQuery.toLowerCase()) || template.type?.toLowerCase().includes(searchQuery.toLowerCase()) - return matchesSearch + const matchesCategory = categoryFilter === 'all' || + template.category?.toLowerCase() === categoryFilter.toLowerCase() || + template.category?.toLowerCase().replace(/\s+/g, '-') === categoryFilter || + (categoryFilter === 'other' && !categories.some(cat => + cat.toLowerCase() === template.category?.toLowerCase() + )) + + return matchesSearch && matchesCategory }) const TemplateCard = ({ template }: { template: AdminTemplate }) => ( @@ -428,30 +475,7 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps - {/* Category Filters */} -
- {getCategoryStats().map((category) => { - const Icon = category.icon - const active = categoryFilter === category.id - return ( - - ) - })} -
+ {/* Category Filters removed; using only dropdown above */} {/* Create Template Modal */}