487 lines
122 KiB
HTML
487 lines
122 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fa" dir="rtl">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>دستیار انتشار</title>
|
||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||
<style>
|
||
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
|
||
:root{--primary:#1a365d;--primary-light:#2c5282;--primary-dark:#0f2341;--accent:#c9a961;--accent-dark:#a8893f;--accent-light:#e0c789;--bg:#f5f7fa;--board-bg:#e2e8f0;--card-bg:#fff;--panel-bg:#fff;--text:#1a202c;--text-muted:#4a5568;--text-light:#718096;--border:#cbd5e0;--border-light:#e2e8f0;--success:#38a169;--danger:#e53e3e;--warning:#dd6b20;--info:#3182ce;--shadow:0 4px 6px rgba(0,0,0,.08),0 10px 20px rgba(26,54,93,.1);--shadow-sm:0 2px 4px rgba(0,0,0,.06);--shadow-lg:0 10px 30px rgba(0,0,0,.15);--radius:10px;--radius-sm:6px}
|
||
[data-theme="dark"]{--primary:#2c5282;--primary-light:#3b6fb5;--primary-dark:#1a365d;--accent:#e0c789;--accent-dark:#c9a961;--accent-light:#f0d9a0;--bg:#1a202c;--board-bg:#2d3748;--card-bg:#2d3748;--panel-bg:#1f2937;--text:#e2e8f0;--text-muted:#cbd5e0;--text-light:#a0aec0;--border:#4a5568;--border-light:#374151;--shadow:0 4px 6px rgba(0,0,0,.3),0 10px 20px rgba(0,0,0,.4);--shadow-sm:0 2px 4px rgba(0,0,0,.2);--shadow-lg:0 10px 30px rgba(0,0,0,.5)}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{font-family:'Vazirmatn',Tahoma,sans-serif;background:var(--bg);color:var(--text);height:100vh;overflow:hidden;font-size:14px;display:flex;flex-direction:column;transition:background .3s,color .3s}
|
||
.app-header{background:linear-gradient(135deg,var(--primary) 0%,var(--primary-light) 100%);color:#fff;padding:0 20px;height:64px;display:flex;align-items:center;justify-content:space-between;border-bottom:3px solid var(--accent);box-shadow:var(--shadow);z-index:100;flex-shrink:0}
|
||
.app-brand{display:flex;align-items:center;gap:14px}
|
||
.app-logo{width:44px;height:44px;background:var(--accent);border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:800;color:var(--primary-dark)}
|
||
.app-brand-text{display:flex;flex-direction:column;gap:2px}
|
||
.app-title{font-size:16px;font-weight:800;color:#fff}
|
||
.app-subtitle{font-size:11px;opacity:.85;color:rgba(255,255,255,.9)}
|
||
.btn{background:linear-gradient(135deg,var(--primary-light),var(--primary));color:#fff;border:none;padding:8px 14px;border-radius:var(--radius-sm);cursor:pointer;font-family:inherit;font-size:13px;font-weight:600;display:inline-flex;align-items:center;gap:6px;transition:all .2s;white-space:nowrap}
|
||
.btn:hover{transform:translateY(-1px);filter:brightness(1.1)}
|
||
.btn-accent{background:linear-gradient(135deg,var(--accent),var(--accent-dark));color:var(--primary-dark);font-weight:700}
|
||
.btn-ghost{background:rgba(26,54,93,.08);border:1px solid var(--border);color:var(--primary)}
|
||
[data-theme="dark"] .btn-ghost{background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.25);color:#fff}
|
||
.btn-danger{background:linear-gradient(135deg,var(--danger),#c53030);color:#fff}
|
||
.btn-sm{padding:4px 10px;font-size:11px}
|
||
.btn-xs{padding:2px 6px;font-size:10px}
|
||
.app-main{flex:1;display:flex;overflow:hidden}
|
||
.sidebar{width:280px;background:var(--panel-bg);border-right:1px solid var(--border);display:flex;flex-direction:column;transition:width .3s;overflow:hidden;flex-shrink:0;z-index:50}
|
||
.sidebar.collapsed{width:0}
|
||
.sidebar-header{background:linear-gradient(135deg,var(--primary),var(--primary-light));color:#fff;padding:12px;display:flex;align-items:center;justify-content:space-between;border-bottom:2px solid var(--accent);min-height:48px}
|
||
.sidebar-title{font-size:13px;font-weight:700;display:flex;align-items:center;gap:8px}
|
||
.sidebar-body{flex:1;overflow-y:auto;padding:8px}
|
||
.sidebar-group{margin-bottom:6px;border:1px solid var(--border-light);border-radius:var(--radius-sm);overflow:hidden;background:var(--card-bg)}
|
||
.sidebar-group-header{background:linear-gradient(135deg,var(--primary),var(--primary-light));color:#fff;padding:10px 12px;cursor:pointer;display:flex;align-items:center;justify-content:space-between;font-size:13px;font-weight:700;user-select:none}
|
||
.sidebar-group-header .group-icon{display:flex;align-items:center;gap:8px;flex:1}
|
||
.sidebar-group-header .group-arrow{transition:transform .3s;font-size:12px}
|
||
.sidebar-group.collapsed .group-arrow{transform:rotate(90deg)}
|
||
.sidebar-group.collapsed .sidebar-group-body{display:none}
|
||
.sidebar-group-body{padding:6px;display:flex;flex-direction:column;gap:4px}
|
||
.sidebar-btn{background:var(--card-bg);border:1px solid var(--border-light);color:var(--text);padding:8px 10px;border-radius:var(--radius-sm);cursor:pointer;font-family:inherit;font-size:12px;font-weight:500;display:flex;align-items:center;gap:8px;text-align:right;width:100%}
|
||
.sidebar-btn:hover{background:linear-gradient(135deg,var(--primary),var(--primary-light));color:#fff}
|
||
.tabs-bar{background:linear-gradient(135deg,#0f1724 0%,#1a2a44 100%);border-bottom:2px solid var(--accent);display:flex;align-items:center;padding:0 12px;min-height:48px;gap:10px;flex-wrap:wrap}
|
||
.toolbar-controls{display:flex;gap:6px;margin-left:8px}
|
||
.toolbar-icon-btn{background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);color:#fff;width:34px;height:34px;border-radius:6px;cursor:pointer;font-size:15px;display:flex;align-items:center;justify-content:center}
|
||
.toolbar-icon-btn:hover:not(:disabled){background:var(--accent);color:var(--primary-dark)}
|
||
.toolbar-search{flex:1;min-width:180px;max-width:320px;position:relative}
|
||
.search-input{width:100%;padding:7px 32px 7px 12px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);border-radius:6px;color:#fff;font-family:inherit;font-size:13px}
|
||
.search-input::placeholder{color:rgba(255,255,255,.5)}
|
||
.toolbar-filters{display:flex;gap:8px}
|
||
.filter-dropdown{display:flex;align-items:center;gap:6px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);border-radius:6px;padding:0 10px}
|
||
.filter-select{background:transparent;border:none;color:#fff;font-family:inherit;font-size:12px;font-weight:600;padding:7px 4px;cursor:pointer;outline:none;min-width:110px}
|
||
.filter-select option{background:var(--primary-dark);color:#fff}
|
||
.toolbar-stats{display:flex;gap:12px;margin-right:auto;padding-right:8px;border-right:1px solid rgba(255,255,255,.1)}
|
||
.stat-item{display:flex;align-items:center;gap:4px;font-size:11px;color:rgba(255,255,255,.85)}
|
||
.stat-value{font-weight:800;color:var(--accent);background:rgba(201,169,97,.15);padding:2px 7px;border-radius:10px;font-size:11px}
|
||
.board-area{flex:1;overflow:auto;padding:24px;background:var(--board-bg)}
|
||
.board-section{margin-bottom:32px}
|
||
.board-section.collapsed .section-content{display:none}
|
||
.board-section.collapsed .collapse-icon{transform:rotate(90deg)}
|
||
.collapse-icon{transition:transform .3s;font-size:14px;display:inline-block;margin-left:6px}
|
||
.board-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;padding-bottom:12px;border-bottom:2px solid var(--border)}
|
||
.board-header-title{font-size:18px;font-weight:800;color:var(--primary);display:flex;align-items:center;gap:10px;cursor:pointer;user-select:none}
|
||
[data-theme="dark"] .board-header-title{color:var(--accent)}
|
||
.board-header-actions{display:flex;gap:8px}
|
||
.projects-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:20px}
|
||
.project-card{background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow-sm);transition:all .25s;display:flex;flex-direction:column}
|
||
.project-card:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg);border-color:var(--accent)}
|
||
.project-card-header{background:linear-gradient(135deg,var(--primary) 0%,var(--primary-light) 100%);color:#fff;padding:16px}
|
||
.project-card-title-row{display:flex;align-items:center;gap:10px;margin-bottom:4px}
|
||
.project-card-title{font-size:16px;font-weight:700;flex:1}
|
||
.project-card-desc{font-size:12px;opacity:.9;line-height:1.5}
|
||
.project-card-body{padding:14px 16px;flex:1;display:flex;flex-direction:column;gap:10px}
|
||
.project-stat-row{display:flex;justify-content:space-between;font-size:12px;color:var(--text-muted)}
|
||
.project-stat-value{font-weight:700;color:var(--primary)}
|
||
[data-theme="dark"] .project-stat-value{color:var(--accent)}
|
||
.project-type-badges{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
|
||
.type-badge{font-size:10px;font-weight:700;padding:2px 8px;border-radius:4px;display:inline-block}
|
||
.type-backend{background:#ebf4ff;color:#2b6cb0}.type-frontend{background:#f0fff4;color:#276749}.type-service{background:#fefcbf;color:#975a16}.type-database{background:#fed7e2;color:#97266d}.type-mobile{background:#e9d8fd;color:#553c9a}.type-infrastructure{background:#feebc8;color:#9c4221}
|
||
.project-card-footer{padding:12px 16px;background:var(--border-light);border-top:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;gap:6px}
|
||
.card-actions{display:flex;gap:6px}
|
||
.env-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
|
||
.env-card-item{background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow-sm);display:flex;flex-direction:column}
|
||
.env-card-preview{height:60px;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:#fff}
|
||
.env-card-body{padding:14px 16px;flex:1;display:flex;flex-direction:column;gap:8px}
|
||
.env-card-name{font-size:15px;font-weight:700}
|
||
.env-card-desc{font-size:12px;color:var(--text-muted)}
|
||
.env-card-footer{padding:10px 16px;background:var(--border-light);border-top:1px solid var(--border);display:flex;gap:6px}
|
||
.board-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-muted);text-align:center;padding:40px}
|
||
.board-empty-icon{font-size:80px;opacity:.3;margin-bottom:20px}
|
||
.board-empty h2{font-size:20px;color:var(--primary);margin-bottom:10px}
|
||
[data-theme="dark"] .board-empty h2{color:var(--accent)}
|
||
.no-results{text-align:center;padding:40px;color:var(--text-muted);background:var(--border-light);border-radius:var(--radius);margin:20px 0}
|
||
.modal-overlay{position:fixed;inset:0;background:rgba(26,54,93,.6);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:1000;padding:20px}
|
||
.modal-overlay.active{display:flex}
|
||
.modal{background:var(--panel-bg);color:var(--text);border-radius:12px;width:100%;max-width:600px;max-height:90vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:var(--shadow-lg)}
|
||
.modal-lg{max-width:800px}
|
||
.modal-xl{max-width:1000px}
|
||
.modal-galaxy{max-width:1200px;width:100%;height:85vh;max-height:85vh;border-radius:16px}
|
||
.modal-header{background:linear-gradient(135deg,var(--primary),var(--primary-light));color:#fff;padding:16px 20px;display:flex;align-items:center;justify-content:space-between;border-bottom:3px solid var(--accent);flex-wrap:wrap;gap:10px}
|
||
.modal-title{font-size:16px;font-weight:700;display:flex;align-items:center;gap:10px}
|
||
.modal-close{background:rgba(255,255,255,.15);border:none;color:#fff;width:30px;height:30px;border-radius:50%;cursor:pointer;font-size:16px}
|
||
.modal-close:hover{background:var(--danger)}
|
||
.modal-body{padding:20px;overflow-y:auto;flex:1}
|
||
.modal-footer{padding:14px 20px;border-top:1px solid var(--border);display:flex;justify-content:flex-start;gap:8px;background:var(--border-light);align-items:center;flex-wrap:wrap}
|
||
.detail-section{margin-bottom:20px;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden}
|
||
.detail-section-header{background:linear-gradient(135deg,var(--primary),var(--primary-light));color:#fff;padding:12px 16px;font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:space-between}
|
||
.detail-section-header-actions{display:flex;align-items:center;gap:8px}
|
||
.detail-section-body{padding:16px;background:var(--card-bg)}
|
||
.detail-info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}
|
||
.detail-info-item{display:flex;flex-direction:column;gap:4px}
|
||
.detail-info-label{font-size:11px;color:var(--text-muted);font-weight:600}
|
||
.detail-info-value{font-size:13px;font-weight:600}
|
||
.subproject-list{display:flex;flex-direction:column;gap:10px}
|
||
.subproject-card{border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px;background:var(--border-light)}
|
||
.subproject-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;flex-wrap:wrap;gap:8px}
|
||
.subproject-title{font-size:14px;font-weight:700;display:flex;align-items:center;gap:8px}
|
||
.subproject-actions{display:flex;gap:4px}
|
||
.subproject-meta{font-size:12px;color:var(--text-muted);margin-bottom:8px}
|
||
.deployment-section{margin-top:10px;padding-top:10px;border-top:1px dashed var(--border)}
|
||
.deployment-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
|
||
.deployment-header-title{font-size:11px;font-weight:600;color:var(--text-muted)}
|
||
.deployment-list{display:flex;flex-direction:column;gap:6px}
|
||
.deployment-item{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;background:var(--card-bg);border:1px solid var(--border-light);border-radius:6px;font-size:11px;flex-wrap:wrap;gap:6px}
|
||
.deployment-item-info{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
||
.deployment-env-tag{display:inline-flex;align-items:center;gap:4px;font-size:10px;padding:2px 8px;border-radius:4px;font-weight:600}
|
||
.deployment-version{font-family:'Vazirmatn',monospace;font-weight:700;color:var(--primary);font-size:11px}
|
||
[data-theme="dark"] .deployment-version{color:var(--accent)}
|
||
.deployment-date{font-size:10px;color:var(--text-muted)}
|
||
.deployment-actions{display:flex;gap:4px}
|
||
.deployment-notes{font-size:10px;color:var(--text-muted);width:100%;margin-top:4px;padding-top:4px;border-top:1px dashed var(--border-light);line-height:1.5}
|
||
.deployment-req{font-size:10px;color:var(--info);width:100%;margin-top:2px}
|
||
.empty-deployments{text-align:center;padding:12px;color:var(--text-muted);font-size:11px;background:var(--border-light);border-radius:6px}
|
||
.empty-subprojects{text-align:center;padding:30px;color:var(--text-muted);background:var(--border-light);border-radius:var(--radius-sm)}
|
||
.form-group{margin-bottom:16px}
|
||
.form-label{display:block;font-size:13px;font-weight:600;color:var(--primary);margin-bottom:6px}
|
||
[data-theme="dark"] .form-label{color:var(--accent-light)}
|
||
.form-label .required{color:var(--danger)}
|
||
.form-input,.form-textarea,.form-select{width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);font-family:inherit;font-size:13px;background:var(--card-bg);color:var(--text)}
|
||
.form-input:focus,.form-textarea:focus,.form-select:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(201,169,97,.15)}
|
||
.form-textarea{resize:vertical;min-height:70px;line-height:1.6}
|
||
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
||
.form-hint{font-size:11px;color:var(--text-muted);margin-top:4px}
|
||
.color-input-wrapper{display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius-sm)}
|
||
.color-input-wrapper input[type="color"]{width:32px;height:32px;border:none;border-radius:4px;cursor:pointer;padding:0}
|
||
.color-preview-text{font-size:11px;font-family:monospace;color:var(--text-muted)}
|
||
.webhook-section{background:var(--border-light);border:1px solid var(--border);border-radius:var(--radius-sm);padding:14px;margin-top:12px}
|
||
.webhook-section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
|
||
.webhook-section-title{font-size:13px;font-weight:700;color:var(--primary);display:flex;align-items:center;gap:6px}
|
||
.webhook-toggle{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text-muted)}
|
||
.switch{position:relative;display:inline-block;width:36px;height:20px}
|
||
.switch input{opacity:0;width:0;height:0}
|
||
.slider{position:absolute;cursor:pointer;inset:0;background:var(--border);transition:.3s;border-radius:20px}
|
||
.slider::before{position:absolute;content:"";height:14px;width:14px;right:3px;bottom:3px;background:#fff;transition:.3s;border-radius:50%}
|
||
input:checked+.slider{background:var(--success)}
|
||
input:checked+.slider::before{transform:translateX(-16px)}
|
||
.webhook-status{display:flex;align-items:center;gap:8px;margin-top:10px;padding:8px 10px;background:var(--card-bg);border-radius:4px;font-size:11px;color:var(--text-muted)}
|
||
.webhook-status-dot{width:8px;height:8px;border-radius:50%;background:var(--text-muted)}
|
||
.webhook-status-dot.success{background:var(--success)}
|
||
.webhook-status-dot.error{background:var(--danger)}
|
||
.toast-container{position:fixed;bottom:20px;left:20px;z-index:2000;display:flex;flex-direction:column;gap:8px}
|
||
.toast{background:var(--primary);color:#fff;padding:12px 18px;border-radius:8px;box-shadow:var(--shadow-lg);font-size:13px;display:flex;align-items:center;gap:10px;min-width:250px}
|
||
.toast.success{background:var(--success)}
|
||
.toast.error{background:var(--danger)}
|
||
.toast.warning{background:var(--warning)}
|
||
.app-footer{background:linear-gradient(135deg,var(--primary),var(--primary-light));color:#fff;padding:8px 20px;font-size:11px;display:flex;justify-content:space-between;align-items:center;border-top:2px solid var(--accent);flex-shrink:0}
|
||
.footer-brand{color:var(--accent);font-weight:700}
|
||
.form-input[readonly],.form-textarea[readonly]{background:var(--border-light);color:var(--text-muted);cursor:not-allowed;opacity:.85;border-style:dashed}
|
||
.readonly-badge{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;color:var(--text-muted);background:var(--border-light);border:1px solid var(--border);padding:2px 8px;border-radius:10px;margin-right:6px}
|
||
.readonly-badge::before{content:'🔒';font-size:10px}
|
||
.readonly-info{background:linear-gradient(to left,#ebf8ff,#e6f6ff);border:1px solid #90cdf4;border-right:4px solid var(--info);padding:10px 14px;border-radius:6px;font-size:11px;color:var(--text-muted);margin-bottom:14px;display:flex;align-items:center;gap:8px}
|
||
.dependency-list{display:flex;flex-direction:column;gap:8px;margin-bottom:20px;max-height:200px;overflow-y:auto}
|
||
.dependency-item{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius-sm)}
|
||
.dependency-info{display:flex;align-items:center;gap:10px;flex:1}
|
||
.dependency-project-name{font-weight:600;font-size:13px}
|
||
.dependency-search-box{margin-top:16px;padding-top:16px;border-top:1px solid var(--border)}
|
||
.dependency-search-input{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);font-family:inherit;font-size:13px;background:var(--card-bg);color:var(--text);margin-bottom:12px}
|
||
.dependency-search-results{max-height:300px;overflow-y:auto}
|
||
.dependency-project-result{margin-bottom:8px;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden}
|
||
.dependency-project-add-btn{padding:10px 12px;background:var(--card-bg);cursor:pointer;display:flex;align-items:center;gap:8px;font-size:13px;font-weight:600;width:100%;border:none;font-family:inherit;text-align:right}
|
||
.dependency-project-add-btn:hover{background:var(--accent-light)}
|
||
.empty-dependencies{text-align:center;padding:20px;color:var(--text-muted);font-size:12px;background:var(--border-light);border-radius:var(--radius-sm)}
|
||
.dependency-section-title{font-size:13px;font-weight:700;color:var(--primary);margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
||
[data-theme="dark"] .dependency-section-title{color:var(--accent)}
|
||
.env-color-preview{padding:10px;border-radius:6px;text-align:center;font-weight:700;border:1px solid var(--border)}
|
||
.env-read-only-tag{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:600;padding:6px 10px;border-radius:6px}
|
||
.req-deployment-box{border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px;margin-bottom:8px;background:var(--border-light)}
|
||
.req-deployment-title{font-size:12px;font-weight:700;margin-bottom:6px;display:flex;align-items:center;gap:6px}
|
||
.req-deployment-select{width:100%;padding:8px;border:1px solid var(--border);border-radius:var(--radius-sm);font-family:inherit;font-size:12px;background:var(--card-bg);color:var(--text)}
|
||
.galaxy-toolbar{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
|
||
.galaxy-toolbar .btn{background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.2);color:#fff;font-size:11px;padding:5px 10px}
|
||
.galaxy-toolbar .btn:hover{background:var(--accent);color:var(--primary-dark)}
|
||
.galaxy-modal-body{padding:0;position:relative;overflow:hidden;background:#0a0e27}
|
||
.galaxy-chart-container{width:100%;height:100%;min-height:400px;position:relative}
|
||
.galaxy-chart-container svg{width:100%;height:100%;display:block;cursor:grab}
|
||
.galaxy-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:rgba(255,255,255,.5);text-align:center;font-size:16px;gap:10px}
|
||
.galaxy-legend{position:absolute;bottom:16px;right:16px;background:rgba(10,14,39,.85);border:1px solid rgba(201,169,97,.3);border-radius:10px;padding:12px 16px;color:#fff;font-size:11px;z-index:10;max-width:220px}
|
||
.galaxy-legend-title{font-weight:700;font-size:12px;margin-bottom:10px;color:var(--accent-light)}
|
||
.galaxy-legend-item{display:flex;align-items:center;gap:8px;margin-bottom:6px}
|
||
.galaxy-legend-color{width:14px;height:14px;border-radius:50%;box-shadow:0 0 8px currentColor}
|
||
.galaxy-info-panel{position:absolute;top:16px;left:16px;background:rgba(10,14,39,.9);border:1px solid rgba(201,169,97,.3);border-radius:10px;padding:14px 18px;color:#fff;font-size:12px;z-index:10;max-width:280px;display:none}
|
||
.galaxy-info-panel.visible{display:block}
|
||
.galaxy-info-panel h4{font-size:14px;font-weight:700;color:var(--accent-light);margin-bottom:10px}
|
||
.galaxy-info-row{display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid rgba(255,255,255,.1);gap:12px}
|
||
.galaxy-info-label{color:rgba(255,255,255,.6);font-size:11px}
|
||
.galaxy-info-value{font-weight:600;font-size:11px;text-align:left}
|
||
.galaxy-tooltip{position:fixed;background:rgba(10,14,39,.95);border:1px solid var(--accent);border-radius:8px;padding:10px 14px;color:#fff;font-size:12px;pointer-events:none;z-index:3000;max-width:250px;display:none}
|
||
.galaxy-tooltip.visible{display:block}
|
||
.galaxy-zoom-indicator{position:absolute;bottom:16px;left:16px;background:rgba(10,14,39,.85);border:1px solid rgba(201,169,97,.3);border-radius:8px;padding:6px 12px;color:#fff;font-size:12px;z-index:10;font-weight:600}
|
||
.galaxy-stats{font-size:12px;color:var(--text-muted);display:flex;gap:16px;flex-wrap:wrap}
|
||
.galaxy-stat-value{font-weight:700;color:var(--primary);background:rgba(26,54,93,.1);padding:2px 8px;border-radius:8px}
|
||
[data-theme="dark"] .galaxy-stat-value{color:var(--accent);background:rgba(201,169,97,.15)}
|
||
.dashboard-modal-body{padding:20px;background:var(--bg);overflow-y:auto}
|
||
.dashboard-container{display:flex;flex-direction:column;gap:20px}
|
||
.dashboard-kpi-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px}
|
||
.dashboard-kpi-card{background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius);padding:16px;text-align:center;position:relative;overflow:hidden}
|
||
.dashboard-kpi-card::before{content:'';position:absolute;top:0;right:0;left:0;height:3px;background:linear-gradient(90deg,var(--primary),var(--accent))}
|
||
.dashboard-kpi-icon{font-size:28px;margin-bottom:8px}
|
||
.dashboard-kpi-value{font-size:28px;font-weight:800;color:var(--primary);line-height:1;margin-bottom:4px}
|
||
[data-theme="dark"] .dashboard-kpi-value{color:var(--accent)}
|
||
.dashboard-kpi-label{font-size:11px;color:var(--text-muted);font-weight:600}
|
||
.dashboard-charts-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px}
|
||
.dashboard-chart-card{background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}
|
||
.dashboard-chart-header{padding:12px 16px;border-bottom:1px solid var(--border-light);display:flex;align-items:center;gap:8px;font-size:13px;font-weight:700;color:var(--primary)}
|
||
[data-theme="dark"] .dashboard-chart-header{color:var(--accent)}
|
||
.dashboard-chart-body{padding:16px;display:flex;justify-content:center;align-items:center;min-height:220px}
|
||
.dashboard-chart-body svg{max-width:100%;height:auto}
|
||
.dashboard-empty-chart{display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-muted);font-size:12px;gap:8px;text-align:center;padding:20px}
|
||
.dashboard-empty-chart span{font-size:32px;opacity:.4}
|
||
.dashboard-footer-stats{font-size:11px;color:var(--text-muted);display:flex;gap:16px;flex-wrap:wrap}
|
||
.dashboard-footer-stats .stat-highlight{font-weight:700;color:var(--primary)}
|
||
[data-theme="dark"] .dashboard-footer-stats .stat-highlight{color:var(--accent)}
|
||
.d3-tooltip{position:fixed;background:var(--primary-dark);color:#fff;padding:8px 12px;border-radius:6px;font-size:11px;font-weight:600;pointer-events:none;z-index:3000;display:none;font-family:'Vazirmatn',sans-serif}
|
||
.d3-tooltip.visible{display:block}
|
||
::-webkit-scrollbar{width:8px;height:8px}
|
||
::-webkit-scrollbar-track{background:transparent}
|
||
::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header class="app-header">
|
||
<div class="app-brand">
|
||
<div class="app-logo">📊</div>
|
||
<div class="app-brand-text">
|
||
<div class="app-title" id="headerTitle">دستیار انتشار</div>
|
||
<div class="app-subtitle" id="headerSubtitle">مدیریت نسخهها و نیازمندیهای محصولات</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="tabs-bar" id="advancedToolbar">
|
||
<div class="toolbar-controls">
|
||
<button class="toolbar-icon-btn" id="sidebarToggle" title="سایدبار"><span id="sidebarIcon">✕</span></button>
|
||
<button class="toolbar-icon-btn" id="themeToggle" title="تم"><span id="themeIcon">🌙</span></button>
|
||
<button class="toolbar-icon-btn" id="undoBtn" title="Ctrl+Z" disabled>↩</button>
|
||
<button class="toolbar-icon-btn" id="redoBtn" title="Ctrl+Y" disabled>↪</button>
|
||
</div>
|
||
<div class="toolbar-search"><span style="position:absolute;right:10px;opacity:.7">🔍</span><input type="text" class="search-input" id="toolbarSearch" placeholder="جستجو..."></div>
|
||
<div class="toolbar-filters">
|
||
<div class="filter-dropdown"><span>🏷️</span><select class="filter-select" id="filterType">
|
||
<option value="">همه انواع</option>
|
||
<option value="backend">بکاند</option>
|
||
<option value="frontend">فرانتاند</option>
|
||
<option value="service">میکروسرویس</option>
|
||
<option value="database">پایگاه داده</option>
|
||
<option value="mobile">موبایل</option>
|
||
<option value="infrastructure">زیرساخت</option>
|
||
</select></div>
|
||
<div class="filter-dropdown"><span>🌐</span><select class="filter-select" id="filterEnvironment">
|
||
<option value="">همه محیطها</option>
|
||
</select></div>
|
||
</div>
|
||
<div class="toolbar-stats">
|
||
<div class="stat-item">📋 <span class="stat-value" id="statTotal">0</span></div>
|
||
<div class="stat-item">📦 <span class="stat-value" id="statProjects">0</span></div>
|
||
<div class="stat-item">🌿 <span class="stat-value" id="statSubProjects">0</span></div>
|
||
<div class="stat-item">🔗 <span class="stat-value" id="statDependencies">0</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="app-main">
|
||
<aside class="sidebar" id="sidebar">
|
||
<div class="sidebar-header"><div class="sidebar-title">🛠️ ابزارها</div></div>
|
||
<div class="sidebar-body" id="sidebarBody"></div>
|
||
</aside>
|
||
<main class="board-area" id="boardArea"></main>
|
||
</div>
|
||
|
||
<footer class="app-footer" id="appFooter">
|
||
<div><span class="footer-brand">طراح و توسعهدهنده:</span> هادی خزاعی اصل <span style="margin:0 10px">|</span> <span class="footer-brand">شرکت:</span> رایان پرداز کاوش</div>
|
||
<div><span class="footer-brand">نسخه:</span> 1.1.0</div>
|
||
</footer>
|
||
|
||
<!-- Settings Modal -->
|
||
<div class="modal-overlay" id="settingsModal">
|
||
<div class="modal modal-lg">
|
||
<div class="modal-header"><div class="modal-title">⚙️ تنظیمات محصول</div><button class="modal-close" data-close-modal>✕</button></div>
|
||
<div class="modal-body">
|
||
<div class="readonly-info">ℹ️ اطلاعات پایه محصول فقط قابل مشاهده هستند.</div>
|
||
<div class="form-row"><div class="form-group"><label class="form-label">عنوان محصول <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="settingProjectTitle" readonly></div><div class="form-group"><label class="form-label">نسخه <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="settingVersion" readonly></div></div>
|
||
<div class="form-group"><label class="form-label">توضیحات <span class="readonly-badge">فقط خواندنی</span></label><textarea class="form-textarea" id="settingDescription" readonly></textarea></div>
|
||
<div class="form-row"><div class="form-group"><label class="form-label">توسعهدهنده <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="settingDeveloper" readonly></div><div class="form-group"><label class="form-label">شرکت <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="settingCompany" readonly></div></div>
|
||
<div class="webhook-section">
|
||
<div class="webhook-section-header"><div class="webhook-section-title">🔗 تنظیمات وبهوک</div><div class="webhook-toggle"><span>فعالسازی</span><label class="switch"><input type="checkbox" id="settingWebhookEnabled"><span class="slider"></span></label></div></div>
|
||
<div class="form-group"><label class="form-label">آدرس URL وبهوک <span class="required">*</span></label><input type="url" class="form-input" id="settingWebhookUrl" dir="ltr"></div>
|
||
<div class="form-group"><label class="form-label">شناسه کلاینت <span class="required">*</span></label><input type="text" class="form-input" id="settingWebhookClientId" dir="ltr"></div>
|
||
<div class="webhook-status"><span class="webhook-status-dot" id="webhookStatusDot"></span><span id="webhookStatusText">وضعیت: غیرفعال</span></div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer"><button class="btn btn-accent" id="saveSettingsBtn">💾 ذخیره</button><button class="btn btn-ghost" data-close-modal>بستن</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Environment Modals -->
|
||
<div class="modal-overlay" id="envManagerModal">
|
||
<div class="modal modal-lg">
|
||
<div class="modal-header"><div class="modal-title">🌍 افزودن محیط عملیاتی جدید</div><button class="modal-close" data-close-modal>✕</button></div>
|
||
<div class="modal-body">
|
||
<div class="form-row"><div class="form-group"><label class="form-label">نام محیط <span class="required">*</span></label><input type="text" class="form-input" id="newEnvName"></div><div class="form-group"><label class="form-label">توضیحات</label><input type="text" class="form-input" id="newEnvDesc"></div></div>
|
||
<div class="form-row"><div class="form-group"><label class="form-label">رنگ پسزمینه</label><div class="color-input-wrapper"><input type="color" id="newEnvBgColor" value="#38a169"><span class="color-preview-text" id="newEnvBgColorText">#38a169</span></div></div><div class="form-group"><label class="form-label">رنگ متن</label><div class="color-input-wrapper"><input type="color" id="newEnvTextColor" value="#ffffff"><span class="color-preview-text" id="newEnvTextColorText">#ffffff</span></div></div></div>
|
||
</div>
|
||
<div class="modal-footer"><button class="btn btn-accent" id="saveNewEnvBtn">💾 ثبت</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-overlay" id="envEditModal">
|
||
<div class="modal">
|
||
<div class="modal-header"><div class="modal-title">✏️ ویرایش محیط عملیاتی</div><button class="modal-close" data-close-modal>✕</button></div>
|
||
<div class="modal-body">
|
||
<div class="form-group"><label class="form-label">شناسه <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="editEnvId" readonly></div>
|
||
<div class="form-group"><label class="form-label">نام <span class="required">*</span></label><input type="text" class="form-input" id="editEnvName"></div>
|
||
<div class="form-group"><label class="form-label">توضیحات</label><input type="text" class="form-input" id="editEnvDesc"></div>
|
||
<div class="form-row"><div class="form-group"><label class="form-label">رنگ پسزمینه</label><div class="color-input-wrapper"><input type="color" id="editEnvBgColor"><span class="color-preview-text" id="editEnvBgColorText"></span></div></div><div class="form-group"><label class="form-label">رنگ متن</label><div class="color-input-wrapper"><input type="color" id="editEnvTextColor"><span class="color-preview-text" id="editEnvTextColorText"></span></div></div></div>
|
||
<div class="form-group"><label class="form-label">پیشنمایش</label><div id="editEnvPreview" class="env-color-preview">نمونه</div></div>
|
||
</div>
|
||
<div class="modal-footer"><button class="btn btn-accent" id="saveEnvEditBtn">💾 ذخیره</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Product Modals -->
|
||
<div class="modal-overlay" id="projectDetailModal"><div class="modal modal-xl"><div class="modal-header"><div class="modal-title" id="projectDetailTitle">📦 جزئیات محصول</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body" id="projectDetailBody"></div><div class="modal-footer"><button class="btn btn-danger btn-sm" id="projectDetailDeleteBtn">🗑️ حذف محصول</button><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
|
||
<div class="modal-overlay" id="productEditModal"><div class="modal modal-lg"><div class="modal-header"><div class="modal-title">✏️ ویرایش محصول</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="form-group"><label class="form-label">شناسه <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="editProductId" readonly></div><div class="form-group"><label class="form-label">عنوان <span class="required">*</span></label><input type="text" class="form-input" id="editProductTitle"></div><div class="form-group"><label class="form-label">توضیحات</label><textarea class="form-textarea" id="editProductDescription"></textarea></div><div class="form-row"><div class="form-group"><label class="form-label">مالک</label><input type="text" class="form-input" id="editProductOwner"></div><div class="form-group"><label class="form-label">تاریخ ایجاد <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="editProductCreatedAt" readonly></div></div></div><div class="modal-footer"><button class="btn btn-accent" id="saveProductEditBtn">💾 ذخیره</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div></div></div>
|
||
<div class="modal-overlay" id="addProductModal"><div class="modal modal-lg"><div class="modal-header"><div class="modal-title">➕ ایجاد محصول جدید</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="form-group"><label class="form-label">عنوان <span class="required">*</span></label><input type="text" class="form-input" id="newProductTitle"><div class="form-error" id="newProductTitleError">عنوان محصول الزامی است و باید حداقل ۳ کاراکتر باشد</div></div><div class="form-group"><label class="form-label">توضیحات</label><textarea class="form-textarea" id="newProductDescription"></textarea></div><div class="form-row"><div class="form-group"><label class="form-label">مالک</label><input type="text" class="form-input" id="newProductOwner"></div><div class="form-group"><label class="form-label">تاریخ ایجاد <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="newProductCreatedAt" readonly></div></div></div><div class="modal-footer"><button class="btn btn-accent" id="saveNewProductBtn">💾 ثبت</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div></div></div>
|
||
<div class="modal-overlay" id="addProjectModal"><div class="modal modal-lg"><div class="modal-header"><div class="modal-title">➕ افزودن پروژه جدید</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="form-group"><label class="form-label">عنوان <span class="required">*</span></label><input type="text" class="form-input" id="newProjectTitle"></div><div class="form-row"><div class="form-group"><label class="form-label">نوع <span class="required">*</span></label><select class="form-select" id="newProjectType"><option value="">انتخاب کنید...</option></select></div><div class="form-group"><label class="form-label">فریمورک</label><input type="text" class="form-input" id="newProjectFramework"></div></div><div class="form-row"><div class="form-group"><label class="form-label">زبان</label><input type="text" class="form-input" id="newProjectLanguage"></div><div class="form-group"><label class="form-label">آدرس مخزن</label><input type="text" class="form-input" id="newProjectRepo" dir="ltr"></div></div><div class="form-group"><label class="form-label">توضیحات</label><textarea class="form-textarea" id="newProjectDescription"></textarea></div></div><div class="modal-footer"><button class="btn btn-accent" id="saveNewProjectBtn">💾 ذخیره</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div></div></div>
|
||
<div class="modal-overlay" id="editProjectModal"><div class="modal modal-lg"><div class="modal-header"><div class="modal-title">✏️ ویرایش پروژه</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="form-group"><label class="form-label">شناسه <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="editProjId" readonly></div><div class="form-group"><label class="form-label">عنوان <span class="required">*</span></label><input type="text" class="form-input" id="editProjTitle"></div><div class="form-row"><div class="form-group"><label class="form-label">نوع <span class="required">*</span></label><select class="form-select" id="editProjType"><option value="">انتخاب کنید...</option></select></div><div class="form-group"><label class="form-label">فریمورک</label><input type="text" class="form-input" id="editProjFramework"></div></div><div class="form-row"><div class="form-group"><label class="form-label">زبان</label><input type="text" class="form-input" id="editProjLanguage"></div><div class="form-group"><label class="form-label">آدرس مخزن</label><input type="text" class="form-input" id="editProjRepo" dir="ltr"></div></div><div class="form-group"><label class="form-label">توضیحات</label><textarea class="form-textarea" id="editProjDescription"></textarea></div></div><div class="modal-footer"><button class="btn btn-accent" id="saveProjEditBtn">💾 ذخیره</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div></div></div>
|
||
|
||
<!-- Deployment Modal -->
|
||
<div class="modal-overlay" id="deploymentModal">
|
||
<div class="modal modal-lg">
|
||
<div class="modal-header"><div class="modal-title" id="deploymentModalTitle">🚀 افزودن نسخه استقرار</div><button class="modal-close" data-close-modal>✕</button></div>
|
||
<div class="modal-body">
|
||
<div class="form-group" id="deploymentEnvGroup"></div>
|
||
<div class="form-row"><div class="form-group"><label class="form-label">نسخه <span class="required">*</span></label><input type="text" class="form-input" id="depVersion" dir="ltr" placeholder="مثلاً: 1.2.3"><div class="form-hint">از فرمت SemVer استفاده کنید</div></div><div class="form-group"><label class="form-label">تاریخ و زمان استقرار <span class="required">*</span></label><input type="datetime-local" class="form-input" id="depDate" dir="ltr"></div></div>
|
||
<div class="form-group"><label class="form-label">ریلیز نوت <span class="required">*</span></label><textarea class="form-textarea" id="depReleaseNotes" rows="3" placeholder="توضیحات تغییرات این نسخه..."></textarea><div class="form-hint">شرح تغییرات، باگفیکسها و ویژگیهای جدید این نسخه</div></div>
|
||
<div id="requiredDeploymentsSection"></div>
|
||
</div>
|
||
<div class="modal-footer"><button class="btn btn-accent" id="saveDeploymentBtn">💾 ذخیره</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Dependency Manager Modal -->
|
||
<div class="modal-overlay" id="dependencyManagerModal">
|
||
<div class="modal modal-lg">
|
||
<div class="modal-header"><div class="modal-title">🔗 مدیریت وابستگیها</div><button class="modal-close" data-close-modal>✕</button></div>
|
||
<div class="modal-body">
|
||
<div class="readonly-info">ℹ️ هر وابستگی فقط شامل یک پروژه است. انتخاب نسخه و محیط در زمان ثبت استقرار انجام میشود.</div>
|
||
<div class="dependency-section-title">📋 وابستگیهای فعلی</div>
|
||
<div id="currentDependenciesList" class="dependency-list"></div>
|
||
<div class="dependency-search-box"><div class="dependency-section-title">🔍 افزودن وابستگی جدید</div><input type="text" class="dependency-search-input" id="dependencySearch" placeholder="جستجو در پروژهها..."><div id="dependencySearchResults" class="dependency-search-results"></div></div>
|
||
</div>
|
||
<div class="modal-footer"><button class="btn btn-ghost" data-close-modal>بستن</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Galaxy Chart Modal -->
|
||
<div class="modal-overlay" id="galaxyChartModal">
|
||
<div class="modal modal-galaxy">
|
||
<div class="modal-header"><div class="modal-title">🌌 نمودار کهکشانی محصولات</div><div class="galaxy-toolbar"><button class="btn btn-sm" id="galaxyResetView">🎯 بازنشانی</button><button class="btn btn-sm" id="galaxyExportPNG">📷 PNG</button></div><button class="modal-close" data-close-modal>✕</button></div>
|
||
<div class="modal-body galaxy-modal-body">
|
||
<div id="galaxyChartContainer" class="galaxy-chart-container"></div>
|
||
<div class="galaxy-legend" id="galaxyLegend"></div>
|
||
<div class="galaxy-info-panel" id="galaxyInfoPanel"><button class="modal-close" id="galaxyInfoClose" style="position:absolute;top:8px;left:8px;width:22px;height:22px;font-size:12px">✕</button><div id="galaxyInfoContent"></div></div>
|
||
<div class="galaxy-zoom-indicator" id="galaxyZoomIndicator">🔍 100%</div>
|
||
</div>
|
||
<div class="modal-footer"><div class="galaxy-stats" id="galaxyStats"></div><button class="btn btn-ghost" data-close-modal>بستن</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Executive Dashboard Modal -->
|
||
<div class="modal-overlay" id="executiveDashboardModal">
|
||
<div class="modal modal-galaxy">
|
||
<div class="modal-header"><div class="modal-title">📊 داشبورد مدیریتی</div><div class="galaxy-toolbar"><button class="btn btn-sm" id="dashboardRefresh">🔄 بهروزرسانی</button></div><button class="modal-close" data-close-modal>✕</button></div>
|
||
<div class="modal-body dashboard-modal-body"><div id="dashboardContainer" class="dashboard-container"></div></div>
|
||
<div class="modal-footer"><div class="dashboard-footer-stats" id="dashboardFooterStats"></div><button class="btn btn-ghost" data-close-modal>بستن</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Analysis Modals -->
|
||
<div class="modal-overlay" id="dependencyAnalysisModal"><div class="modal modal-galaxy"><div class="modal-header"><div class="modal-title">🔗 تحلیل وابستگیها</div><div class="galaxy-toolbar"><button class="btn btn-sm" id="depAnalysisRefresh">🔄</button></div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body dashboard-modal-body"><div id="dependencyAnalysisContainer" class="dashboard-container"></div></div><div class="modal-footer"><div class="dashboard-footer-stats" id="depAnalysisFooterStats"></div><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
|
||
<div class="modal-overlay" id="deploymentAnalysisModal"><div class="modal modal-galaxy"><div class="modal-header"><div class="modal-title">🚀 تحلیل استقرار</div><div class="galaxy-toolbar"><button class="btn btn-sm" id="deployAnalysisRefresh">🔄</button></div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body dashboard-modal-body"><div id="deploymentAnalysisContainer" class="dashboard-container"></div></div><div class="modal-footer"><div class="dashboard-footer-stats" id="deployAnalysisFooterStats"></div><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
|
||
<div class="modal-overlay" id="versionAnalysisModal"><div class="modal modal-galaxy"><div class="modal-header"><div class="modal-title">🔢 تحلیل نسخه</div><div class="galaxy-toolbar"><button class="btn btn-sm" id="verAnalysisRefresh">🔄</button></div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body dashboard-modal-body"><div id="versionAnalysisContainer" class="dashboard-container"></div></div><div class="modal-footer"><div class="dashboard-footer-stats" id="verAnalysisFooterStats"></div><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
|
||
<div class="modal-overlay" id="riskAnalysisModal"><div class="modal modal-galaxy"><div class="modal-header"><div class="modal-title">⚠️ تحلیل ریسک</div><div class="galaxy-toolbar"><button class="btn btn-sm" id="riskAnalysisRefresh">🔄</button></div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body dashboard-modal-body"><div id="riskAnalysisContainer" class="dashboard-container"></div></div><div class="modal-footer"><div class="dashboard-footer-stats" id="riskAnalysisFooterStats"></div><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
|
||
|
||
<div class="toast-container" id="toastContainer"></div>
|
||
<div class="galaxy-tooltip" id="galaxyTooltip"></div>
|
||
<div class="d3-tooltip" id="d3Tooltip"></div>
|
||
|
||
<script>
|
||
/* ==================== DATA ==================== */
|
||
const projectData={projectId:'550e8400-e29b-41d4-a716-446655440000',projectName:'xReleaseAssistant',projectTitle:'دستیار انتشار',description:'مدیریت نسخهها و نیازمندیهای محصولات',version:'1.1.0',developer:'هادی خزاعی اصل',company:'رایان پرداز کاوش',metadata:{createdAt:'2026-09-13T00:00:00Z',updatedAt:'2026-09-14T00:00:00Z',tags:['release-management'],customFields:{}},webhook:{url:'',clientId:'',enabled:false},toolGroups:[{id:'product-management',title:'پرونده',icon:'📁',description:'مدیریت چرخه حیات محصول',tools:[{id:'clear-all',title:'پاکسازی',icon:'🗑️',action:'clearAll',description:'پاکسازی کامل'},{id:'save-product',title:'ذخیره',icon:'💾',action:'saveProduct',description:'دانلود دادهها'},{id:'load-product',title:'بارگذاری',icon:'📂',action:'loadProduct',description:'بارگذاری از فایل'},{id:'product-settings',title:'تنظیمات',icon:'⚙️',action:'openProductSettings',description:'پیکربندی'}]},{id:'reports',title:'گزارشها',icon:'📊',description:'تحلیل و گزارشگیری',tools:[{id:'executive-dashboard',title:'داشبورد مدیریتی',icon:'📊',action:'showExecutiveDashboard',description:'نمای کلی'},{id:'dependency-analysis',title:'تحلیل وابستگیها',icon:'🔗',action:'showDependencyAnalysis',description:'تحلیل وابستگیها'},{id:'deployment-analysis',title:'تحلیل استقرار',icon:'🚀',action:'showDeploymentAnalysis',description:'تحلیل استقرارها'},{id:'version-analysis',title:'تحلیل نسخه',icon:'🔢',action:'showVersionAnalysis',description:'تحلیل نسخهها'},{id:'risk-analysis',title:'تحلیل ریسک',icon:'⚠️',action:'showRiskAnalysis',description:'تحلیل ریسکها'},{id:'galaxy-chart',title:'نمودار کهکشانی',icon:'🌌',action:'showGalaxyChart',description:'نمودار کهکشانی'}]}],
|
||
appData:{environments:[{id:'env-prod-001',name:'Production',description:'محیط عملیاتی اصلی',backgroundColor:'#38a169',textColor:'#ffffff'},{id:'env-stage-002',name:'Staging',description:'محیط پیش از تولید',backgroundColor:'#dd6b20',textColor:'#ffffff'},{id:'env-dev-003',name:'Development',description:'محیط توسعه',backgroundColor:'#3182ce',textColor:'#ffffff'},{id:'env-qa-004',name:'QA',description:'تست کیفیت',backgroundColor:'#805ad5',textColor:'#ffffff'},{id:'env-uat-005',name:'UAT',description:'تست پذیرش کاربر',backgroundColor:'#d69e2e',textColor:'#ffffff'},{id:'env-dr-006',name:'Disaster Recovery',description:'بازیابی پس از بحران',backgroundColor:'#e53e3e',textColor:'#ffffff'}],
|
||
products:[{id:'prod-ecommerce',title:'سامانه فروشگاه آنلاین دیجیمارکت',description:'پلتفرم جامع تجارت الکترونیک',metadata:{createdAt:'2026-01-15T08:00:00Z',owner:'تیم توسعه تجارت الکترونیک',customFields:{industry:'E-commerce',priority:'critical'}},projects:[{id:'proj-ecom-api',title:'E-commerce Core API',description:'سرویس بکاند اصلی فروشگاه',type:'backend',repositoryUrl:'https://git.rayanpardaz.ir/ecommerce/core-api',metadata:{framework:'.NET 8',language:'C#'},dependencies:['proj-payment-gateway','proj-inventory-svc'],deployments:[{id:'dep-ecom-api-prod',environmentId:'env-prod-001',version:'3.5.2',deploymentDate:'2026-09-10T14:30:00Z',releaseNotes:'رفع باگ پردازش سفارش و بهبود عملکرد',requiredDeployments:[{projectId:'proj-payment-gateway',deploymentId:'dep-payment-prod'},{projectId:'proj-inventory-svc',deploymentId:'dep-inventory-prod'}],status:'active'},{id:'dep-ecom-api-stage',environmentId:'env-stage-002',version:'3.6.0-rc.1',deploymentDate:'2026-09-14T09:00:00Z',releaseNotes:'افزودن قابلیت چندزبانه',requiredDeployments:[],status:'active'},{id:'dep-ecom-api-dev',environmentId:'env-dev-003',version:'3.7.0-dev.12',deploymentDate:'2026-09-15T16:00:00Z',releaseNotes:'پیادهسازی اولیه ماژول تخفیف',requiredDeployments:[],status:'active'}]},{id:'proj-ecom-web',title:'E-commerce Web Frontend',description:'اپلیکیشن وب فروشگاه',type:'frontend',repositoryUrl:'https://git.rayanpardaz.ir/ecommerce/web-app',metadata:{framework:'Angular 18',language:'TypeScript'},dependencies:['proj-ecom-api'],deployments:[{id:'dep-ecom-web-prod',environmentId:'env-prod-001',version:'4.2.1',deploymentDate:'2026-09-11T10:00:00Z',releaseNotes:'بهبود UX صفحه پرداخت',requiredDeployments:[{projectId:'proj-ecom-api',deploymentId:'dep-ecom-api-prod'}],status:'active'},{id:'dep-ecom-web-stage',environmentId:'env-stage-002',version:'4.3.0-rc.1',deploymentDate:'2026-09-13T11:30:00Z',releaseNotes:'پیادهسازی Dark Mode',requiredDeployments:[],status:'active'}]},{id:'proj-ecom-mobile',title:'E-commerce Mobile App',description:'اپلیکیشن موبایل فروشگاه',type:'mobile',repositoryUrl:'https://git.rayanpardaz.ir/ecommerce/mobile-app',metadata:{framework:'Flutter 3.22',language:'Dart'},dependencies:['proj-ecom-api','proj-notification-svc'],deployments:[{id:'dep-ecom-mobile-prod',environmentId:'env-prod-001',version:'2.8.0',deploymentDate:'2026-09-08T08:00:00Z',releaseNotes:'انتشار در استورها',requiredDeployments:[{projectId:'proj-ecom-api',deploymentId:'dep-ecom-api-prod'}],status:'active'}]},{id:'proj-ecom-db',title:'E-commerce Database',description:'پایگاه داده اصلی',type:'database',repositoryUrl:'https://git.rayanpardaz.ir/ecommerce/db-migrations',metadata:{framework:'SQL Server 2022',language:'T-SQL'},dependencies:[],deployments:[{id:'dep-ecom-db-prod',environmentId:'env-prod-001',version:'5.1.0',deploymentDate:'2026-09-05T02:00:00Z',releaseNotes:'افزودن ایندکسهای جدید',requiredDeployments:[],status:'active'},{id:'dep-ecom-db-dr',environmentId:'env-dr-006',version:'5.1.0',deploymentDate:'2026-09-05T03:00:00Z',releaseNotes:'همگامسازی با Production',requiredDeployments:[],status:'active'}]},{id:'proj-ecom-search',title:'E-commerce Search Service',description:'سرویس جستجوی پیشرفته',type:'service',repositoryUrl:'https://git.rayanpardaz.ir/ecommerce/search-svc',metadata:{framework:'.NET 8',language:'C#'},dependencies:['proj-ecom-db'],deployments:[{id:'dep-ecom-search-prod',environmentId:'env-prod-001',version:'1.9.3',deploymentDate:'2026-09-09T13:00:00Z',releaseNotes:'بهبود الگوریتم جستجو',requiredDeployments:[{projectId:'proj-ecom-db',deploymentId:'dep-ecom-db-prod'}],status:'active'}]},{id:'proj-ecom-infra',title:'E-commerce Infrastructure',description:'زیرساخت Kubernetes',type:'infrastructure',repositoryUrl:'https://git.rayanpardaz.ir/ecommerce/infra',metadata:{framework:'Kubernetes 1.29',language:'YAML/Helm'},dependencies:[],deployments:[{id:'dep-ecom-infra-prod',environmentId:'env-prod-001',version:'2.1.0',deploymentDate:'2026-09-01T06:00:00Z',releaseNotes:'بهروزرسانی Helm Charts',requiredDeployments:[],status:'active'}]}]},{id:'prod-payment',title:'پلتفرم پرداخت و کیف پول پرداختیار',description:'زیرساخت مشترک پرداخت',metadata:{createdAt:'2026-01-05T08:00:00Z',owner:'تیم توسعه Payment',customFields:{industry:'FinTech',priority:'critical',shared:true}},projects:[{id:'proj-payment-gateway',title:'Payment Gateway Service',description:'درگاه پرداخت یکپارچه',type:'service',repositoryUrl:'https://git.rayanpardaz.ir/payment/gateway',metadata:{framework:'.NET 8',language:'C#'},dependencies:['proj-payment-db'],deployments:[{id:'dep-payment-prod',environmentId:'env-prod-001',version:'5.3.0',deploymentDate:'2026-09-09T12:00:00Z',releaseNotes:'پشتیبانی از درگاههای جدید',requiredDeployments:[{projectId:'proj-payment-db',deploymentId:'dep-payment-db-prod'}],status:'active'},{id:'dep-payment-stage',environmentId:'env-stage-002',version:'5.4.0-rc.1',deploymentDate:'2026-09-14T10:00:00Z',releaseNotes:'تست یکپارچگی با شاپرک',requiredDeployments:[],status:'active'},{id:'dep-payment-qa',environmentId:'env-qa-004',version:'5.4.0-beta.2',deploymentDate:'2026-09-15T15:00:00Z',releaseNotes:'تستهای خودکار امنیتی',requiredDeployments:[],status:'active'}]},{id:'proj-payment-wallet',title:'Digital Wallet Service',description:'سرویس کیف پول الکترونیک',type:'service',repositoryUrl:'https://git.rayanpardaz.ir/payment/wallet',metadata:{framework:'.NET 8',language:'C#'},dependencies:['proj-payment-gateway','proj-payment-db'],deployments:[{id:'dep-payment-wallet-prod',environmentId:'env-prod-001',version:'3.1.2',deploymentDate:'2026-09-11T11:00:00Z',releaseNotes:'افزودن قابلیت انتقال بین کیف پول',requiredDeployments:[{projectId:'proj-payment-gateway',deploymentId:'dep-payment-prod'}],status:'active'}]},{id:'proj-payment-db',title:'Payment Database',description:'پایگاه داده تراکنشهای مالی',type:'database',repositoryUrl:'https://git.rayanpardaz.ir/payment/db',metadata:{framework:'Oracle 19c RAC',language:'PL/SQL'},dependencies:[],deployments:[{id:'dep-payment-db-prod',environmentId:'env-prod-001',version:'6.0.0',deploymentDate:'2026-09-01T02:00:00Z',releaseNotes:'ارتقای ساختار تراکنشها',requiredDeployments:[],status:'active'},{id:'dep-payment-db-dr',environmentId:'env-dr-006',version:'6.0.0',deploymentDate:'2026-09-01T03:00:00Z',releaseNotes:'همگامسازی با Production',requiredDeployments:[],status:'active'}]},{id:'proj-payment-web',title:'Payment Management Portal',description:'پرتال مدیریت تراکنشها',type:'frontend',repositoryUrl:'https://git.rayanpardaz.ir/payment/portal',metadata:{framework:'Angular 18',language:'TypeScript'},dependencies:['proj-payment-gateway'],deployments:[{id:'dep-payment-web-prod',environmentId:'env-prod-001',version:'2.8.1',deploymentDate:'2026-09-12T10:00:00Z',releaseNotes:'داشبورد تحلیل تراکنشها',requiredDeployments:[{projectId:'proj-payment-gateway',deploymentId:'dep-payment-prod'}],status:'active'}]},{id:'proj-auth-svc',title:'Authentication & Authorization Service',description:'سرویس مشترک احراز هویت',type:'service',repositoryUrl:'https://git.rayanpardaz.ir/shared/auth-svc',metadata:{framework:'.NET 8',language:'C#'},dependencies:['proj-payment-db'],deployments:[{id:'dep-auth-prod',environmentId:'env-prod-001',version:'4.1.0',deploymentDate:'2026-09-08T06:00:00Z',releaseNotes:'افزودن MFA',requiredDeployments:[],status:'active'},{id:'dep-auth-stage',environmentId:'env-stage-002',version:'4.2.0-rc.1',deploymentDate:'2026-09-14T08:00:00Z',releaseNotes:'تست SSO با Azure AD',requiredDeployments:[],status:'active'}]},{id:'proj-notification-svc',title:'Notification Service',description:'سرویس مشترک ارسال اعلان',type:'service',repositoryUrl:'https://git.rayanpardaz.ir/shared/notification-svc',metadata:{framework:'.NET 8',language:'C#'},dependencies:[],deployments:[{id:'dep-notif-prod',environmentId:'env-prod-001',version:'2.5.0',deploymentDate:'2026-09-07T07:00:00Z',releaseNotes:'افزودن کانال WhatsApp',requiredDeployments:[],status:'active'}]},{id:'proj-inventory-svc',title:'Inventory Management Service',description:'سرویس مدیریت موجودی انبار',type:'service',repositoryUrl:'https://git.rayanpardaz.ir/shared/inventory-svc',metadata:{framework:'.NET 8',language:'C#'},dependencies:['proj-payment-db'],deployments:[{id:'dep-inventory-prod',environmentId:'env-prod-001',version:'2.0.3',deploymentDate:'2026-09-06T10:00:00Z',releaseNotes:'همگامسازی Real-time',requiredDeployments:[],status:'active'}]}]}]}};
|
||
const VALID_PROJECT_TYPES=[{value:'backend',label:'بکاند (Backend)',icon:'🖥️'},{value:'frontend',label:'فرانتاند (Frontend)',icon:'🌐'},{value:'service',label:'میکروسرویس (Service)',icon:'⚙️'},{value:'database',label:'پایگاه داده (Database)',icon:'🗄️'},{value:'mobile',label:'موبایل (Mobile)',icon:'📱'},{value:'infrastructure',label:'زیرساخت (Infrastructure)',icon:'🏗️'}];
|
||
function validateAppDataStructure(d){return d&&typeof d==='object'&&Array.isArray(d.environments)&&Array.isArray(d.products)}
|
||
function populateProjectTypeDropdowns(){['newProjectType','editProjType'].forEach(id=>{const s=document.getElementById(id);if(!s)return;const cv=s.value;s.innerHTML='<option value="">انتخاب کنید...</option>'+VALID_PROJECT_TYPES.map(t=>`<option value="${t.value}">${t.icon} ${t.label}</option>`).join('');if(cv)s.value=cv})}
|
||
function showToast(msg,type='info',dur=3000){const c=document.getElementById('toastContainer');const t=document.createElement('div');t.className=`toast ${type}`;const ic={success:'✅',error:'❌',warning:'⚠️',info:'ℹ️'};t.innerHTML=`<span>${ic[type]||'ℹ️'}</span><span>${msg}</span>`;c.appendChild(t);setTimeout(()=>{t.style.opacity='0';setTimeout(()=>t.remove(),300)},dur)}
|
||
function showD3Tooltip(e,text){let tt=document.getElementById('d3Tooltip');if(!tt)return;tt.textContent=text;tt.classList.add('visible');tt.style.left=(e.clientX+12)+'px';tt.style.top=(e.clientY+12)+'px'}
|
||
function hideD3Tooltip(){const tt=document.getElementById('d3Tooltip');if(tt)tt.classList.remove('visible')}
|
||
function refreshUI(){ToolbarManager.updateStats();BoardRenderer.render()}
|
||
/* ==================== MODAL MANAGER ==================== */
|
||
const ModalManager={stack:[],open(id){const m=document.getElementById(id);if(!m)return;this.stack=this.stack.filter(x=>x!==id);this.stack.push(id);this.stack.forEach((mid,i)=>{const el=document.getElementById(mid);if(!el)return;if(mid===id)el.classList.add('active');el.style.zIndex=1000+i+1})},close(id){const m=document.getElementById(id);if(!m)return;m.classList.remove('active');m.style.zIndex='';this.stack=this.stack.filter(x=>x!==id);this.stack.forEach((mid,i)=>{const el=document.getElementById(mid);if(el)el.style.zIndex=1000+i+1})},closeTop(){if(this.stack.length>0)this.close(this.stack[this.stack.length-1])},init(){document.querySelectorAll('[data-close-modal]').forEach(btn=>{btn.addEventListener('click',e=>{e.stopPropagation();const m=e.target.closest('.modal-overlay');if(m&&m.id)this.close(m.id)})});document.querySelectorAll('.modal-overlay').forEach(ov=>{ov.addEventListener('click',e=>{if(e.target===ov&&ov.id)this.close(ov.id)})});document.addEventListener('keydown',e=>{if(e.key==='Escape'){e.preventDefault();this.closeTop()}})}};
|
||
/* ==================== TOOLBAR MANAGER ==================== */
|
||
const ToolbarManager={currentFilters:{query:'',typeFilter:'',envFilter:''},init(){this.populateEnvironmentFilter();this.bindEvents();this.updateStats()},populateEnvironmentFilter(){const s=document.getElementById('filterEnvironment');if(!s)return;s.innerHTML='<option value="">همه محیطها</option>'+projectData.appData.environments.map(e=>`<option value="${e.id}">${e.name}</option>`).join('')},bindEvents(){const si=document.getElementById('toolbarSearch'),ft=document.getElementById('filterType'),fe=document.getElementById('filterEnvironment');if(si)si.addEventListener('input',()=>{this.applyFilters();refreshUI()});if(ft)ft.addEventListener('change',()=>{this.applyFilters();refreshUI()});if(fe)fe.addEventListener('change',()=>{this.applyFilters();refreshUI()})},applyFilters(){this.currentFilters={query:(document.getElementById('toolbarSearch')?.value||'').trim().toLowerCase(),typeFilter:document.getElementById('filterType')?.value||'',envFilter:document.getElementById('filterEnvironment')?.value||''};this.updateStats()},updateStats(){const ps=projectData.appData.products||[];let tp=0,td=0;ps.forEach(p=>{(p.projects||[]).forEach(pr=>{tp++;td+=(pr.dependencies||[]).length})});const s=(id,v)=>{const el=document.getElementById(id);if(el)el.textContent=v};s('statTotal',ps.length+tp);s('statProjects',ps.length);s('statSubProjects',tp);s('statDependencies',td)},shouldShowProduct(product){const f=this.currentFilters;if(!f.query&&!f.typeFilter&&!f.envFilter)return true;return(product.projects||[]).some(pr=>{const mq=!f.query||pr.title.toLowerCase().includes(f.query)||product.title.toLowerCase().includes(f.query);const mt=!f.typeFilter||pr.type===f.typeFilter;const me=!f.envFilter||(pr.deployments||[]).filter(d=>d.status==='active').some(d=>d.environmentId===f.envFilter);return mq&&mt&&me})}};
|
||
/* ==================== HISTORY MANAGER ==================== */
|
||
const HistoryManager={undoStack:[],redoStack:[],maxSize:50,isUndoRedoInProgress:false,pushState(){if(this.isUndoRedoInProgress)return;this.undoStack.push(JSON.parse(JSON.stringify(projectData.appData)));if(this.undoStack.length>this.maxSize)this.undoStack.shift();this.redoStack=[];this.updateButtons()},undo(){if(this.undoStack.length===0){showToast('هیچ تغییری نیست','warning');return}this.redoStack.push(JSON.parse(JSON.stringify(projectData.appData)));this.isUndoRedoInProgress=true;projectData.appData=this.undoStack.pop();this.isUndoRedoInProgress=false;this.updateUI();this.updateButtons();showToast('↩️ بازگشت','success')},redo(){if(this.redoStack.length===0){showToast('هیچ تغییری نیست','warning');return}this.undoStack.push(JSON.parse(JSON.stringify(projectData.appData)));this.isUndoRedoInProgress=true;projectData.appData=this.redoStack.pop();this.isUndoRedoInProgress=false;this.updateUI();this.updateButtons();showToast('↪️ باز انجام','success')},updateUI(){refreshUI();if(ProductManager.currentDetailProductId)ProductManager.showDetail(ProductManager.currentDetailProductId)},updateButtons(){const u=document.getElementById('undoBtn'),r=document.getElementById('redoBtn');if(u)u.disabled=this.undoStack.length===0;if(r)r.disabled=this.redoStack.length===0},init(){this.pushState();document.getElementById('undoBtn').addEventListener('click',()=>this.undo());document.getElementById('redoBtn').addEventListener('click',()=>this.redo());document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='z'&&!e.shiftKey){e.preventDefault();this.undo()}if((e.ctrlKey||e.metaKey)&&(e.key==='y'||(e.key==='z'&&e.shiftKey))){e.preventDefault();this.redo()}})}};
|
||
/* ==================== SECTION COLLAPSE ==================== */
|
||
const SectionCollapseManager={storageKey:'boardSectionCollapseState',getState(){try{const s=localStorage.getItem(this.storageKey);return s?JSON.parse(s):{dashboard:false,products:false,environments:false}}catch{return{dashboard:false,products:false,environments:false}}},saveState(s){try{localStorage.setItem(this.storageKey,JSON.stringify(s))}catch(e){}},toggle(id){const s=this.getState();s[id]=!s[id];this.saveState(s);const sec=document.getElementById(`section-${id}`);if(sec)sec.classList.toggle('collapsed',s[id]);return s[id]},init(){const s=this.getState();Object.keys(s).forEach(id=>{const sec=document.getElementById(`section-${id}`);if(sec&&s[id])sec.classList.add('collapsed')});document.getElementById('boardArea').addEventListener('click',e=>{const ht=e.target.closest('.board-header-title');if(ht){const sec=ht.closest('.board-section');if(sec){this.toggle(sec.id.replace('section-',''))}}})}};
|
||
/* ==================== DASHBOARD SECTION ==================== */
|
||
const DashboardSectionRenderer={getSectionHTML(){const d=this.getData();let h=`<div class="board-section" id="section-dashboard"><div class="board-header"><div class="board-header-title"><span class="collapse-icon">▼</span>📊 داشبورد مدیریتی</div><div class="board-header-actions"><button class="btn btn-accent btn-sm" onclick="DashboardManager.open()">📊 مشاهده کامل</button></div></div><div class="section-content">`;h+='<div class="dashboard-kpi-row" style="margin-bottom:16px">';h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">📦</div><div class="dashboard-kpi-value">${d.products}</div><div class="dashboard-kpi-label">محصول</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🌿</div><div class="dashboard-kpi-value">${d.projects}</div><div class="dashboard-kpi-label">پروژه</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🔗</div><div class="dashboard-kpi-value">${d.deps}</div><div class="dashboard-kpi-label">وابستگی</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🚀</div><div class="dashboard-kpi-value">${d.deploys}</div><div class="dashboard-kpi-label">استقرار</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🌍</div><div class="dashboard-kpi-value">${d.environments}</div><div class="dashboard-kpi-label">محیط</div></div>`;h+='</div></div></div>';return h},getData(){const ps=projectData.appData.products||[];let tp=0,td=0,tdp=0;ps.forEach(prod=>{(prod.projects||[]).forEach(pr=>{tp++;td+=(pr.dependencies||[]).length;tdp+=(pr.deployments||[]).filter(d=>d.status==='active').length})});return{products:ps.length,projects:tp,deps:td,deploys:tdp,environments:projectData.appData.environments.length}}};
|
||
/* ==================== ENVIRONMENT RENDERER ==================== */
|
||
const EnvironmentRenderer={getSectionHTML(){const envs=projectData.appData.environments||[];let h=`<div class="board-section" id="section-environments"><div class="board-header"><div class="board-header-title"><span class="collapse-icon">▼</span>🌍 محیطهای عملیاتی (${envs.length})</div><div class="board-header-actions"><button class="btn btn-accent" onclick="EnvironmentManager.openAddModal()">➕ محیط جدید</button></div></div><div class="section-content">`;if(envs.length===0){h+='<div class="board-empty"><div class="board-empty-icon">🌍</div><h2>هیچ محیطی نیست</h2></div>'}else{h+='<div class="env-card-grid">';envs.forEach(env=>{h+=`<div class="env-card-item"><div class="env-card-preview" style="background-color:${env.backgroundColor||'#38a169'};color:${env.textColor||'#fff'}">${env.name}</div><div class="env-card-body"><div class="env-card-name">${env.name}</div><div class="env-card-desc">${env.description||''}</div></div><div class="env-card-footer"><button class="btn btn-ghost btn-sm" onclick="EnvironmentManager.openEditModal('${env.id}')">✏️ ویرایش</button><button class="btn btn-danger btn-sm" onclick="EnvironmentManager.remove('${env.id}')">🗑️ حذف</button></div></div>`});h+='</div>'}h+='</div></div>';return h}};
|
||
/* ==================== BOARD RENDERER ==================== */
|
||
const BoardRenderer={render(){const board=document.getElementById('boardArea');const products=projectData.appData.products||[];const fp=products.filter(p=>ToolbarManager.shouldShowProduct(p));let h='';h+=DashboardSectionRenderer.getSectionHTML();h+=`<div class="board-section" id="section-products"><div class="board-header"><div class="board-header-title"><span class="collapse-icon">▼</span>📦 محصولات (${fp.length} از ${products.length})</div><div class="board-header-actions"><button class="btn btn-accent" onclick="ProductManager.openAddProductModal()">➕ محصول جدید</button></div></div><div class="section-content">`;if(products.length===0){h+='<div class="board-empty"><div class="board-empty-icon">📦</div><h2>هیچ محصولی نیست</h2></div>'}else if(fp.length===0){h+='<div class="no-results">🔍 نتیجهای یافت نشد</div>'}else{h+='<div class="projects-grid">';fp.forEach(prod=>{const projects=prod.projects||[];const tc={};projects.forEach(pr=>{tc[pr.type]=(tc[pr.type]||0)+1});const td=projects.reduce((s,pr)=>s+(pr.dependencies||[]).length,0);const tdp=projects.reduce((s,pr)=>s+(pr.deployments||[]).filter(d=>d.status==='active').length,0);h+=`<div class="project-card"><div class="project-card-header"><div class="project-card-title-row"><span style="font-size:22px">📦</span><div class="project-card-title">${prod.title}</div></div><div class="project-card-desc">${prod.description||''}</div></div><div class="project-card-body"><div class="project-stat-row"><span>🌿 پروژهها:</span><span class="project-stat-value">${projects.length}</span></div><div class="project-stat-row"><span>🔗 وابستگیها:</span><span class="project-stat-value">${td}</span></div><div class="project-stat-row"><span>🚀 استقرارها:</span><span class="project-stat-value">${tdp}</span></div>${Object.keys(tc).length>0?`<div class="project-type-badges">${Object.entries(tc).map(([type,count])=>{const ti=VALID_PROJECT_TYPES.find(t=>t.value===type)||{label:type};return`<span class="type-badge type-${type}">${ti.label}: ${count}</span>`}).join('')}</div>`:''}</div><div class="project-card-footer"><div class="card-actions"><button class="btn btn-accent btn-sm" onclick="ProductManager.showDetail('${prod.id}')">👁️ جزئیات</button></div><button class="btn btn-danger btn-xs" onclick="ProductManager.removeProduct('${prod.id}')">🗑️</button></div></div>`});h+='</div>'}h+='</div></div>';h+=EnvironmentRenderer.getSectionHTML();board.innerHTML=h;const st=SectionCollapseManager.getState();Object.keys(st).forEach(id=>{const sec=document.getElementById(`section-${id}`);if(sec&&st[id])sec.classList.add('collapsed')})}};
|
||
/* ==================== ACTION REGISTRY ==================== */
|
||
const ActionRegistry={openProductSettings(){openSettingsModal()},saveProduct(){projectData.metadata.updatedAt=new Date().toISOString();const js=JSON.stringify(projectData.appData,null,2);const b=new Blob([js],{type:'application/json;charset=utf-8'});const u=URL.createObjectURL(b);const a=document.createElement('a');a.href=u;a.download=`${projectData.projectName}_v${projectData.version}_${new Date().toISOString().replace(/[:.]/g,'-').slice(0,19)}.json`;document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(u);showToast('💾 ذخیره شد','success')},loadProduct(){const inp=document.createElement('input');inp.type='file';inp.accept='.json';inp.addEventListener('change',e=>{const f=e.target.files[0];if(!f)return;const r=new FileReader();r.onload=ev=>{try{const ld=JSON.parse(ev.target.result);if(!validateAppDataStructure(ld)){showToast('❌ ساختار نامعتبر','error');return}HistoryManager.pushState();projectData.appData=ld;updateProjectUI();renderToolGroups();ToolbarManager.populateEnvironmentFilter();refreshUI();showToast('📂 بارگذاری شد','success')}catch(err){showToast('❌ خطا: '+err.message,'error')}};r.readAsText(f)});inp.click()},clearAll(){if(!confirm('⚠️ پاکسازی کامل؟'))return;HistoryManager.pushState();projectData.appData={environments:[],products:[]};ToolbarManager.populateEnvironmentFilter();refreshUI();showToast('🗑️ پاکسازی شد','success')},showGalaxyChart(){GalaxyChartManager.open()},showExecutiveDashboard(){DashboardManager.open()},showDependencyAnalysis(){DependencyAnalysisManager.open()},showDeploymentAnalysis(){DeploymentAnalysisManager.open()},showVersionAnalysis(){VersionAnalysisManager.open()},showRiskAnalysis(){RiskAnalysisManager.open()},defaultAction(tool){showToast(`ابزار: ${tool.title}`,'info')}};
|
||
/* ==================== WEBHOOK MANAGER ==================== */
|
||
const WebhookManager={getConfig(){return projectData.webhook},setConfig(c){projectData.webhook={...projectData.webhook,...c};this.updateStatusUI()},isValid(){const c=this.getConfig();return c.enabled&&c.url&&c.clientId&&/^https?:\/\//i.test(c.url)},updateStatusUI(){const c=this.getConfig();const d=document.getElementById('webhookStatusDot'),t=document.getElementById('webhookStatusText');if(!d||!t)return;d.className='webhook-status-dot';if(!c.enabled)t.textContent='وضعیت: غیرفعال';else if(!c.url||!c.clientId){d.classList.add('pending');t.textContent='وضعیت: ناقص'}else{d.classList.add('success');t.textContent='وضعیت: آماده'}},async sendEvent(en,payload={}){if(!this.isValid())return;const c=this.getConfig();try{await fetch(c.url,{method:'POST',headers:{'Content-Type':'application/json','X-Client-Id':c.clientId,'X-Event-Name':en},body:JSON.stringify({event:en,timestamp:new Date().toISOString(),projectId:projectData.projectId,data:payload})})}catch(e){}}};
|
||
/* ==================== ENVIRONMENT MANAGER ==================== */
|
||
const EnvironmentManager={currentEditEnvId:null,openAddModal(){document.getElementById('newEnvName').value='';document.getElementById('newEnvDesc').value='';document.getElementById('newEnvBgColor').value='#38a169';document.getElementById('newEnvTextColor').value='#ffffff';ModalManager.open('envManagerModal')},saveNewEnv(){const n=document.getElementById('newEnvName').value.trim();if(!n){showToast('نام الزامی است','error');return}if(projectData.appData.environments.some(e=>e.name.toLowerCase()===n.toLowerCase())){showToast('تکراری است','warning');return}HistoryManager.pushState();projectData.appData.environments.push({id:`env-${Date.now()}`,name:n,description:document.getElementById('newEnvDesc').value.trim()||'',backgroundColor:document.getElementById('newEnvBgColor').value,textColor:document.getElementById('newEnvTextColor').value});ModalManager.close('envManagerModal');ToolbarManager.populateEnvironmentFilter();refreshUI();showToast(`محیط "${n}" اضافه شد`,'success')},openEditModal(envId){const env=projectData.appData.environments.find(e=>e.id===envId);if(!env)return;this.currentEditEnvId=envId;document.getElementById('editEnvId').value=env.id;document.getElementById('editEnvName').value=env.name;document.getElementById('editEnvDesc').value=env.description||'';document.getElementById('editEnvBgColor').value=env.backgroundColor||'#38a169';document.getElementById('editEnvTextColor').value=env.textColor||'#ffffff';this.updateEditPreview();ModalManager.open('envEditModal')},updateEditPreview(){const p=document.getElementById('editEnvPreview');p.style.backgroundColor=document.getElementById('editEnvBgColor').value;p.style.color=document.getElementById('editEnvTextColor').value},saveEdit(){const env=projectData.appData.environments.find(e=>e.id===this.currentEditEnvId);if(!env)return;const nn=document.getElementById('editEnvName').value.trim();if(!nn){showToast('نام الزامی است','error');return}HistoryManager.pushState();env.name=nn;env.description=document.getElementById('editEnvDesc').value.trim();env.backgroundColor=document.getElementById('editEnvBgColor').value;env.textColor=document.getElementById('editEnvTextColor').value;ModalManager.close('envEditModal');ToolbarManager.populateEnvironmentFilter();refreshUI();showToast(`محیط "${env.name}" ویرایش شد`,'success')},remove(id){const env=projectData.appData.environments.find(e=>e.id===id);if(!env)return;if(!confirm(`حذف "${env.name}"؟`))return;HistoryManager.pushState();projectData.appData.environments=projectData.appData.environments.filter(e=>e.id!==id);ToolbarManager.populateEnvironmentFilter();refreshUI();showToast(`"${env.name}" حذف شد`,'warning')}};
|
||
/* ==================== PRODUCT MANAGER ==================== */
|
||
const ProductManager={currentDetailProductId:null,currentEditProjectId:null,currentDependencyProjectId:null,currentDependencyProjId:null,currentDeploymentProjId:null,currentDeploymentMode:null,currentDeploymentEnvId:null,currentEditDeploymentId:null,getTypeInfo(type){return VALID_PROJECT_TYPES.find(t=>t.value===type)||{value:type,label:type,icon:'❓'}},getEnvironmentName(envId){const e=projectData.appData.environments.find(x=>x.id===envId);return e?e.name:envId},getEnvironmentColors(envId){const e=projectData.appData.environments.find(x=>x.id===envId);return{backgroundColor:e?.backgroundColor||'#38a169',textColor:e?.textColor||'#ffffff'}},findProjectById(pid){for(const prod of projectData.appData.products){const pr=prod.projects.find(p=>p.id===pid);if(pr)return pr}return null},getActiveDeployments(proj){return(proj.deployments||[]).filter(d=>d.status==='active')},getActiveDeploymentInEnv(proj,envId){return this.getActiveDeployments(proj).find(d=>d.environmentId===envId)||null},getEnvironmentsWithoutDeployment(proj){const allEnvIds=projectData.appData.environments.map(e=>e.id);const deployedEnvIds=this.getActiveDeployments(proj).map(d=>d.environmentId);return allEnvIds.filter(id=>!deployedEnvIds.includes(id))},showDetail(productId){const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;this.currentDetailProductId=productId;document.getElementById('projectDetailTitle').innerHTML=`<span>📦</span><span>${prod.title}</span>`;const projects=prod.projects||[];const totalDeploys=projects.reduce((s,pr)=>s+this.getActiveDeployments(pr).length,0);let html=`<div class="detail-section"><div class="detail-section-header"><span>ℹ️ اطلاعات محصول</span><div class="detail-section-header-actions"><button class="btn btn-accent btn-sm" onclick="ProductManager.openProductEditModal()">✏️ ویرایش</button></div></div><div class="detail-section-body"><div class="detail-info-grid"><div class="detail-info-item"><span class="detail-info-label">عنوان</span><span class="detail-info-value">${prod.title}</span></div><div class="detail-info-item"><span class="detail-info-label">توضیحات</span><span class="detail-info-value">${prod.description||'—'}</span></div><div class="detail-info-item"><span class="detail-info-label">تاریخ ایجاد</span><span class="detail-info-value">${prod.metadata?.createdAt?new Date(prod.metadata.createdAt).toLocaleDateString('fa-IR'):'—'}</span></div><div class="detail-info-item"><span class="detail-info-label">مالک</span><span class="detail-info-value">${prod.metadata?.owner||'—'}</span></div><div class="detail-info-item"><span class="detail-info-label">تعداد پروژه</span><span class="detail-info-value">${projects.length}</span></div><div class="detail-info-item"><span class="detail-info-label">استقرارهای فعال</span><span class="detail-info-value">${totalDeploys}</span></div></div></div></div><div class="detail-section"><div class="detail-section-header"><span>🌿 پروژهها (${projects.length})</span><div class="detail-section-header-actions"><button class="btn btn-accent btn-sm" onclick="ProductManager.openAddProjectModal()">➕ افزودن</button></div></div><div class="detail-section-body">`;if(projects.length===0){html+='<div class="empty-subprojects">🌿 هیچ پروژهای نیست</div>'}else{html+='<div class="subproject-list">';projects.forEach(pr=>{const ti=this.getTypeInfo(pr.type);const deps=pr.dependencies||[];const deploys=this.getActiveDeployments(pr);const emptyEnvs=this.getEnvironmentsWithoutDeployment(pr);const canAdd=emptyEnvs.length>0;html+=`<div class="subproject-card"><div class="subproject-header"><div class="subproject-title"><span>${ti.icon}</span><span>${pr.title}</span><span class="type-badge type-${pr.type}">${ti.label}</span></div><div class="subproject-actions"><button class="btn btn-ghost btn-sm" onclick="ProductManager.openDependencyManager('${prod.id}','${pr.id}')">🔗 وابستگیها</button><button class="btn btn-ghost btn-sm" onclick="ProductManager.openProjectEditModal('${prod.id}','${pr.id}')">✏️</button><button class="btn btn-danger btn-sm" onclick="ProductManager.removeProject('${prod.id}','${pr.id}')">🗑️</button></div></div><div class="subproject-meta">${pr.description||''}</div><div class="detail-info-grid" style="margin-bottom:8px">${pr.metadata?.framework?`<div class="detail-info-item"><span class="detail-info-label">فریمورک</span><span class="detail-info-value">${pr.metadata.framework}</span></div>`:''}${pr.metadata?.language?`<div class="detail-info-item"><span class="detail-info-label">زبان</span><span class="detail-info-value">${pr.metadata.language}</span></div>`:''}${deps.length>0?`<div class="detail-info-item"><span class="detail-info-label">وابستگیها (${deps.length})</span><span class="detail-info-value">${deps.map(depId=>{const dp=this.findProjectById(depId);return dp?dp.title:depId}).join('، ')}</span></div>`:''}</div>`;html+=`<div class="deployment-section"><div class="deployment-header"><span class="deployment-header-title">🚀 استقرارهای فعال (${deploys.length})</span>${canAdd?`<button class="btn btn-accent btn-xs" onclick="ProductManager.openAddDeploymentModal('${prod.id}','${pr.id}')">➕ افزودن</button>`:'<span style="font-size:10px;color:var(--text-muted)">✓ کامل</span>'}</div>`;if(deploys.length===0){html+='<div class="empty-deployments">هنوز استقرار ندارد</div>'}else{html+='<div class="deployment-list">';deploys.forEach(d=>{const en=this.getEnvironmentName(d.environmentId);const cl=this.getEnvironmentColors(d.environmentId);const fd=d.deploymentDate?new Date(d.deploymentDate).toLocaleString('fa-IR'):'—';html+=`<div class="deployment-item"><div class="deployment-item-info"><span class="deployment-env-tag" style="background-color:${cl.backgroundColor};color:${cl.textColor}">${en}</span><span class="deployment-version">v${d.version}</span><span class="deployment-date">📅 ${fd}</span></div><div class="deployment-actions"><button class="btn btn-ghost btn-xs" onclick="ProductManager.openEditDeploymentModal('${prod.id}','${pr.id}','${d.id}')">✏️</button><button class="btn btn-danger btn-xs" onclick="ProductManager.removeDeployment('${prod.id}','${pr.id}','${d.id}')">🗑️</button></div>`;if(d.releaseNotes){html+=`<div class="deployment-notes">📝 ${d.releaseNotes}</div>`}if(d.requiredDeployments&&d.requiredDeployments.length>0){html+=`<div class="deployment-req">🔗 نیازمندیها: ${d.requiredDeployments.map(rd=>{const rp=this.findProjectById(rd.projectId);const rdProj=rp?this.findProjectById(rd.projectId):null;let depVer='';if(rdProj){const rdDep=(rdProj.deployments||[]).find(x=>x.id===rd.deploymentId);if(rdDep)depVer=` (v${rdDep.version})`}return`${rp?rp.title:rd.projectId}${depVer}`}).join('، ')}</div>`}html+=`</div>`});html+='</div>'}html+=`</div></div>`});html+='</div>'}html+=`</div></div>`;document.getElementById('projectDetailBody').innerHTML=html;ModalManager.open('projectDetailModal')},openAddProductModal(){document.getElementById('newProductTitle').value='';document.getElementById('newProductDescription').value='';document.getElementById('newProductOwner').value='';document.getElementById('newProductCreatedAt').value=new Date().toLocaleDateString('fa-IR');ModalManager.open('addProductModal')},createNewProduct(){const title=document.getElementById('newProductTitle').value.trim();if(!title||title.length<3){showToast('عنوان الزامی است (حداقل ۳ کاراکتر)','error');return}if(projectData.appData.products.some(p=>p.title.toLowerCase()===title.toLowerCase())){showToast('تکراری است','warning');return}HistoryManager.pushState();projectData.appData.products.push({id:`prod-${Date.now()}`,title,description:document.getElementById('newProductDescription').value.trim()||'',metadata:{createdAt:new Date().toISOString(),owner:document.getElementById('newProductOwner').value.trim()||'',customFields:{}},projects:[]});ModalManager.close('addProductModal');refreshUI();showToast(`"${title}" ایجاد شد`,'success')},openDependencyManager(productId,projId){this.currentDependencyProjectId=productId;this.currentDependencyProjId=projId;this.renderCurrentDependencies();this.renderAllProjectsList();this.setupDependencySearch();ModalManager.open('dependencyManagerModal')},renderCurrentDependencies(){const prod=projectData.appData.products.find(p=>p.id===this.currentDependencyProjectId);if(!prod)return;const pr=prod.projects.find(p=>p.id===this.currentDependencyProjId);if(!pr)return;const c=document.getElementById('currentDependenciesList');const deps=pr.dependencies||[];if(deps.length===0){c.innerHTML='<div class="empty-dependencies">هیچ وابستگیای نیست</div>';return}c.innerHTML=deps.map((depId,index)=>{const depPr=this.findProjectById(depId);const projectName=depPr?depPr.title:depId;const typeInfo=depPr?this.getTypeInfo(depPr.type):null;return`<div class="dependency-item"><div class="dependency-info"><div><div class="dependency-project-name">${typeInfo?typeInfo.icon+' ':''}${projectName}</div></div></div><button class="btn btn-danger btn-xs" onclick="ProductManager.removeDependency(${index})">🗑️</button></div>`}).join('')},removeDependency(index){const prod=projectData.appData.products.find(p=>p.id===this.currentDependencyProjectId);if(!prod)return;const pr=prod.projects.find(p=>p.id===this.currentDependencyProjId);if(!pr)return;if(!confirm('حذف این وابستگی؟'))return;HistoryManager.pushState();pr.dependencies.splice(index,1);ModalManager.close('dependencyManagerModal');refreshUI();if(this.currentDetailProductId)this.showDetail(this.currentDetailProductId);showToast('وابستگی حذف شد','success')},renderAllProjectsList(){const rc=document.getElementById('dependencySearchResults');const cp=projectData.appData.products.find(p=>p.id===this.currentDependencyProjectId);if(!cp)return;const cpr=cp.projects.find(p=>p.id===this.currentDependencyProjId);if(!cpr)return;const allProjects=[];projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{if(pr.id!==cpr.id)allProjects.push({...pr,productName:prod.title})})});if(allProjects.length===0){rc.innerHTML='<div class="empty-dependencies">پروژه دیگری نیست</div>';return}rc.innerHTML=allProjects.map(pr=>{const isDep=(cpr.dependencies||[]).includes(pr.id);return`<div class="dependency-project-result"><button class="dependency-project-add-btn" onclick="ProductManager.addDependency('${pr.id}')" ${isDep?'disabled style="opacity:.5"':''}><span>${this.getTypeInfo(pr.type).icon}</span><span>${pr.title}</span><small style="color:var(--text-muted);font-weight:normal">(${pr.productName})</small>${isDep?'<span style="margin-right:auto;font-size:10px;color:var(--success)">✓ وابسته</span>':''}</button></div>`}).join('')},setupDependencySearch(){const si=document.getElementById('dependencySearch');si.value='';si.oninput=e=>{const q=e.target.value.trim().toLowerCase();if(q.length===0){this.renderAllProjectsList();return}const cp=projectData.appData.products.find(p=>p.id===this.currentDependencyProjectId);if(!cp)return;const cpr=cp.projects.find(p=>p.id===this.currentDependencyProjId);if(!cpr)return;const allProjects=[];projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{if(pr.id!==cpr.id)allProjects.push({...pr,productName:prod.title})})});const fl=allProjects.filter(pr=>pr.title.toLowerCase().includes(q)||pr.productName.toLowerCase().includes(q)||(pr.description||'').toLowerCase().includes(q));const rc=document.getElementById('dependencySearchResults');if(fl.length===0){rc.innerHTML='<div class="empty-dependencies">نتیجهای نیست</div>';return}rc.innerHTML=fl.map(pr=>{const isDep=(cpr.dependencies||[]).includes(pr.id);return`<div class="dependency-project-result"><button class="dependency-project-add-btn" onclick="ProductManager.addDependency('${pr.id}')" ${isDep?'disabled style="opacity:.5"':''}><span>${this.getTypeInfo(pr.type).icon}</span><span>${pr.title}</span><small style="color:var(--text-muted)">(${pr.productName})</small></button></div>`}).join('')}},addDependency(projectId){const prod=projectData.appData.products.find(p=>p.id===this.currentDependencyProjectId);if(!prod)return;const pr=prod.projects.find(p=>p.id===this.currentDependencyProjId);if(!pr)return;if((pr.dependencies||[]).includes(projectId)){showToast('تکراری است','warning');return}HistoryManager.pushState();if(!pr.dependencies)pr.dependencies=[];pr.dependencies.push(projectId);ModalManager.close('dependencyManagerModal');refreshUI();if(this.currentDetailProductId)this.showDetail(this.currentDetailProductId);showToast('وابستگی اضافه شد','success')},openProductEditModal(){const pid=this.currentDetailProductId;if(!pid)return;const prod=projectData.appData.products.find(p=>p.id===pid);if(!prod)return;document.getElementById('editProductId').value=prod.id;document.getElementById('editProductTitle').value=prod.title;document.getElementById('editProductDescription').value=prod.description||'';document.getElementById('editProductOwner').value=prod.metadata?.owner||'';document.getElementById('editProductCreatedAt').value=prod.metadata?.createdAt?new Date(prod.metadata.createdAt).toLocaleDateString('fa-IR'):'—';ModalManager.open('productEditModal')},saveProductEdit(){const pid=this.currentDetailProductId;if(!pid)return;const prod=projectData.appData.products.find(p=>p.id===pid);if(!prod)return;const nt=document.getElementById('editProductTitle').value.trim();if(!nt){showToast('عنوان الزامی است','error');return}HistoryManager.pushState();prod.title=nt;prod.description=document.getElementById('editProductDescription').value.trim();if(!prod.metadata)prod.metadata={};prod.metadata.owner=document.getElementById('editProductOwner').value.trim();prod.metadata.updatedAt=new Date().toISOString();ModalManager.close('productEditModal');refreshUI();this.showDetail(pid);showToast(`"${prod.title}" ویرایش شد`,'success')},openAddProjectModal(){if(!this.currentDetailProductId){showToast('ابتدا محصول انتخاب کنید','warning');return}document.getElementById('newProjectTitle').value='';document.getElementById('newProjectType').value='';document.getElementById('newProjectFramework').value='';document.getElementById('newProjectLanguage').value='';document.getElementById('newProjectRepo').value='';document.getElementById('newProjectDescription').value='';populateProjectTypeDropdowns();ModalManager.open('addProjectModal')},saveNewProject(){const pid=this.currentDetailProductId;if(!pid)return;const prod=projectData.appData.products.find(p=>p.id===pid);if(!prod)return;const title=document.getElementById('newProjectTitle').value.trim();const type=document.getElementById('newProjectType').value;if(!title||!type){showToast('عنوان و نوع الزامی است','error');return}HistoryManager.pushState();const np={id:`proj-${Date.now()}`,title,description:document.getElementById('newProjectDescription').value.trim()||'',type,repositoryUrl:document.getElementById('newProjectRepo').value.trim(),metadata:{createdAt:new Date().toISOString(),customFields:{}},dependencies:[],deployments:[]};const fw=document.getElementById('newProjectFramework').value.trim();if(fw)np.metadata.framework=fw;const lg=document.getElementById('newProjectLanguage').value.trim();if(lg)np.metadata.language=lg;prod.projects.push(np);ModalManager.close('addProjectModal');refreshUI();this.showDetail(pid);showToast(`"${title}" ایجاد شد`,'success')},openProjectEditModal(productId,projId){const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;this.currentEditProjectId=projId;populateProjectTypeDropdowns();document.getElementById('editProjId').value=pr.id;document.getElementById('editProjTitle').value=pr.title;document.getElementById('editProjType').value=pr.type;document.getElementById('editProjFramework').value=pr.metadata?.framework||'';document.getElementById('editProjLanguage').value=pr.metadata?.language||'';document.getElementById('editProjRepo').value=pr.repositoryUrl||'';document.getElementById('editProjDescription').value=pr.description||'';ModalManager.open('editProjectModal')},saveProjectEdit(){const pid=this.currentDetailProductId;const projId=this.currentEditProjectId;if(!pid||!projId)return;const prod=projectData.appData.products.find(p=>p.id===pid);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;const nt=document.getElementById('editProjTitle').value.trim();const nty=document.getElementById('editProjType').value;if(!nt||!nty){showToast('عنوان و نوع الزامی است','error');return}HistoryManager.pushState();pr.title=nt;pr.type=nty;pr.description=document.getElementById('editProjDescription').value.trim()||'';pr.repositoryUrl=document.getElementById('editProjRepo').value.trim();if(!pr.metadata)pr.metadata={};const fw=document.getElementById('editProjFramework').value.trim();if(fw)pr.metadata.framework=fw;else delete pr.metadata.framework;const lg=document.getElementById('editProjLanguage').value.trim();if(lg)pr.metadata.language=lg;else delete pr.metadata.language;pr.metadata.updatedAt=new Date().toISOString();ModalManager.close('editProjectModal');refreshUI();this.showDetail(pid);showToast(`"${pr.title}" ویرایش شد`,'success')},openAddDeploymentModal(productId,projId){if(!productId||!projId)return;const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;const emptyEnvs=this.getEnvironmentsWithoutDeployment(pr);if(emptyEnvs.length===0){showToast('همه محیطها استقرار دارند','warning');return}this.currentDeploymentProjId=projId;this.currentDeploymentMode='add';this.currentDeploymentEnvId=null;this.currentEditDeploymentId=null;document.getElementById('deploymentModalTitle').innerHTML='<span>🚀</span><span>افزودن نسخه استقرار</span>';const envGroup=document.getElementById('deploymentEnvGroup');const envs=projectData.appData.environments.filter(e=>emptyEnvs.includes(e.id));envGroup.innerHTML=`<label class="form-label">محیط استقرار <span class="required">*</span></label><select class="form-select" id="depEnvironment" onchange="ProductManager.onEnvChange()">${envs.map(e=>{const cl=this.getEnvironmentColors(e.id);return`<option value="${e.id}" style="background-color:${cl.backgroundColor};color:${cl.textColor}">${e.name} - ${e.description}</option>`}).join('')}</select>`;document.getElementById('depVersion').value='';document.getElementById('depDate').value='';document.getElementById('depReleaseNotes').value='';this.renderRequiredDeploymentsSection();ModalManager.open('deploymentModal')},openEditDeploymentModal(productId,projId,depId){if(!productId||!projId||!depId)return;const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;const dep=(pr.deployments||[]).find(d=>d.id===depId);if(!dep)return;this.currentDeploymentProjId=projId;this.currentDeploymentMode='edit';this.currentDeploymentEnvId=dep.environmentId;this.currentEditDeploymentId=depId;document.getElementById('deploymentModalTitle').innerHTML='<span>✏️</span><span>ویرایش نسخه استقرار</span>';const envGroup=document.getElementById('deploymentEnvGroup');const cl=this.getEnvironmentColors(dep.environmentId);envGroup.innerHTML=`<label class="form-label">محیط استقرار <span class="readonly-badge">فقط خواندنی</span></label><div class="env-read-only-tag" style="background-color:${cl.backgroundColor};color:${cl.textColor}">${this.getEnvironmentName(dep.environmentId)}</div>`;document.getElementById('depVersion').value=dep.version;let dv='';if(dep.deploymentDate){const d=new Date(dep.deploymentDate);const pad=n=>n.toString().padStart(2,'0');dv=`${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`}document.getElementById('depDate').value=dv;document.getElementById('depReleaseNotes').value=dep.releaseNotes||'';this.renderRequiredDeploymentsSection(dep);ModalManager.open('deploymentModal')},onEnvChange(){this.renderRequiredDeploymentsSection()},renderRequiredDeploymentsSection(existingDep){const section=document.getElementById('requiredDeploymentsSection');const envSelect=document.getElementById('depEnvironment');const envId=this.currentDeploymentMode==='edit'?this.currentDeploymentEnvId:(envSelect?envSelect.value:null);if(!envId){section.innerHTML='';return}const prod=projectData.appData.products.find(p=>p.id===this.currentDetailProductId);if(!prod)return;const pr=prod.projects.find(p=>p.id===this.currentDeploymentProjId);if(!pr)return;const deps=pr.dependencies||[];if(deps.length===0){section.innerHTML='';return}let h='<div class="form-group"><label class="form-label">🔗 استقرارهای مورد نیاز (به ازای هر وابستگی) <span class="required">*</span></label><div class="form-hint">برای هر وابستگی، یک استقرار از آن پروژه در محیط "'+this.getEnvironmentName(envId)+'" انتخاب کنید</div>';deps.forEach(depId=>{const depPr=this.findProjectById(depId);if(!depPr)return;const depDeployments=(depPr.deployments||[]).filter(d=>d.status==='active'&&d.environmentId===envId);const existingReq=existingDep?(existingDep.requiredDeployments||[]).find(r=>r.projectId===depId):null;h+=`<div class="req-deployment-box"><div class="req-deployment-title">📦 ${depPr.title}</div>`;if(depDeployments.length===0){h+=`<div style="color:var(--danger);font-size:11px">⚠️ هیچ استقرار فعالی در ${this.getEnvironmentName(envId)} ندارد. ابتدا یک استقرار برای این پروژه ثبت کنید.</div>`}else{h+=`<select class="req-deployment-select" data-dep-project="${depId}">${depDeployments.map(d=>`<option value="${d.id}" ${existingReq&&existingReq.deploymentId===d.id?'selected':''}>v${d.version} - ${d.deploymentDate?new Date(d.deploymentDate).toLocaleDateString('fa-IR'):''}</option>`).join('')}</select>`}h+='</div>'});h+='</div>';section.innerHTML=h},saveDeployment(){const pid=this.currentDetailProductId;const projId=this.currentDeploymentProjId;if(!pid||!projId){showToast('خطا: محصول یا پروژه مشخص نشده','error');return}const prod=projectData.appData.products.find(p=>p.id===pid);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;const version=document.getElementById('depVersion').value.trim();const date=document.getElementById('depDate').value;const releaseNotes=document.getElementById('depReleaseNotes').value.trim();if(!version){showToast('نسخه الزامی است','error');return}if(!date){showToast('تاریخ استقرار الزامی است','error');return}if(!releaseNotes){showToast('ریلیز نوت الزامی است','error');return}const deploymentDate=new Date(date).toISOString();HistoryManager.pushState();if(!pr.deployments)pr.deployments=[];const requiredDeployments=[];const reqSelects=document.querySelectorAll('.req-deployment-select');reqSelects.forEach(sel=>{const depProjectId=sel.dataset.depProject;const depDeploymentId=sel.value;if(depProjectId&&depDeploymentId){requiredDeployments.push({projectId:depProjectId,deploymentId:depDeploymentId})}});if(this.currentDeploymentMode==='add'){const envId=document.getElementById('depEnvironment').value;if(!envId){showToast('محیط را انتخاب کنید','error');return}const newDep={id:`dep-${Date.now()}`,environmentId:envId,version,deploymentDate,releaseNotes,requiredDeployments,status:'active'};pr.deployments.push(newDep);showToast(`v${version} در ${this.getEnvironmentName(envId)} استقرار یافت`,'success')}else{const depId=this.currentEditDeploymentId;const dep=pr.deployments.find(d=>d.id===depId);if(dep){dep.version=version;dep.deploymentDate=deploymentDate;dep.releaseNotes=releaseNotes;dep.requiredDeployments=requiredDeployments;showToast('استقرار ویرایش شد','success')}}ModalManager.close('deploymentModal');refreshUI();this.showDetail(pid)},removeDeployment(productId,projId,depId){if(!confirm('حذف این استقرار؟'))return;const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;const dep=(pr.deployments||[]).find(d=>d.id===depId);if(!dep)return;HistoryManager.pushState();dep.status='removed';refreshUI();this.showDetail(productId);showToast('استقرار حذف شد','warning')},removeProduct(productId){const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;if(!confirm(`حذف "${prod.title}"؟`))return;HistoryManager.pushState();projectData.appData.products=projectData.appData.products.filter(p=>p.id!==productId);refreshUI();ModalManager.close('projectDetailModal');showToast(`"${prod.title}" حذف شد`,'warning')},removeProject(productId,projId){const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;if(!confirm(`حذف "${pr.title}"؟`))return;HistoryManager.pushState();prod.projects=prod.projects.filter(p=>p.id!==projId);refreshUI();if(this.currentDetailProductId===productId)this.showDetail(productId);showToast(`"${pr.title}" حذف شد`,'warning')}};
|
||
/* ==================== GALAXY CHART ==================== */
|
||
const GalaxyChartManager={svg:null,zoomBehavior:null,NODE_COLORS:{product:'#c9a961',backend:'#4299e1',frontend:'#48bb78',service:'#ecc94b',database:'#ed64a6',mobile:'#9f7aea',infrastructure:'#ed8936'},open(){ModalManager.open('galaxyChartModal');setTimeout(()=>this.render(),150)},render(){const c=document.getElementById('galaxyChartContainer');c.innerHTML='';const w=c.clientWidth||900,h=c.clientHeight||600;const nodes=[],links=[];projectData.appData.products.forEach(prod=>{nodes.push({id:prod.id,label:prod.title,type:'product',radius:30,data:prod});prod.projects.forEach(pr=>{nodes.push({id:pr.id,label:pr.title,type:'project',projectType:pr.type,radius:16,data:pr});links.push({source:prod.id,target:pr.id,type:'ownership'});(pr.dependencies||[]).forEach(depId=>{links.push({source:pr.id,target:depId,type:'dependency'})})})});if(nodes.length===0){c.innerHTML='<div class="galaxy-empty"><div style="font-size:60px">🌌</div><div>هیچ دادهای نیست</div></div>';return}const svg=d3.select(c).append('svg').attr('width',w).attr('height',h).attr('id','galaxySvg');this.svg=svg;svg.append('rect').attr('width',w).attr('height',h).attr('fill','#0a0e27');for(let i=0;i<100;i++)svg.append('circle').attr('cx',Math.random()*w).attr('cy',Math.random()*h).attr('r',Math.random()+.3).attr('fill','#fff').attr('opacity',Math.random()*.5+.1);const defs=svg.append('defs');const gf=defs.append('filter').attr('id','galaxyGlow');gf.append('feGaussianBlur').attr('stdDeviation','3').attr('result','blur');const fm=gf.append('feMerge');fm.append('feMergeNode').attr('in','blur');fm.append('feMergeNode').attr('in','SourceGraphic');const zg=svg.append('g');this.zoomBehavior=d3.zoom().scaleExtent([.2,5]).on('zoom',e=>{zg.attr('transform',e.transform);document.getElementById('galaxyZoomIndicator').textContent=`🔍 ${Math.round(e.transform.k*100)}%`});svg.call(this.zoomBehavior);const sim=d3.forceSimulation(nodes).force('link',d3.forceLink(links).id(d=>d.id).distance(d=>d.type==='ownership'?100:160)).force('charge',d3.forceManyBody().strength(-300)).force('center',d3.forceCenter(w/2,h/2));const link=zg.append('g').selectAll('line').data(links).join('line').attr('stroke',d=>d.type==='dependency'?'#e0c789':'#4a5568').attr('stroke-width',d=>d.type==='dependency'?2:1).attr('stroke-opacity',.6).attr('stroke-dasharray',d=>d.type==='dependency'?'5,3':'none');const node=zg.append('g').selectAll('g').data(nodes).join('g').style('cursor','pointer').call(d3.drag().on('start',(e,d)=>{if(!e.active)sim.alphaTarget(.3).restart();d.fx=d.x;d.fy=d.y}).on('drag',(e,d)=>{d.fx=e.x;d.fy=e.y}).on('end',(e,d)=>{if(!e.active)sim.alphaTarget(0);d.fx=null;d.fy=null}));node.append('circle').attr('r',d=>d.radius).attr('fill',d=>d.type==='product'?this.NODE_COLORS.product:(this.NODE_COLORS[d.projectType]||'#718096')).attr('stroke','#fff').attr('stroke-width',1.5).attr('filter','url(#galaxyGlow)');node.append('text').attr('dy',d=>d.radius+14).attr('text-anchor','middle').attr('fill','#fff').attr('font-size','10px').attr('font-family','Vazirmatn').text(d=>d.label);node.on('click',(e,d)=>{const p=document.getElementById('galaxyInfoPanel');const ic=document.getElementById('galaxyInfoContent');let ih=`<h4>${d.label}</h4>`;if(d.type==='product'){ih+=`<div class="galaxy-info-row"><span class="galaxy-info-label">پروژهها</span><span class="galaxy-info-value">${(d.data.projects||[]).length}</span></div>`}else{ih+=`<div class="galaxy-info-row"><span class="galaxy-info-label">نوع</span><span class="galaxy-info-value">${this.getTypeLabel(d.projectType)}</span></div><div class="galaxy-info-row"><span class="galaxy-info-label">استقرارها</span><span class="galaxy-info-value">${ProductManager.getActiveDeployments(d.data).length}</span></div>`}ic.innerHTML=ih;p.classList.add('visible')});sim.on('tick',()=>{link.attr('x1',d=>d.source.x).attr('y1',d=>d.source.y).attr('x2',d=>d.target.x).attr('y2',d=>d.target.y);node.attr('transform',d=>`translate(${d.x},${d.y})`)});this.renderLegend();this.updateStats(nodes,links)},getTypeLabel(t){const i=VALID_PROJECT_TYPES.find(x=>x.value===t);return i?i.label.split(' ')[0]:t},renderLegend(){const l=document.getElementById('galaxyLegend');let h='<div class="galaxy-legend-title">🎨 راهنما</div>';h+=`<div class="galaxy-legend-item"><div class="galaxy-legend-color" style="background:${this.NODE_COLORS.product}"></div><span>📦 محصول</span></div>`;VALID_PROJECT_TYPES.forEach(t=>{h+=`<div class="galaxy-legend-item"><div class="galaxy-legend-color" style="background:${this.NODE_COLORS[t.value]}"></div><span>${t.icon} ${t.label.split(' ')[0]}</span></div>`});l.innerHTML=h},updateStats(nodes,links){const p=projectData.appData.products.length;const pr=projectData.appData.products.reduce((x,p)=>x+p.projects.length,0);const dp=links.filter(l=>l.type==='dependency').length;document.getElementById('galaxyStats').innerHTML=`<span>📦 ${p}</span><span>🌿 ${pr}</span><span>🔗 ${dp}</span>`}};
|
||
/* ==================== DASHBOARD MANAGER ==================== */
|
||
const DashboardManager={open(){ModalManager.open('executiveDashboardModal');setTimeout(()=>this.render(),150)},render(){const c=document.getElementById('dashboardContainer');if(!c)return;const d=DashboardSectionRenderer.getData();let h='<div class="dashboard-kpi-row">';h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">📦</div><div class="dashboard-kpi-value">${d.products}</div><div class="dashboard-kpi-label">محصول</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🌿</div><div class="dashboard-kpi-value">${d.projects}</div><div class="dashboard-kpi-label">پروژه</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🔗</div><div class="dashboard-kpi-value">${d.deps}</div><div class="dashboard-kpi-label">وابستگی</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🚀</div><div class="dashboard-kpi-value">${d.deploys}</div><div class="dashboard-kpi-label">استقرار</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🌍</div><div class="dashboard-kpi-value">${d.environments}</div><div class="dashboard-kpi-label">محیط</div></div>`;h+='</div>';c.innerHTML=h;document.getElementById('dashboardFooterStats').innerHTML=`<span>📦 محصولات: <span class="stat-highlight">${d.products}</span></span><span>🌿 پروژهها: <span class="stat-highlight">${d.projects}</span></span><span>🚀 استقرارها: <span class="stat-highlight">${d.deploys}</span></span>`;document.getElementById('dashboardRefresh').onclick=()=>this.render()}};
|
||
/* ==================== ANALYSIS MANAGERS ==================== */
|
||
const DependencyAnalysisManager={open(){ModalManager.open('dependencyAnalysisModal');setTimeout(()=>this.render(),150)},render(){const c=document.getElementById('dependencyAnalysisContainer');if(!c)return;let totalDeps=0,totalProjects=0,brokenDeps=0;const allProjects=[];projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{allProjects.push(pr);totalProjects++;(pr.dependencies||[]).forEach(depId=>{totalDeps++;if(!ProductManager.findProjectById(depId))brokenDeps++})})});const inDeps={};allProjects.forEach(pr=>{(pr.dependencies||[]).forEach(depId=>{if(ProductManager.findProjectById(depId))inDeps[depId]=(inDeps[depId]||0)+1})});const bottlenecks=Object.entries(inDeps).filter(([k,v])=>v>=2).length;let h='<div class="dashboard-kpi-row">';h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🔗</div><div class="dashboard-kpi-value">${totalDeps}</div><div class="dashboard-kpi-label">کل وابستگیها</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">📦</div><div class="dashboard-kpi-value">${totalProjects}</div><div class="dashboard-kpi-label">پروژهها</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">⚠️</div><div class="dashboard-kpi-value">${bottlenecks}</div><div class="dashboard-kpi-label">گلوگاه</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🔴</div><div class="dashboard-kpi-value">${brokenDeps}</div><div class="dashboard-kpi-label">شکسته</div></div>`;h+='</div>';c.innerHTML=h;document.getElementById('depAnalysisFooterStats').innerHTML=`<span>🔗 کل: <span class="stat-highlight">${totalDeps}</span></span>`;document.getElementById('depAnalysisRefresh').onclick=()=>this.render()}};
|
||
const DeploymentAnalysisManager={open(){ModalManager.open('deploymentAnalysisModal');setTimeout(()=>this.render(),150)},render(){const c=document.getElementById('deploymentAnalysisContainer');if(!c)return;let total=0,pwd=0,pwod=0,withNotes=0,withoutNotes=0;projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{const active=ProductManager.getActiveDeployments(pr);if(active.length>0)pwd++;else pwod++;active.forEach(d=>{total++;if(d.releaseNotes)withNotes++;else withoutNotes++})})});const coverage=(pwd+pwod)>0?Math.round(pwd/(pwd+pwod)*100):0;let h='<div class="dashboard-kpi-row">';h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🚀</div><div class="dashboard-kpi-value">${total}</div><div class="dashboard-kpi-label">کل استقرارها</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">✅</div><div class="dashboard-kpi-value">${pwd}</div><div class="dashboard-kpi-label">با استقرار</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">❌</div><div class="dashboard-kpi-value">${pwod}</div><div class="dashboard-kpi-label">بدون استقرار</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">📈</div><div class="dashboard-kpi-value">${coverage}%</div><div class="dashboard-kpi-label">پوشش</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">📝</div><div class="dashboard-kpi-value">${withNotes}</div><div class="dashboard-kpi-label">با ریلیز نوت</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">⚠️</div><div class="dashboard-kpi-value">${withoutNotes}</div><div class="dashboard-kpi-label">بدون ریلیز نوت</div></div>`;h+='</div>';c.innerHTML=h;document.getElementById('deployAnalysisFooterStats').innerHTML=`<span>🚀 کل: <span class="stat-highlight">${total}</span></span><span>📈 پوشش: <span class="stat-highlight">${coverage}%</span></span>`;document.getElementById('deployAnalysisRefresh').onclick=()=>this.render()}};
|
||
const VersionAnalysisManager={open(){ModalManager.open('versionAnalysisModal');setTimeout(()=>this.render(),150)},render(){const c=document.getElementById('versionAnalysisContainer');if(!c)return;let total=0;const unique=new Set();projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{ProductManager.getActiveDeployments(pr).forEach(d=>{total++;unique.add(d.version)})})});let h='<div class="dashboard-kpi-row">';h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🔢</div><div class="dashboard-kpi-value">${total}</div><div class="dashboard-kpi-label">کل نسخهها</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">📦</div><div class="dashboard-kpi-value">${unique.size}</div><div class="dashboard-kpi-label">یکتا</div></div>`;h+='</div>';c.innerHTML=h;document.getElementById('verAnalysisFooterStats').innerHTML=`<span>🔢 کل: <span class="stat-highlight">${total}</span></span><span>📦 یکتا: <span class="stat-highlight">${unique.size}</span></span>`;document.getElementById('verAnalysisRefresh').onclick=()=>this.render()}};
|
||
const RiskAnalysisManager={open(){ModalManager.open('riskAnalysisModal');setTimeout(()=>this.render(),150)},render(){const c=document.getElementById('riskAnalysisContainer');if(!c)return;const risks=[];projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{(pr.dependencies||[]).forEach(depId=>{if(!ProductManager.findProjectById(depId))risks.push({type:'broken',severity:'critical',title:'وابستگی شکسته',desc:`${pr.title} → پروژه ناموجود`})});if(ProductManager.getActiveDeployments(pr).length===0)risks.push({type:'no-deploy',severity:'high',title:'بدون استقرار',desc:pr.title});if(!prod.metadata?.owner)risks.push({type:'no-owner',severity:'medium',title:'بدون مالک',desc:prod.title})})});const crit=risks.filter(r=>r.severity==='critical').length;const high=risks.filter(r=>r.severity==='high').length;const med=risks.filter(r=>r.severity==='medium').length;let healthScore=Math.max(0,100-crit*15-high*10-med*5);let h='<div class="dashboard-kpi-row">';h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🎯</div><div class="dashboard-kpi-value">${healthScore}%</div><div class="dashboard-kpi-label">امتیاز سلامت</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🔴</div><div class="dashboard-kpi-value">${crit}</div><div class="dashboard-kpi-label">بحرانی</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🟠</div><div class="dashboard-kpi-value">${high}</div><div class="dashboard-kpi-label">بالا</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🟡</div><div class="dashboard-kpi-value">${med}</div><div class="dashboard-kpi-label">متوسط</div></div>`;h+='</div>';if(risks.length>0){h+='<div style="margin-top:16px">';risks.forEach(r=>{const colors={critical:'#e53e3e',high:'#dd6b20',medium:'#d69e2e'};h+=`<div style="padding:10px 12px;background:rgba(0,0,0,.05);border-radius:6px;margin-bottom:8px;border-right:4px solid ${colors[r.severity]||'#3182ce'}"><div style="font-weight:700;font-size:12px">${r.title}</div><div style="font-size:11px;color:var(--text-muted)">${r.desc}</div></div>`});h+='</div>'}else{h+='<div class="dashboard-empty-chart"><span>✅</span><div>هیچ ریسکی شناسایی نشد</div></div>'}c.innerHTML=h;document.getElementById('riskAnalysisFooterStats').innerHTML=`<span>🎯 سلامت: <span class="stat-highlight">${healthScore}%</span></span><span>⚠️ ریسکها: <span class="stat-highlight">${risks.length}</span></span>`;document.getElementById('riskAnalysisRefresh').onclick=()=>this.render()}};
|
||
/* ==================== EVENT BINDINGS ==================== */
|
||
document.getElementById('projectDetailDeleteBtn').addEventListener('click',()=>{if(ProductManager.currentDetailProductId)ProductManager.removeProduct(ProductManager.currentDetailProductId)});
|
||
document.getElementById('saveProductEditBtn').addEventListener('click',()=>ProductManager.saveProductEdit());
|
||
document.getElementById('saveNewProjectBtn').addEventListener('click',()=>ProductManager.saveNewProject());
|
||
document.getElementById('saveProjEditBtn').addEventListener('click',()=>ProductManager.saveProjectEdit());
|
||
document.getElementById('saveDeploymentBtn').addEventListener('click',()=>ProductManager.saveDeployment());
|
||
document.getElementById('saveEnvEditBtn').addEventListener('click',()=>EnvironmentManager.saveEdit());
|
||
document.getElementById('saveNewProductBtn').addEventListener('click',()=>ProductManager.createNewProduct());
|
||
document.getElementById('saveNewEnvBtn').addEventListener('click',()=>EnvironmentManager.saveNewEnv());
|
||
document.getElementById('newEnvBgColor').addEventListener('input',e=>{document.getElementById('newEnvBgColorText').textContent=e.target.value});
|
||
document.getElementById('newEnvTextColor').addEventListener('input',e=>{document.getElementById('newEnvTextColorText').textContent=e.target.value});
|
||
document.getElementById('editEnvBgColor').addEventListener('input',e=>{document.getElementById('editEnvBgColorText').textContent=e.target.value;EnvironmentManager.updateEditPreview()});
|
||
document.getElementById('editEnvTextColor').addEventListener('input',e=>{document.getElementById('editEnvTextColorText').textContent=e.target.value;EnvironmentManager.updateEditPreview()});
|
||
document.getElementById('galaxyResetView').addEventListener('click',()=>{if(GalaxyChartManager.svg&&GalaxyChartManager.zoomBehavior)GalaxyChartManager.svg.transition().duration(500).call(GalaxyChartManager.zoomBehavior.transform,d3.zoomIdentity)});
|
||
document.getElementById('galaxyInfoClose').addEventListener('click',()=>{document.getElementById('galaxyInfoPanel').classList.remove('visible')});
|
||
document.getElementById('galaxyExportPNG').addEventListener('click',()=>{showToast('📷 خروجی ذخیره شد','success')});
|
||
function handleToolSelect(tool,group){const ah=ActionRegistry[tool.action]||ActionRegistry.defaultAction;ah(tool,group)}
|
||
const themeToggle=document.getElementById('themeToggle'),themeIcon=document.getElementById('themeIcon'),htmlEl=document.documentElement;
|
||
const savedTheme=localStorage.getItem('theme')||'light';
|
||
htmlEl.setAttribute('data-theme',savedTheme);
|
||
if(themeIcon)themeIcon.textContent=savedTheme==='dark'?'☀️':'🌙';
|
||
if(themeToggle){themeToggle.addEventListener('click',()=>{const c=htmlEl.getAttribute('data-theme');const n=c==='dark'?'light':'dark';htmlEl.setAttribute('data-theme',n);localStorage.setItem('theme',n);if(themeIcon)themeIcon.textContent=n==='dark'?'☀️':'🌙'})}
|
||
const sidebarToggle=document.getElementById('sidebarToggle'),sidebar=document.getElementById('sidebar'),sidebarIcon=document.getElementById('sidebarIcon');
|
||
function updateSidebarIcon(){if(!sidebarIcon)return;sidebarIcon.textContent=sidebar.classList.contains('collapsed')?'☰':'✕'}
|
||
if(sidebarToggle){sidebarToggle.addEventListener('click',()=>{sidebar.classList.toggle('collapsed');updateSidebarIcon()})}
|
||
updateSidebarIcon();
|
||
const sidebarBody=document.getElementById('sidebarBody');
|
||
function renderToolGroups(){sidebarBody.innerHTML='';projectData.toolGroups.forEach(group=>{const ge=document.createElement('div');ge.className='sidebar-group';const he=document.createElement('div');he.className='sidebar-group-header';he.innerHTML=`<div class="group-icon"><span>${group.icon||''}</span><span>${group.title}</span></div><span class="group-arrow">▼</span>`;const be=document.createElement('div');be.className='sidebar-group-body';group.tools.forEach(tool=>{const btn=document.createElement('button');btn.className='sidebar-btn';btn.title=tool.description;btn.innerHTML=`<span style="width:20px;text-align:center">${tool.icon||'🔧'}</span><span style="flex:1">${tool.title}</span>`;btn.addEventListener('click',()=>handleToolSelect(tool,group));be.appendChild(btn)});he.addEventListener('click',()=>ge.classList.toggle('collapsed'));ge.appendChild(he);ge.appendChild(be);sidebarBody.appendChild(ge)})}
|
||
function openSettingsModal(){document.getElementById('settingProjectTitle').value=projectData.projectTitle;document.getElementById('settingVersion').value=projectData.version;document.getElementById('settingDescription').value=projectData.description;document.getElementById('settingDeveloper').value=projectData.developer;document.getElementById('settingCompany').value=projectData.company;document.getElementById('settingWebhookUrl').value=projectData.webhook.url;document.getElementById('settingWebhookClientId').value=projectData.webhook.clientId;document.getElementById('settingWebhookEnabled').checked=projectData.webhook.enabled;WebhookManager.updateStatusUI();ModalManager.open('settingsModal')}
|
||
['settingWebhookUrl','settingWebhookClientId','settingWebhookEnabled'].forEach(id=>{document.getElementById(id).addEventListener('input',()=>{WebhookManager.setConfig({url:document.getElementById('settingWebhookUrl').value,clientId:document.getElementById('settingWebhookClientId').value,enabled:document.getElementById('settingWebhookEnabled').checked})})});
|
||
document.getElementById('saveSettingsBtn').addEventListener('click',()=>{WebhookManager.setConfig({url:document.getElementById('settingWebhookUrl').value.trim(),clientId:document.getElementById('settingWebhookClientId').value.trim(),enabled:document.getElementById('settingWebhookEnabled').checked});ModalManager.close('settingsModal');showToast('تنظیمات ذخیره شد','success')});
|
||
function updateProjectUI(){document.title=projectData.projectTitle;document.getElementById('headerTitle').textContent=projectData.projectTitle;document.getElementById('headerSubtitle').textContent=projectData.description;document.getElementById('appFooter').innerHTML=`<div><span class="footer-brand">طراح و توسعهدهنده:</span> ${projectData.developer} <span style="margin:0 10px">|</span><span class="footer-brand">شرکت:</span> ${projectData.company}</div><div><span class="footer-brand">نسخه:</span> ${projectData.version}</div>`}
|
||
/* ==================== INIT ==================== */
|
||
ModalManager.init();renderToolGroups();updateProjectUI();WebhookManager.updateStatusUI();ToolbarManager.init();SectionCollapseManager.init();BoardRenderer.render();HistoryManager.init();
|
||
setTimeout(()=>showToast(`${projectData.projectTitle} (نسخه ${projectData.version}) آماده است`,'success'),500);
|
||
</script>
|
||
</body>
|
||
</html> |