last ...
This commit is contained in:
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,226 @@
|
|||||||
|
{
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"id": "env-1789757606914",
|
||||||
|
"name": "کاوش - توسعه",
|
||||||
|
"description": "این محیط فقط برای تست عملیاتی توسعه دهندگان است",
|
||||||
|
"backgroundColor": "#3f94ee",
|
||||||
|
"textColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "env-1789757658261",
|
||||||
|
"name": "کاوش تست",
|
||||||
|
"description": "این محیط برای تست محصول توسط تیم های تضمین کیفیت و تحلیل است",
|
||||||
|
"backgroundColor": "#df3416",
|
||||||
|
"textColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "env-1789757730121",
|
||||||
|
"name": "کانون - قرمز",
|
||||||
|
"description": "محیطی برای تست محصولات در فضای کانون",
|
||||||
|
"backgroundColor": "#ff0000",
|
||||||
|
"textColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "env-1789757791901",
|
||||||
|
"name": "کانون - آبی",
|
||||||
|
"description": "محیط انتشار عمومی محصولات در کانون",
|
||||||
|
"backgroundColor": "#0000ff",
|
||||||
|
"textColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "env-1789757853333",
|
||||||
|
"name": "سازمان - قرمز",
|
||||||
|
"description": "محیطی برای تست محصولات در سازمان",
|
||||||
|
"backgroundColor": "#ff0000",
|
||||||
|
"textColor": "#ffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "env-1789757900213",
|
||||||
|
"name": "سازمان - آبی",
|
||||||
|
"description": "محیط انتشار عمومی محصولات در سازمان",
|
||||||
|
"backgroundColor": "#0000ff",
|
||||||
|
"textColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": "prod-1789906178306",
|
||||||
|
"title": "زیرساخت",
|
||||||
|
"description": "کلیه پروژه هایی که بعنوان زیرساخت برای سایر محصولات شرکت مورد استفاده قرار می گیرند ...",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:09:38.306Z",
|
||||||
|
"owner": "تیم آتنا",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789906450942",
|
||||||
|
"title": "کنترل سنتر",
|
||||||
|
"description": "مدیریت تنظیمات و ارائه اطلاعات پایه برای پروژه های مختلف ...",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:14:10.942Z",
|
||||||
|
"owner": "تیم آتنا",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789906580440",
|
||||||
|
"title": "مانا",
|
||||||
|
"description": "مدیریت ارجاع نقشه برداران ایران",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:16:20.440Z",
|
||||||
|
"owner": "تیم کاداستر",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789906612889",
|
||||||
|
"title": "مدام",
|
||||||
|
"description": "مدیریت داده های مکانی",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:16:52.889Z",
|
||||||
|
"owner": "تیم کاداستر",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789906649432",
|
||||||
|
"title": "مدیریت قرارداد ها",
|
||||||
|
"description": "",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:17:29.432Z",
|
||||||
|
"owner": "تیم کاداستر",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789907070679",
|
||||||
|
"title": "پیام رسان",
|
||||||
|
"description": "",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:24:30.679Z",
|
||||||
|
"owner": "تیم کاداستر",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789907660064",
|
||||||
|
"title": "تفکیک آپارتمانی - کاداستر",
|
||||||
|
"description": "",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:34:20.064Z",
|
||||||
|
"owner": "تیم کاداستر",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789907676416",
|
||||||
|
"title": "ابزار WebGIS",
|
||||||
|
"description": "",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:34:36.416Z",
|
||||||
|
"owner": "تیم کاداستر",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789907707481",
|
||||||
|
"title": "سرویس های املاک",
|
||||||
|
"description": "",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:35:07.481Z",
|
||||||
|
"owner": "تیم کاداستر",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789907737585",
|
||||||
|
"title": "ساماندهی",
|
||||||
|
"description": "",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:35:37.585Z",
|
||||||
|
"owner": "تیم املاک",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789907760114",
|
||||||
|
"title": "تعیین تکلیف",
|
||||||
|
"description": "",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:36:00.114Z",
|
||||||
|
"owner": "تیم املاک",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789907772450",
|
||||||
|
"title": "دفترچه ای",
|
||||||
|
"description": "",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:36:12.450Z",
|
||||||
|
"owner": "تیم املاک",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789907792986",
|
||||||
|
"title": "تفکیک آپارتمانی",
|
||||||
|
"description": "",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:36:32.986Z",
|
||||||
|
"owner": "تیم املاک",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789907850075",
|
||||||
|
"title": "ثبت اطلاعات سامانه املاک",
|
||||||
|
"description": "",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:37:30.075Z",
|
||||||
|
"owner": "تیم املاک",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789907867139",
|
||||||
|
"title": "املاک من",
|
||||||
|
"description": "",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:37:47.139Z",
|
||||||
|
"owner": "تیم املاک",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prod-1789907887203",
|
||||||
|
"title": "شروع از کارگزاری",
|
||||||
|
"description": "",
|
||||||
|
"metadata": {
|
||||||
|
"createdAt": "2026-09-20T12:38:07.203Z",
|
||||||
|
"owner": "تیم املاک",
|
||||||
|
"customFields": {}
|
||||||
|
},
|
||||||
|
"projects": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"boards": [],
|
||||||
|
"boardTags": []
|
||||||
|
}
|
||||||
+279
-85
@@ -3017,6 +3017,12 @@
|
|||||||
<option value="">همه محیطها</option>
|
<option value="">همه محیطها</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="filter-dropdown">
|
||||||
|
<span>👤</span>
|
||||||
|
<select class="filter-select" id="filterOwner">
|
||||||
|
<option value="">همه مالکان</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-stats">
|
<div class="toolbar-stats">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
@@ -3296,32 +3302,38 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">عنوان <span class="required">*</span></label><input type="text" class="form-input"
|
<label class="form-label">عنوان <span class="required">*</span></label>
|
||||||
id="newProjectTitle" />
|
<input type="text" class="form-input" id="newProjectTitle" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">نوع <span class="required">*</span></label><select class="form-select"
|
<label class="form-label">نوع <span class="required">*</span></label>
|
||||||
id="newProjectType">
|
<select class="form-select" id="newProjectType">
|
||||||
<option value="">انتخاب کنید...</option>
|
<option value="">انتخاب کنید...</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">فریمورک</label><input type="text" class="form-input" id="newProjectFramework" />
|
<label class="form-label">فریمورک</label>
|
||||||
|
<input type="text" class="form-input" id="newProjectFramework" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">زبان</label><input type="text" class="form-input" id="newProjectLanguage" />
|
<label class="form-label">مالک پروژه</label>
|
||||||
|
<input type="text" class="form-input" id="newProjectOwner" placeholder="نام مسئول پروژه..." />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">آدرس مخزن</label><input type="text" class="form-input" id="newProjectRepo"
|
<label class="form-label">زبان</label>
|
||||||
dir="ltr" />
|
<input type="text" class="form-input" id="newProjectLanguage" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">توضیحات</label><textarea class="form-textarea"
|
<label class="form-label">آدرس مخزن</label>
|
||||||
id="newProjectDescription"></textarea>
|
<input type="text" class="form-input" id="newProjectRepo" dir="ltr" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">توضیحات</label>
|
||||||
|
<textarea class="form-textarea" id="newProjectDescription"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@@ -3338,35 +3350,42 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">شناسه <span class="readonly-badge">فقط خواندنی</span></label><input type="text"
|
<label class="form-label">شناسه <span class="readonly-badge">فقط خواندنی</span></label>
|
||||||
class="form-input" id="editProjId" readonly />
|
<input type="text" class="form-input" id="editProjId" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">عنوان <span class="required">*</span></label><input type="text" class="form-input"
|
<label class="form-label">عنوان <span class="required">*</span></label>
|
||||||
id="editProjTitle" />
|
<input type="text" class="form-input" id="editProjTitle" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">نوع <span class="required">*</span></label><select class="form-select"
|
<label class="form-label">نوع <span class="required">*</span></label>
|
||||||
id="editProjType">
|
<select class="form-select" id="editProjType">
|
||||||
<option value="">انتخاب کنید...</option>
|
<option value="">انتخاب کنید...</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">فریمورک</label><input type="text" class="form-input" id="editProjFramework" />
|
<label class="form-label">فریمورک</label>
|
||||||
|
<input type="text" class="form-input" id="editProjFramework" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">زبان</label><input type="text" class="form-input" id="editProjLanguage" />
|
<label class="form-label">مالک پروژه</label>
|
||||||
|
<input type="text" class="form-input" id="editProjOwner" placeholder="نام مسئول پروژه..." />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">آدرس مخزن</label><input type="text" class="form-input" id="editProjRepo"
|
<label class="form-label">زبان</label>
|
||||||
dir="ltr" />
|
<input type="text" class="form-input" id="editProjLanguage" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">توضیحات</label><textarea class="form-textarea" id="editProjDescription"></textarea>
|
<label class="form-label">آدرس مخزن</label>
|
||||||
|
<input type="text" class="form-input" id="editProjRepo" dir="ltr" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">توضیحات</label>
|
||||||
|
<textarea class="form-textarea" id="editProjDescription"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@@ -3486,8 +3505,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<div class="galaxy-stats" id="galaxyStats"></div>
|
|
||||||
<button class="btn btn-ghost" data-close-modal>بستن</button>
|
<button class="btn btn-ghost" data-close-modal>بستن</button>
|
||||||
|
<div class="galaxy-stats" id="galaxyStats"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3505,8 +3524,8 @@
|
|||||||
<div id="dashboardContainer" class="dashboard-container"></div>
|
<div id="dashboardContainer" class="dashboard-container"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<div class="dashboard-footer-stats" id="dashboardFooterStats"></div>
|
|
||||||
<button class="btn btn-ghost" data-close-modal>بستن</button>
|
<button class="btn btn-ghost" data-close-modal>بستن</button>
|
||||||
|
<div class="dashboard-footer-stats" id="dashboardFooterStats"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3524,8 +3543,8 @@
|
|||||||
<div id="dependencyAnalysisContainer" class="dashboard-container"></div>
|
<div id="dependencyAnalysisContainer" class="dashboard-container"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<div class="dashboard-footer-stats" id="depAnalysisFooterStats"></div>
|
|
||||||
<button class="btn btn-ghost" data-close-modal>بستن</button>
|
<button class="btn btn-ghost" data-close-modal>بستن</button>
|
||||||
|
<div class="dashboard-footer-stats" id="depAnalysisFooterStats"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3543,8 +3562,8 @@
|
|||||||
<div id="deploymentAnalysisContainer" class="dashboard-container"></div>
|
<div id="deploymentAnalysisContainer" class="dashboard-container"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<div class="dashboard-footer-stats" id="deployAnalysisFooterStats"></div>
|
|
||||||
<button class="btn btn-ghost" data-close-modal>بستن</button>
|
<button class="btn btn-ghost" data-close-modal>بستن</button>
|
||||||
|
<div class="dashboard-footer-stats" id="deployAnalysisFooterStats"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3581,8 +3600,8 @@
|
|||||||
<div id="riskAnalysisContainer" class="dashboard-container"></div>
|
<div id="riskAnalysisContainer" class="dashboard-container"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<div class="dashboard-footer-stats" id="riskAnalysisFooterStats"></div>
|
|
||||||
<button class="btn btn-ghost" data-close-modal>بستن</button>
|
<button class="btn btn-ghost" data-close-modal>بستن</button>
|
||||||
|
<div class="dashboard-footer-stats" id="riskAnalysisFooterStats"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -4283,9 +4302,10 @@
|
|||||||
};
|
};
|
||||||
/* ==================== TOOLBAR MANAGER ==================== */
|
/* ==================== TOOLBAR MANAGER ==================== */
|
||||||
const ToolbarManager = {
|
const ToolbarManager = {
|
||||||
currentFilters: { query: "", typeFilter: "", envFilter: "" },
|
currentFilters: { query: "", typeFilter: "", envFilter: "", ownerFilter: "" },
|
||||||
init() {
|
init() {
|
||||||
this.populateEnvironmentFilter();
|
this.populateEnvironmentFilter();
|
||||||
|
this.populateOwnerFilter();
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
this.updateStats();
|
this.updateStats();
|
||||||
},
|
},
|
||||||
@@ -4298,10 +4318,28 @@
|
|||||||
.map((e) => `<option value="${e.id}">${e.name}</option>`)
|
.map((e) => `<option value="${e.id}">${e.name}</option>`)
|
||||||
.join("");
|
.join("");
|
||||||
},
|
},
|
||||||
|
populateOwnerFilter() {
|
||||||
|
const s = document.getElementById("filterOwner");
|
||||||
|
if (!s) return;
|
||||||
|
const owners = new Set();
|
||||||
|
(projectData.appData.products || []).forEach((p) => {
|
||||||
|
const owner = p.metadata?.owner?.trim();
|
||||||
|
if (owner) owners.add(owner);
|
||||||
|
});
|
||||||
|
const sortedOwners = Array.from(owners).sort((a, b) =>
|
||||||
|
a.localeCompare(b, "fa"),
|
||||||
|
);
|
||||||
|
s.innerHTML =
|
||||||
|
'<option value="">همه مالکان</option>' +
|
||||||
|
sortedOwners
|
||||||
|
.map((o) => `<option value="${o}">${o}</option>`)
|
||||||
|
.join("");
|
||||||
|
},
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
const si = document.getElementById("toolbarSearch"),
|
const si = document.getElementById("toolbarSearch"),
|
||||||
ft = document.getElementById("filterType"),
|
ft = document.getElementById("filterType"),
|
||||||
fe = document.getElementById("filterEnvironment");
|
fe = document.getElementById("filterEnvironment"),
|
||||||
|
fo = document.getElementById("filterOwner");
|
||||||
if (si)
|
if (si)
|
||||||
si.addEventListener("input", () => {
|
si.addEventListener("input", () => {
|
||||||
this.applyFilters();
|
this.applyFilters();
|
||||||
@@ -4317,6 +4355,11 @@
|
|||||||
this.applyFilters();
|
this.applyFilters();
|
||||||
refreshUI();
|
refreshUI();
|
||||||
});
|
});
|
||||||
|
if (fo)
|
||||||
|
fo.addEventListener("change", () => {
|
||||||
|
this.applyFilters();
|
||||||
|
refreshUI();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
applyFilters() {
|
applyFilters() {
|
||||||
this.currentFilters = {
|
this.currentFilters = {
|
||||||
@@ -4324,8 +4367,8 @@
|
|||||||
.trim()
|
.trim()
|
||||||
.toLowerCase(),
|
.toLowerCase(),
|
||||||
typeFilter: document.getElementById("filterType")?.value || "",
|
typeFilter: document.getElementById("filterType")?.value || "",
|
||||||
envFilter:
|
envFilter: document.getElementById("filterEnvironment")?.value || "",
|
||||||
document.getElementById("filterEnvironment")?.value || "",
|
ownerFilter: document.getElementById("filterOwner")?.value || "",
|
||||||
};
|
};
|
||||||
this.updateStats();
|
this.updateStats();
|
||||||
},
|
},
|
||||||
@@ -4350,7 +4393,15 @@
|
|||||||
},
|
},
|
||||||
shouldShowProduct(product) {
|
shouldShowProduct(product) {
|
||||||
const f = this.currentFilters;
|
const f = this.currentFilters;
|
||||||
|
if (!f.query && !f.typeFilter && !f.envFilter && !f.ownerFilter) return true;
|
||||||
|
|
||||||
|
if (f.ownerFilter) {
|
||||||
|
const prodOwner = product.metadata?.owner?.trim();
|
||||||
|
if (prodOwner !== f.ownerFilter) return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!f.query && !f.typeFilter && !f.envFilter) return true;
|
if (!f.query && !f.typeFilter && !f.envFilter) return true;
|
||||||
|
|
||||||
return (product.projects || []).some((pr) => {
|
return (product.projects || []).some((pr) => {
|
||||||
const mq =
|
const mq =
|
||||||
!f.query ||
|
!f.query ||
|
||||||
@@ -4362,6 +4413,7 @@
|
|||||||
(pr.deployments || [])
|
(pr.deployments || [])
|
||||||
.filter((d) => d.status === "active")
|
.filter((d) => d.status === "active")
|
||||||
.some((d) => d.environmentId === f.envFilter);
|
.some((d) => d.environmentId === f.envFilter);
|
||||||
|
|
||||||
return mq && mt && me;
|
return mq && mt && me;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -4891,6 +4943,7 @@
|
|||||||
updateProjectUI();
|
updateProjectUI();
|
||||||
renderToolGroups();
|
renderToolGroups();
|
||||||
ToolbarManager.populateEnvironmentFilter();
|
ToolbarManager.populateEnvironmentFilter();
|
||||||
|
ToolbarManager.populateOwnerFilter();
|
||||||
refreshUI();
|
refreshUI();
|
||||||
showToast("📂 بارگذاری شد", "success");
|
showToast("📂 بارگذاری شد", "success");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -4913,6 +4966,7 @@
|
|||||||
};
|
};
|
||||||
AutosaveManager.clear();
|
AutosaveManager.clear();
|
||||||
ToolbarManager.populateEnvironmentFilter();
|
ToolbarManager.populateEnvironmentFilter();
|
||||||
|
ToolbarManager.populateOwnerFilter();
|
||||||
refreshUI();
|
refreshUI();
|
||||||
showToast("🗑️ پاکسازی شد", "success");
|
showToast("🗑️ پاکسازی شد", "success");
|
||||||
},
|
},
|
||||||
@@ -5132,7 +5186,13 @@
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
getActiveDeployments(proj) {
|
getActiveDeployments(proj) {
|
||||||
return (proj.deployments || []).filter((d) => d.status === "active");
|
return (proj.deployments || [])
|
||||||
|
.filter((d) => d.status === "active")
|
||||||
|
.sort((a, b) => {
|
||||||
|
const dateA = a.deploymentDate ? new Date(a.deploymentDate).getTime() : 0;
|
||||||
|
const dateB = b.deploymentDate ? new Date(b.deploymentDate).getTime() : 0;
|
||||||
|
return dateB - dateA;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
getEnvironmentsWithoutDeployment(proj) {
|
getEnvironmentsWithoutDeployment(proj) {
|
||||||
const allEnvIds = projectData.appData.environments.map((e) => e.id);
|
const allEnvIds = projectData.appData.environments.map((e) => e.id);
|
||||||
@@ -5154,7 +5214,9 @@
|
|||||||
(s, pr) => s + this.getActiveDeployments(pr).length,
|
(s, pr) => s + this.getActiveDeployments(pr).length,
|
||||||
0,
|
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">`;
|
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) {
|
if (projects.length === 0) {
|
||||||
html += '<div class="empty-subprojects">🌿 هیچ پروژهای نیست</div>';
|
html += '<div class="empty-subprojects">🌿 هیچ پروژهای نیست</div>';
|
||||||
} else {
|
} else {
|
||||||
@@ -5163,21 +5225,43 @@
|
|||||||
const ti = this.getTypeInfo(pr.type);
|
const ti = this.getTypeInfo(pr.type);
|
||||||
const deps = pr.dependencies || [];
|
const deps = pr.dependencies || [];
|
||||||
const deploys = this.getActiveDeployments(pr);
|
const deploys = this.getActiveDeployments(pr);
|
||||||
const emptyEnvs = this.getEnvironmentsWithoutDeployment(pr);
|
const archivedDeploys = (pr.deployments || [])
|
||||||
const canAdd = emptyEnvs.length > 0;
|
.filter((d) => d.status === "archived")
|
||||||
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
|
.sort((a, b) => {
|
||||||
? `<div class="detail-info-item"><span class="detail-info-label">وابستگیها (${deps.length})</span><span class="detail-info-value">${deps
|
const dateA = a.archivedAt ? new Date(a.archivedAt).getTime() : 0;
|
||||||
|
const dateB = b.archivedAt ? new Date(b.archivedAt).getTime() : 0;
|
||||||
|
return dateB - dateA;
|
||||||
|
});
|
||||||
|
const hasEnvs = projectData.appData.environments.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">`;
|
||||||
|
|
||||||
|
if (pr.metadata?.owner) {
|
||||||
|
html += `<div class="detail-info-item"><span class="detail-info-label">👤 مالک</span><span class="detail-info-value">${pr.metadata.owner}</span></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pr.metadata?.framework) {
|
||||||
|
html += `<div class="detail-info-item"><span class="detail-info-label">فریمورک</span><span class="detail-info-value">${pr.metadata.framework}</span></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pr.metadata?.language) {
|
||||||
|
html += `<div class="detail-info-item"><span class="detail-info-label">زبان</span><span class="detail-info-value">${pr.metadata.language}</span></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deps.length > 0) {
|
||||||
|
html += `<div class="detail-info-item"><span class="detail-info-label">وابستگیها (${deps.length})</span><span class="detail-info-value">${deps
|
||||||
.map((depId) => {
|
.map((depId) => {
|
||||||
const dp = this.findProjectById(depId);
|
const dp = this.findProjectById(depId);
|
||||||
return dp ? dp.title : depId;
|
return dp ? dp.title : depId;
|
||||||
})
|
})
|
||||||
.join("، ")}</span></div>`
|
.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>`;
|
html += `</div>`;
|
||||||
|
html += `<div class="deployment-section"><div class="deployment-header"><span class="deployment-header-title">🚀 استقرارهای فعال (${deploys.length})</span>${hasEnvs ? `<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) {
|
if (deploys.length === 0) {
|
||||||
html +=
|
html += '<div class="empty-deployments">هنوز استقرار ندارد</div>';
|
||||||
'<div class="empty-deployments">هنوز استقرار ندارد</div>';
|
|
||||||
} else {
|
} else {
|
||||||
html += '<div class="deployment-list">';
|
html += '<div class="deployment-list">';
|
||||||
deploys.forEach((d) => {
|
deploys.forEach((d) => {
|
||||||
@@ -5186,13 +5270,22 @@
|
|||||||
const fd = d.deploymentDate
|
const fd = d.deploymentDate
|
||||||
? new Date(d.deploymentDate).toLocaleString("fa-IR")
|
? 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-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">`;
|
||||||
|
|
||||||
|
html += `<button class="btn btn-ghost btn-xs" onclick="ProductManager.openEditDeploymentModal('${prod.id}','${pr.id}','${d.id}')" title="ویرایش">✏️</button>`;
|
||||||
|
|
||||||
|
html += `<button class="btn btn-xs" style="background:#dd6b20;color:#fff" onclick="ProductManager.archiveDeployment('${prod.id}','${pr.id}','${d.id}')" title="انتقال به تاریخچه">📦 آرشیو</button>`;
|
||||||
|
|
||||||
|
html += `<button class="btn btn-danger btn-xs" onclick="ProductManager.removeDeployment('${prod.id}','${pr.id}','${d.id}')" title="حذف کامل">🗑️</button>`;
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
|
||||||
|
if (d.releaseNotes) {
|
||||||
html += `<div class="deployment-notes">📝 ${d.releaseNotes}</div>`;
|
html += `<div class="deployment-notes">📝 ${d.releaseNotes}</div>`;
|
||||||
if (
|
}
|
||||||
d.requiredDeployments &&
|
|
||||||
d.requiredDeployments.length > 0
|
if (d.requiredDeployments && d.requiredDeployments.length > 0) {
|
||||||
) {
|
|
||||||
html += `<div class="deployment-req">🔗 نیازمندیها: ${d.requiredDeployments
|
html += `<div class="deployment-req">🔗 نیازمندیها: ${d.requiredDeployments
|
||||||
.map((rd) => {
|
.map((rd) => {
|
||||||
const rp = this.findProjectById(rd.projectId);
|
const rp = this.findProjectById(rd.projectId);
|
||||||
@@ -5207,14 +5300,62 @@
|
|||||||
})
|
})
|
||||||
.join("، ")}</div>`;
|
.join("، ")}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
});
|
});
|
||||||
html += "</div>";
|
html += "</div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html += `<div class="deployment-section" style="margin-top:12px;border-top:2px dashed var(--border)">`;
|
||||||
|
html += `<div class="deployment-header">`;
|
||||||
|
html += `<span class="deployment-header-title">📜 تاریخچه استقرارها (${archivedDeploys.length})</span>`;
|
||||||
|
if (archivedDeploys.length > 0) {
|
||||||
|
html += `<button class="btn btn-ghost btn-xs" onclick="ProductManager.toggleArchiveVisibility('${pr.id}')" id="archiveToggle-${pr.id}">👁️ نمایش/مخفی</button>`;
|
||||||
|
}
|
||||||
|
html += `</div>`;
|
||||||
|
|
||||||
|
if (archivedDeploys.length === 0) {
|
||||||
|
html += `<div class="empty-deployments" style="opacity:0.6">هنوز استقراری آرشیو نشده است</div>`;
|
||||||
|
} else {
|
||||||
|
html += `<div class="deployment-list" id="archiveList-${pr.id}" style="display:none">`;
|
||||||
|
archivedDeploys.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")
|
||||||
|
: "—";
|
||||||
|
const archivedDate = d.archivedAt
|
||||||
|
? new Date(d.archivedAt).toLocaleString("fa-IR")
|
||||||
|
: "—";
|
||||||
|
|
||||||
|
html += `<div class="deployment-item" style="opacity:0.75;background:var(--border-light)">`;
|
||||||
|
html += `<div class="deployment-item-info">`;
|
||||||
|
html += `<span class="deployment-env-tag" style="background-color:${cl.backgroundColor};color:${cl.textColor};opacity:0.7">${en}</span>`;
|
||||||
|
html += `<span class="deployment-version" style="opacity:0.7">v${d.version}</span>`;
|
||||||
|
html += `<span class="deployment-date">📅 ${fd}</span>`;
|
||||||
|
html += `<span class="deployment-date" style="color:var(--warning)">📦 آرشیو: ${archivedDate}</span>`;
|
||||||
|
html += `</div>`;
|
||||||
|
html += `<div class="deployment-actions">`;
|
||||||
|
html += `<button class="btn btn-xs" style="background:#3182ce;color:#fff" onclick="ProductManager.restoreDeployment('${prod.id}','${pr.id}','${d.id}')" title="بازگرداندن به لیست فعال">↩️ بازگردانی</button>`;
|
||||||
|
html += `<button class="btn btn-danger btn-xs" onclick="ProductManager.removeDeployment('${prod.id}','${pr.id}','${d.id}')" title="حذف کامل">🗑️</button>`;
|
||||||
|
html += `</div>`;
|
||||||
|
|
||||||
|
if (d.releaseNotes) {
|
||||||
|
html += `<div class="deployment-notes" style="opacity:0.7">📝 ${d.releaseNotes}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
});
|
||||||
|
html += `</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
|
||||||
html += `</div></div>`;
|
html += `</div></div>`;
|
||||||
});
|
});
|
||||||
html += "</div>";
|
html += "</div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
html += `</div></div>`;
|
html += `</div></div>`;
|
||||||
document.getElementById("projectDetailBody").innerHTML = html;
|
document.getElementById("projectDetailBody").innerHTML = html;
|
||||||
ModalManager.open("projectDetailModal");
|
ModalManager.open("projectDetailModal");
|
||||||
@@ -5259,6 +5400,7 @@
|
|||||||
});
|
});
|
||||||
ModalManager.close("addProductModal");
|
ModalManager.close("addProductModal");
|
||||||
refreshUI();
|
refreshUI();
|
||||||
|
ToolbarManager.populateOwnerFilter();
|
||||||
showToast(`"${title}" ایجاد شد`, "success");
|
showToast(`"${title}" ایجاد شد`, "success");
|
||||||
},
|
},
|
||||||
openDependencyManager(productId, projId) {
|
openDependencyManager(productId, projId) {
|
||||||
@@ -5449,6 +5591,7 @@
|
|||||||
prod.metadata.updatedAt = new Date().toISOString();
|
prod.metadata.updatedAt = new Date().toISOString();
|
||||||
ModalManager.close("productEditModal");
|
ModalManager.close("productEditModal");
|
||||||
refreshUI();
|
refreshUI();
|
||||||
|
ToolbarManager.populateOwnerFilter();
|
||||||
this.showDetail(pid);
|
this.showDetail(pid);
|
||||||
showToast(`"${prod.title}" ویرایش شد`, "success");
|
showToast(`"${prod.title}" ویرایش شد`, "success");
|
||||||
},
|
},
|
||||||
@@ -5460,6 +5603,7 @@
|
|||||||
document.getElementById("newProjectTitle").value = "";
|
document.getElementById("newProjectTitle").value = "";
|
||||||
document.getElementById("newProjectType").value = "";
|
document.getElementById("newProjectType").value = "";
|
||||||
document.getElementById("newProjectFramework").value = "";
|
document.getElementById("newProjectFramework").value = "";
|
||||||
|
document.getElementById("newProjectOwner").value = ""; // 🆕
|
||||||
document.getElementById("newProjectLanguage").value = "";
|
document.getElementById("newProjectLanguage").value = "";
|
||||||
document.getElementById("newProjectRepo").value = "";
|
document.getElementById("newProjectRepo").value = "";
|
||||||
document.getElementById("newProjectDescription").value = "";
|
document.getElementById("newProjectDescription").value = "";
|
||||||
@@ -5482,20 +5626,18 @@
|
|||||||
const np = {
|
const np = {
|
||||||
id: `proj-${Date.now()}`,
|
id: `proj-${Date.now()}`,
|
||||||
title,
|
title,
|
||||||
description:
|
description: document.getElementById("newProjectDescription").value.trim() || "",
|
||||||
document.getElementById("newProjectDescription").value.trim() ||
|
|
||||||
"",
|
|
||||||
type,
|
type,
|
||||||
repositoryUrl: document
|
repositoryUrl: document.getElementById("newProjectRepo").value.trim(),
|
||||||
.getElementById("newProjectRepo")
|
metadata: {
|
||||||
.value.trim(),
|
createdAt: new Date().toISOString(),
|
||||||
metadata: { createdAt: new Date().toISOString(), customFields: {} },
|
owner: document.getElementById("newProjectOwner").value.trim() || "", // 🆕
|
||||||
|
customFields: {}
|
||||||
|
},
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
deployments: [],
|
deployments: [],
|
||||||
};
|
};
|
||||||
const fw = document
|
const fw = document.getElementById("newProjectFramework").value.trim();
|
||||||
.getElementById("newProjectFramework")
|
|
||||||
.value.trim();
|
|
||||||
if (fw) np.metadata.framework = fw;
|
if (fw) np.metadata.framework = fw;
|
||||||
const lg = document.getElementById("newProjectLanguage").value.trim();
|
const lg = document.getElementById("newProjectLanguage").value.trim();
|
||||||
if (lg) np.metadata.language = lg;
|
if (lg) np.metadata.language = lg;
|
||||||
@@ -5506,9 +5648,7 @@
|
|||||||
showToast(`"${title}" ایجاد شد`, "success");
|
showToast(`"${title}" ایجاد شد`, "success");
|
||||||
},
|
},
|
||||||
openProjectEditModal(productId, projId) {
|
openProjectEditModal(productId, projId) {
|
||||||
const prod = projectData.appData.products.find(
|
const prod = projectData.appData.products.find((p) => p.id === productId);
|
||||||
(p) => p.id === productId,
|
|
||||||
);
|
|
||||||
if (!prod) return;
|
if (!prod) return;
|
||||||
const pr = prod.projects.find((p) => p.id === projId);
|
const pr = prod.projects.find((p) => p.id === projId);
|
||||||
if (!pr) return;
|
if (!pr) return;
|
||||||
@@ -5517,14 +5657,11 @@
|
|||||||
document.getElementById("editProjId").value = pr.id;
|
document.getElementById("editProjId").value = pr.id;
|
||||||
document.getElementById("editProjTitle").value = pr.title;
|
document.getElementById("editProjTitle").value = pr.title;
|
||||||
document.getElementById("editProjType").value = pr.type;
|
document.getElementById("editProjType").value = pr.type;
|
||||||
document.getElementById("editProjFramework").value =
|
document.getElementById("editProjFramework").value = pr.metadata?.framework || "";
|
||||||
pr.metadata?.framework || "";
|
document.getElementById("editProjOwner").value = pr.metadata?.owner || ""; // 🆕
|
||||||
document.getElementById("editProjLanguage").value =
|
document.getElementById("editProjLanguage").value = pr.metadata?.language || "";
|
||||||
pr.metadata?.language || "";
|
document.getElementById("editProjRepo").value = pr.repositoryUrl || "";
|
||||||
document.getElementById("editProjRepo").value =
|
document.getElementById("editProjDescription").value = pr.description || "";
|
||||||
pr.repositoryUrl || "";
|
|
||||||
document.getElementById("editProjDescription").value =
|
|
||||||
pr.description || "";
|
|
||||||
ModalManager.open("editProjectModal");
|
ModalManager.open("editProjectModal");
|
||||||
},
|
},
|
||||||
saveProjectEdit() {
|
saveProjectEdit() {
|
||||||
@@ -5545,15 +5682,15 @@
|
|||||||
AutosaveManager.scheduleSave();
|
AutosaveManager.scheduleSave();
|
||||||
pr.title = nt;
|
pr.title = nt;
|
||||||
pr.type = nty;
|
pr.type = nty;
|
||||||
pr.description =
|
pr.description = document.getElementById("editProjDescription").value.trim() || "";
|
||||||
document.getElementById("editProjDescription").value.trim() || "";
|
pr.repositoryUrl = document.getElementById("editProjRepo").value.trim();
|
||||||
pr.repositoryUrl = document
|
|
||||||
.getElementById("editProjRepo")
|
|
||||||
.value.trim();
|
|
||||||
if (!pr.metadata) pr.metadata = {};
|
if (!pr.metadata) pr.metadata = {};
|
||||||
const fw = document.getElementById("editProjFramework").value.trim();
|
const fw = document.getElementById("editProjFramework").value.trim();
|
||||||
if (fw) pr.metadata.framework = fw;
|
if (fw) pr.metadata.framework = fw;
|
||||||
else delete pr.metadata.framework;
|
else delete pr.metadata.framework;
|
||||||
|
const owner = document.getElementById("editProjOwner").value.trim(); // 🆕
|
||||||
|
if (owner) pr.metadata.owner = owner;
|
||||||
|
else delete pr.metadata.owner;
|
||||||
const lg = document.getElementById("editProjLanguage").value.trim();
|
const lg = document.getElementById("editProjLanguage").value.trim();
|
||||||
if (lg) pr.metadata.language = lg;
|
if (lg) pr.metadata.language = lg;
|
||||||
else delete pr.metadata.language;
|
else delete pr.metadata.language;
|
||||||
@@ -5565,36 +5702,39 @@
|
|||||||
},
|
},
|
||||||
openAddDeploymentModal(productId, projId) {
|
openAddDeploymentModal(productId, projId) {
|
||||||
if (!productId || !projId) return;
|
if (!productId || !projId) return;
|
||||||
const prod = projectData.appData.products.find(
|
const prod = projectData.appData.products.find((p) => p.id === productId);
|
||||||
(p) => p.id === productId,
|
|
||||||
);
|
|
||||||
if (!prod) return;
|
if (!prod) return;
|
||||||
const pr = prod.projects.find((p) => p.id === projId);
|
const pr = prod.projects.find((p) => p.id === projId);
|
||||||
if (!pr) return;
|
if (!pr) return;
|
||||||
const emptyEnvs = this.getEnvironmentsWithoutDeployment(pr);
|
|
||||||
if (emptyEnvs.length === 0) {
|
const allEnvs = projectData.appData.environments;
|
||||||
showToast("همه محیطها استقرار دارند", "warning");
|
if (allEnvs.length === 0) {
|
||||||
|
showToast("ابتدا یک محیط عملیاتی ایجاد کنید", "warning");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentDeploymentProjId = projId;
|
this.currentDeploymentProjId = projId;
|
||||||
this.currentDeploymentMode = "add";
|
this.currentDeploymentMode = "add";
|
||||||
this.currentDeploymentEnvId = null;
|
this.currentDeploymentEnvId = null;
|
||||||
this.currentEditDeploymentId = null;
|
this.currentEditDeploymentId = null;
|
||||||
|
|
||||||
document.getElementById("deploymentModalTitle").innerHTML =
|
document.getElementById("deploymentModalTitle").innerHTML =
|
||||||
"<span>🚀</span><span>افزودن نسخه استقرار</span>";
|
"<span>🚀</span><span>افزودن نسخه استقرار</span>";
|
||||||
|
|
||||||
const envGroup = document.getElementById("deploymentEnvGroup");
|
const envGroup = document.getElementById("deploymentEnvGroup");
|
||||||
const envs = projectData.appData.environments.filter((e) =>
|
const envs = allEnvs;
|
||||||
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
|
envGroup.innerHTML = `<label class="form-label">محیط استقرار <span class="required">*</span></label><select class="form-select" id="depEnvironment" onchange="ProductManager.onEnvChange()">${envs
|
||||||
.map((e) => {
|
.map((e) => {
|
||||||
const cl = this.getEnvironmentColors(e.id);
|
const cl = this.getEnvironmentColors(e.id);
|
||||||
return `<option value="${e.id}" style="background-color:${cl.backgroundColor};color:${cl.textColor}">${e.name} - ${e.description}</option>`;
|
return `<option value="${e.id}" style="background-color:${cl.backgroundColor};color:${cl.textColor}">${e.name} - ${e.description}</option>`;
|
||||||
})
|
})
|
||||||
.join("")}</select>`;
|
.join("")}</select>`;
|
||||||
|
|
||||||
document.getElementById("depVersion").value = "";
|
document.getElementById("depVersion").value = "";
|
||||||
document.getElementById("depDate").value = "";
|
document.getElementById("depDate").value = "";
|
||||||
document.getElementById("depReleaseNotes").value = "";
|
document.getElementById("depReleaseNotes").value = "";
|
||||||
|
|
||||||
this.renderRequiredDeploymentsSection();
|
this.renderRequiredDeploymentsSection();
|
||||||
ModalManager.open("deploymentModal");
|
ModalManager.open("deploymentModal");
|
||||||
},
|
},
|
||||||
@@ -5762,7 +5902,26 @@
|
|||||||
this.showDetail(pid);
|
this.showDetail(pid);
|
||||||
},
|
},
|
||||||
removeDeployment(productId, projId, depId) {
|
removeDeployment(productId, projId, depId) {
|
||||||
if (!confirm("حذف این استقرار؟")) return;
|
if (!confirm("⚠️ حذف کامل این استقرار؟\n\nاین عمل غیرقابل بازگشت است.\nاگر میخواهید فقط از لیست فعال خارج شود، از دکمه «آرشیو» استفاده کنید.")) 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;
|
||||||
|
|
||||||
|
HistoryManager.pushState();
|
||||||
|
AutosaveManager.scheduleSave();
|
||||||
|
|
||||||
|
pr.deployments = (pr.deployments || []).filter((d) => d.id !== depId);
|
||||||
|
|
||||||
|
refreshUI();
|
||||||
|
this.showDetail(productId);
|
||||||
|
showToast("🗑️ استقرار بهطور کامل حذف شد", "warning");
|
||||||
|
},
|
||||||
|
archiveDeployment(productId, projId, depId) {
|
||||||
|
if (!confirm("📦 انتقال این استقرار به تاریخچه؟\n\nاستقرار از لیست فعال خارج شده و در بخش تاریخچه نگهداری میشود.")) return;
|
||||||
|
|
||||||
const prod = projectData.appData.products.find(
|
const prod = projectData.appData.products.find(
|
||||||
(p) => p.id === productId,
|
(p) => p.id === productId,
|
||||||
);
|
);
|
||||||
@@ -5771,12 +5930,16 @@
|
|||||||
if (!pr) return;
|
if (!pr) return;
|
||||||
const dep = (pr.deployments || []).find((d) => d.id === depId);
|
const dep = (pr.deployments || []).find((d) => d.id === depId);
|
||||||
if (!dep) return;
|
if (!dep) return;
|
||||||
|
|
||||||
HistoryManager.pushState();
|
HistoryManager.pushState();
|
||||||
AutosaveManager.scheduleSave();
|
AutosaveManager.scheduleSave();
|
||||||
dep.status = "removed";
|
|
||||||
|
dep.status = "archived";
|
||||||
|
dep.archivedAt = new Date().toISOString();
|
||||||
|
|
||||||
refreshUI();
|
refreshUI();
|
||||||
this.showDetail(productId);
|
this.showDetail(productId);
|
||||||
showToast("استقرار حذف شد", "warning");
|
showToast("📦 استقرار به تاریخچه منتقل شد", "success");
|
||||||
},
|
},
|
||||||
removeProduct(productId) {
|
removeProduct(productId) {
|
||||||
const prod = projectData.appData.products.find(
|
const prod = projectData.appData.products.find(
|
||||||
@@ -5790,6 +5953,7 @@
|
|||||||
(p) => p.id !== productId,
|
(p) => p.id !== productId,
|
||||||
);
|
);
|
||||||
refreshUI();
|
refreshUI();
|
||||||
|
ToolbarManager.populateOwnerFilter();
|
||||||
ModalManager.close("projectDetailModal");
|
ModalManager.close("projectDetailModal");
|
||||||
showToast(`"${prod.title}" حذف شد`, "warning");
|
showToast(`"${prod.title}" حذف شد`, "warning");
|
||||||
},
|
},
|
||||||
@@ -5809,6 +5973,35 @@
|
|||||||
this.showDetail(productId);
|
this.showDetail(productId);
|
||||||
showToast(`"${pr.title}" حذف شد`, "warning");
|
showToast(`"${pr.title}" حذف شد`, "warning");
|
||||||
},
|
},
|
||||||
|
toggleArchiveVisibility(projId) {
|
||||||
|
const list = document.getElementById(`archiveList-${projId}`);
|
||||||
|
if (!list) return;
|
||||||
|
const isHidden = list.style.display === "none";
|
||||||
|
list.style.display = isHidden ? "flex" : "none";
|
||||||
|
},
|
||||||
|
restoreDeployment(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();
|
||||||
|
AutosaveManager.scheduleSave();
|
||||||
|
|
||||||
|
// ✅ بازگرداندن به وضعیت active
|
||||||
|
dep.status = "active";
|
||||||
|
delete dep.archivedAt;
|
||||||
|
|
||||||
|
refreshUI();
|
||||||
|
this.showDetail(productId);
|
||||||
|
showToast("↩️ استقرار به لیست فعال بازگردانده شد", "success");
|
||||||
|
},
|
||||||
};
|
};
|
||||||
/* ==================== DASHBOARD MANAGER ==================== */
|
/* ==================== DASHBOARD MANAGER ==================== */
|
||||||
const DashboardManager = {
|
const DashboardManager = {
|
||||||
@@ -6710,6 +6903,7 @@
|
|||||||
updateProjectUI();
|
updateProjectUI();
|
||||||
renderToolGroups();
|
renderToolGroups();
|
||||||
ToolbarManager.populateEnvironmentFilter();
|
ToolbarManager.populateEnvironmentFilter();
|
||||||
|
ToolbarManager.populateOwnerFilter();
|
||||||
refreshUI();
|
refreshUI();
|
||||||
if (ReleaseBoardsManager) {
|
if (ReleaseBoardsManager) {
|
||||||
ReleaseBoardsManager.render(true);
|
ReleaseBoardsManager.render(true);
|
||||||
|
|||||||
+368
-85
@@ -3017,6 +3017,12 @@
|
|||||||
<option value="">همه محیطها</option>
|
<option value="">همه محیطها</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="filter-dropdown">
|
||||||
|
<span>👤</span>
|
||||||
|
<select class="filter-select" id="filterOwner">
|
||||||
|
<option value="">همه مالکان</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-stats">
|
<div class="toolbar-stats">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
@@ -3296,32 +3302,38 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">عنوان <span class="required">*</span></label><input type="text" class="form-input"
|
<label class="form-label">عنوان <span class="required">*</span></label>
|
||||||
id="newProjectTitle" />
|
<input type="text" class="form-input" id="newProjectTitle" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">نوع <span class="required">*</span></label><select class="form-select"
|
<label class="form-label">نوع <span class="required">*</span></label>
|
||||||
id="newProjectType">
|
<select class="form-select" id="newProjectType">
|
||||||
<option value="">انتخاب کنید...</option>
|
<option value="">انتخاب کنید...</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">فریمورک</label><input type="text" class="form-input" id="newProjectFramework" />
|
<label class="form-label">فریمورک</label>
|
||||||
|
<input type="text" class="form-input" id="newProjectFramework" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">زبان</label><input type="text" class="form-input" id="newProjectLanguage" />
|
<label class="form-label">مالک پروژه</label>
|
||||||
|
<input type="text" class="form-input" id="newProjectOwner" placeholder="نام مسئول پروژه..." />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">آدرس مخزن</label><input type="text" class="form-input" id="newProjectRepo"
|
<label class="form-label">زبان</label>
|
||||||
dir="ltr" />
|
<input type="text" class="form-input" id="newProjectLanguage" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">توضیحات</label><textarea class="form-textarea"
|
<label class="form-label">آدرس مخزن</label>
|
||||||
id="newProjectDescription"></textarea>
|
<input type="text" class="form-input" id="newProjectRepo" dir="ltr" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">توضیحات</label>
|
||||||
|
<textarea class="form-textarea" id="newProjectDescription"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@@ -3338,35 +3350,42 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">شناسه <span class="readonly-badge">فقط خواندنی</span></label><input type="text"
|
<label class="form-label">شناسه <span class="readonly-badge">فقط خواندنی</span></label>
|
||||||
class="form-input" id="editProjId" readonly />
|
<input type="text" class="form-input" id="editProjId" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">عنوان <span class="required">*</span></label><input type="text" class="form-input"
|
<label class="form-label">عنوان <span class="required">*</span></label>
|
||||||
id="editProjTitle" />
|
<input type="text" class="form-input" id="editProjTitle" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">نوع <span class="required">*</span></label><select class="form-select"
|
<label class="form-label">نوع <span class="required">*</span></label>
|
||||||
id="editProjType">
|
<select class="form-select" id="editProjType">
|
||||||
<option value="">انتخاب کنید...</option>
|
<option value="">انتخاب کنید...</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">فریمورک</label><input type="text" class="form-input" id="editProjFramework" />
|
<label class="form-label">فریمورک</label>
|
||||||
|
<input type="text" class="form-input" id="editProjFramework" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">زبان</label><input type="text" class="form-input" id="editProjLanguage" />
|
<label class="form-label">مالک پروژه</label>
|
||||||
|
<input type="text" class="form-input" id="editProjOwner" placeholder="نام مسئول پروژه..." />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">آدرس مخزن</label><input type="text" class="form-input" id="editProjRepo"
|
<label class="form-label">زبان</label>
|
||||||
dir="ltr" />
|
<input type="text" class="form-input" id="editProjLanguage" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">توضیحات</label><textarea class="form-textarea" id="editProjDescription"></textarea>
|
<label class="form-label">آدرس مخزن</label>
|
||||||
|
<input type="text" class="form-input" id="editProjRepo" dir="ltr" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">توضیحات</label>
|
||||||
|
<textarea class="form-textarea" id="editProjDescription"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@@ -4283,9 +4302,10 @@
|
|||||||
};
|
};
|
||||||
/* ==================== TOOLBAR MANAGER ==================== */
|
/* ==================== TOOLBAR MANAGER ==================== */
|
||||||
const ToolbarManager = {
|
const ToolbarManager = {
|
||||||
currentFilters: { query: "", typeFilter: "", envFilter: "" },
|
currentFilters: { query: "", typeFilter: "", envFilter: "", ownerFilter: "" },
|
||||||
init() {
|
init() {
|
||||||
this.populateEnvironmentFilter();
|
this.populateEnvironmentFilter();
|
||||||
|
this.populateOwnerFilter();
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
this.updateStats();
|
this.updateStats();
|
||||||
},
|
},
|
||||||
@@ -4298,10 +4318,28 @@
|
|||||||
.map((e) => `<option value="${e.id}">${e.name}</option>`)
|
.map((e) => `<option value="${e.id}">${e.name}</option>`)
|
||||||
.join("");
|
.join("");
|
||||||
},
|
},
|
||||||
|
populateOwnerFilter() {
|
||||||
|
const s = document.getElementById("filterOwner");
|
||||||
|
if (!s) return;
|
||||||
|
const owners = new Set();
|
||||||
|
(projectData.appData.products || []).forEach((p) => {
|
||||||
|
const owner = p.metadata?.owner?.trim();
|
||||||
|
if (owner) owners.add(owner);
|
||||||
|
});
|
||||||
|
const sortedOwners = Array.from(owners).sort((a, b) =>
|
||||||
|
a.localeCompare(b, "fa"),
|
||||||
|
);
|
||||||
|
s.innerHTML =
|
||||||
|
'<option value="">همه مالکان</option>' +
|
||||||
|
sortedOwners
|
||||||
|
.map((o) => `<option value="${o}">${o}</option>`)
|
||||||
|
.join("");
|
||||||
|
},
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
const si = document.getElementById("toolbarSearch"),
|
const si = document.getElementById("toolbarSearch"),
|
||||||
ft = document.getElementById("filterType"),
|
ft = document.getElementById("filterType"),
|
||||||
fe = document.getElementById("filterEnvironment");
|
fe = document.getElementById("filterEnvironment"),
|
||||||
|
fo = document.getElementById("filterOwner");
|
||||||
if (si)
|
if (si)
|
||||||
si.addEventListener("input", () => {
|
si.addEventListener("input", () => {
|
||||||
this.applyFilters();
|
this.applyFilters();
|
||||||
@@ -4317,6 +4355,11 @@
|
|||||||
this.applyFilters();
|
this.applyFilters();
|
||||||
refreshUI();
|
refreshUI();
|
||||||
});
|
});
|
||||||
|
if (fo)
|
||||||
|
fo.addEventListener("change", () => {
|
||||||
|
this.applyFilters();
|
||||||
|
refreshUI();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
applyFilters() {
|
applyFilters() {
|
||||||
this.currentFilters = {
|
this.currentFilters = {
|
||||||
@@ -4324,8 +4367,8 @@
|
|||||||
.trim()
|
.trim()
|
||||||
.toLowerCase(),
|
.toLowerCase(),
|
||||||
typeFilter: document.getElementById("filterType")?.value || "",
|
typeFilter: document.getElementById("filterType")?.value || "",
|
||||||
envFilter:
|
envFilter: document.getElementById("filterEnvironment")?.value || "",
|
||||||
document.getElementById("filterEnvironment")?.value || "",
|
ownerFilter: document.getElementById("filterOwner")?.value || "",
|
||||||
};
|
};
|
||||||
this.updateStats();
|
this.updateStats();
|
||||||
},
|
},
|
||||||
@@ -4350,7 +4393,15 @@
|
|||||||
},
|
},
|
||||||
shouldShowProduct(product) {
|
shouldShowProduct(product) {
|
||||||
const f = this.currentFilters;
|
const f = this.currentFilters;
|
||||||
|
if (!f.query && !f.typeFilter && !f.envFilter && !f.ownerFilter) return true;
|
||||||
|
|
||||||
|
if (f.ownerFilter) {
|
||||||
|
const prodOwner = product.metadata?.owner?.trim();
|
||||||
|
if (prodOwner !== f.ownerFilter) return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!f.query && !f.typeFilter && !f.envFilter) return true;
|
if (!f.query && !f.typeFilter && !f.envFilter) return true;
|
||||||
|
|
||||||
return (product.projects || []).some((pr) => {
|
return (product.projects || []).some((pr) => {
|
||||||
const mq =
|
const mq =
|
||||||
!f.query ||
|
!f.query ||
|
||||||
@@ -4362,6 +4413,7 @@
|
|||||||
(pr.deployments || [])
|
(pr.deployments || [])
|
||||||
.filter((d) => d.status === "active")
|
.filter((d) => d.status === "active")
|
||||||
.some((d) => d.environmentId === f.envFilter);
|
.some((d) => d.environmentId === f.envFilter);
|
||||||
|
|
||||||
return mq && mt && me;
|
return mq && mt && me;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -4891,6 +4943,7 @@
|
|||||||
updateProjectUI();
|
updateProjectUI();
|
||||||
renderToolGroups();
|
renderToolGroups();
|
||||||
ToolbarManager.populateEnvironmentFilter();
|
ToolbarManager.populateEnvironmentFilter();
|
||||||
|
ToolbarManager.populateOwnerFilter();
|
||||||
refreshUI();
|
refreshUI();
|
||||||
showToast("📂 بارگذاری شد", "success");
|
showToast("📂 بارگذاری شد", "success");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -4913,6 +4966,7 @@
|
|||||||
};
|
};
|
||||||
AutosaveManager.clear();
|
AutosaveManager.clear();
|
||||||
ToolbarManager.populateEnvironmentFilter();
|
ToolbarManager.populateEnvironmentFilter();
|
||||||
|
ToolbarManager.populateOwnerFilter();
|
||||||
refreshUI();
|
refreshUI();
|
||||||
showToast("🗑️ پاکسازی شد", "success");
|
showToast("🗑️ پاکسازی شد", "success");
|
||||||
},
|
},
|
||||||
@@ -5132,7 +5186,13 @@
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
getActiveDeployments(proj) {
|
getActiveDeployments(proj) {
|
||||||
return (proj.deployments || []).filter((d) => d.status === "active");
|
return (proj.deployments || [])
|
||||||
|
.filter((d) => d.status === "active")
|
||||||
|
.sort((a, b) => {
|
||||||
|
const dateA = a.deploymentDate ? new Date(a.deploymentDate).getTime() : 0;
|
||||||
|
const dateB = b.deploymentDate ? new Date(b.deploymentDate).getTime() : 0;
|
||||||
|
return dateB - dateA;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
getEnvironmentsWithoutDeployment(proj) {
|
getEnvironmentsWithoutDeployment(proj) {
|
||||||
const allEnvIds = projectData.appData.environments.map((e) => e.id);
|
const allEnvIds = projectData.appData.environments.map((e) => e.id);
|
||||||
@@ -5154,7 +5214,9 @@
|
|||||||
(s, pr) => s + this.getActiveDeployments(pr).length,
|
(s, pr) => s + this.getActiveDeployments(pr).length,
|
||||||
0,
|
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">`;
|
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) {
|
if (projects.length === 0) {
|
||||||
html += '<div class="empty-subprojects">🌿 هیچ پروژهای نیست</div>';
|
html += '<div class="empty-subprojects">🌿 هیچ پروژهای نیست</div>';
|
||||||
} else {
|
} else {
|
||||||
@@ -5163,21 +5225,43 @@
|
|||||||
const ti = this.getTypeInfo(pr.type);
|
const ti = this.getTypeInfo(pr.type);
|
||||||
const deps = pr.dependencies || [];
|
const deps = pr.dependencies || [];
|
||||||
const deploys = this.getActiveDeployments(pr);
|
const deploys = this.getActiveDeployments(pr);
|
||||||
const emptyEnvs = this.getEnvironmentsWithoutDeployment(pr);
|
const archivedDeploys = (pr.deployments || [])
|
||||||
const canAdd = emptyEnvs.length > 0;
|
.filter((d) => d.status === "archived")
|
||||||
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
|
.sort((a, b) => {
|
||||||
? `<div class="detail-info-item"><span class="detail-info-label">وابستگیها (${deps.length})</span><span class="detail-info-value">${deps
|
const dateA = a.archivedAt ? new Date(a.archivedAt).getTime() : 0;
|
||||||
|
const dateB = b.archivedAt ? new Date(b.archivedAt).getTime() : 0;
|
||||||
|
return dateB - dateA;
|
||||||
|
});
|
||||||
|
const hasEnvs = projectData.appData.environments.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">`;
|
||||||
|
|
||||||
|
if (pr.metadata?.owner) {
|
||||||
|
html += `<div class="detail-info-item"><span class="detail-info-label">👤 مالک</span><span class="detail-info-value">${pr.metadata.owner}</span></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pr.metadata?.framework) {
|
||||||
|
html += `<div class="detail-info-item"><span class="detail-info-label">فریمورک</span><span class="detail-info-value">${pr.metadata.framework}</span></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pr.metadata?.language) {
|
||||||
|
html += `<div class="detail-info-item"><span class="detail-info-label">زبان</span><span class="detail-info-value">${pr.metadata.language}</span></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deps.length > 0) {
|
||||||
|
html += `<div class="detail-info-item"><span class="detail-info-label">وابستگیها (${deps.length})</span><span class="detail-info-value">${deps
|
||||||
.map((depId) => {
|
.map((depId) => {
|
||||||
const dp = this.findProjectById(depId);
|
const dp = this.findProjectById(depId);
|
||||||
return dp ? dp.title : depId;
|
return dp ? dp.title : depId;
|
||||||
})
|
})
|
||||||
.join("، ")}</span></div>`
|
.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>`;
|
html += `</div>`;
|
||||||
|
html += `<div class="deployment-section"><div class="deployment-header"><span class="deployment-header-title">🚀 استقرارهای فعال (${deploys.length})</span>${hasEnvs ? `<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) {
|
if (deploys.length === 0) {
|
||||||
html +=
|
html += '<div class="empty-deployments">هنوز استقرار ندارد</div>';
|
||||||
'<div class="empty-deployments">هنوز استقرار ندارد</div>';
|
|
||||||
} else {
|
} else {
|
||||||
html += '<div class="deployment-list">';
|
html += '<div class="deployment-list">';
|
||||||
deploys.forEach((d) => {
|
deploys.forEach((d) => {
|
||||||
@@ -5186,13 +5270,22 @@
|
|||||||
const fd = d.deploymentDate
|
const fd = d.deploymentDate
|
||||||
? new Date(d.deploymentDate).toLocaleString("fa-IR")
|
? 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-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">`;
|
||||||
|
|
||||||
|
html += `<button class="btn btn-ghost btn-xs" onclick="ProductManager.openEditDeploymentModal('${prod.id}','${pr.id}','${d.id}')" title="ویرایش">✏️</button>`;
|
||||||
|
|
||||||
|
html += `<button class="btn btn-xs" style="background:#dd6b20;color:#fff" onclick="ProductManager.archiveDeployment('${prod.id}','${pr.id}','${d.id}')" title="انتقال به تاریخچه">📦 آرشیو</button>`;
|
||||||
|
|
||||||
|
html += `<button class="btn btn-danger btn-xs" onclick="ProductManager.removeDeployment('${prod.id}','${pr.id}','${d.id}')" title="حذف کامل">🗑️</button>`;
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
|
||||||
|
if (d.releaseNotes) {
|
||||||
html += `<div class="deployment-notes">📝 ${d.releaseNotes}</div>`;
|
html += `<div class="deployment-notes">📝 ${d.releaseNotes}</div>`;
|
||||||
if (
|
}
|
||||||
d.requiredDeployments &&
|
|
||||||
d.requiredDeployments.length > 0
|
if (d.requiredDeployments && d.requiredDeployments.length > 0) {
|
||||||
) {
|
|
||||||
html += `<div class="deployment-req">🔗 نیازمندیها: ${d.requiredDeployments
|
html += `<div class="deployment-req">🔗 نیازمندیها: ${d.requiredDeployments
|
||||||
.map((rd) => {
|
.map((rd) => {
|
||||||
const rp = this.findProjectById(rd.projectId);
|
const rp = this.findProjectById(rd.projectId);
|
||||||
@@ -5207,14 +5300,62 @@
|
|||||||
})
|
})
|
||||||
.join("، ")}</div>`;
|
.join("، ")}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
});
|
});
|
||||||
html += "</div>";
|
html += "</div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html += `<div class="deployment-section" style="margin-top:12px;border-top:2px dashed var(--border)">`;
|
||||||
|
html += `<div class="deployment-header">`;
|
||||||
|
html += `<span class="deployment-header-title">📜 تاریخچه استقرارها (${archivedDeploys.length})</span>`;
|
||||||
|
if (archivedDeploys.length > 0) {
|
||||||
|
html += `<button class="btn btn-ghost btn-xs" onclick="ProductManager.toggleArchiveVisibility('${pr.id}')" id="archiveToggle-${pr.id}">👁️ نمایش/مخفی</button>`;
|
||||||
|
}
|
||||||
|
html += `</div>`;
|
||||||
|
|
||||||
|
if (archivedDeploys.length === 0) {
|
||||||
|
html += `<div class="empty-deployments" style="opacity:0.6">هنوز استقراری آرشیو نشده است</div>`;
|
||||||
|
} else {
|
||||||
|
html += `<div class="deployment-list" id="archiveList-${pr.id}" style="display:none">`;
|
||||||
|
archivedDeploys.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")
|
||||||
|
: "—";
|
||||||
|
const archivedDate = d.archivedAt
|
||||||
|
? new Date(d.archivedAt).toLocaleString("fa-IR")
|
||||||
|
: "—";
|
||||||
|
|
||||||
|
html += `<div class="deployment-item" style="opacity:0.75;background:var(--border-light)">`;
|
||||||
|
html += `<div class="deployment-item-info">`;
|
||||||
|
html += `<span class="deployment-env-tag" style="background-color:${cl.backgroundColor};color:${cl.textColor};opacity:0.7">${en}</span>`;
|
||||||
|
html += `<span class="deployment-version" style="opacity:0.7">v${d.version}</span>`;
|
||||||
|
html += `<span class="deployment-date">📅 ${fd}</span>`;
|
||||||
|
html += `<span class="deployment-date" style="color:var(--warning)">📦 آرشیو: ${archivedDate}</span>`;
|
||||||
|
html += `</div>`;
|
||||||
|
html += `<div class="deployment-actions">`;
|
||||||
|
html += `<button class="btn btn-xs" style="background:#3182ce;color:#fff" onclick="ProductManager.restoreDeployment('${prod.id}','${pr.id}','${d.id}')" title="بازگرداندن به لیست فعال">↩️ بازگردانی</button>`;
|
||||||
|
html += `<button class="btn btn-danger btn-xs" onclick="ProductManager.removeDeployment('${prod.id}','${pr.id}','${d.id}')" title="حذف کامل">🗑️</button>`;
|
||||||
|
html += `</div>`;
|
||||||
|
|
||||||
|
if (d.releaseNotes) {
|
||||||
|
html += `<div class="deployment-notes" style="opacity:0.7">📝 ${d.releaseNotes}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
});
|
||||||
|
html += `</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
|
||||||
html += `</div></div>`;
|
html += `</div></div>`;
|
||||||
});
|
});
|
||||||
html += "</div>";
|
html += "</div>";
|
||||||
}
|
}
|
||||||
|
|
||||||
html += `</div></div>`;
|
html += `</div></div>`;
|
||||||
document.getElementById("projectDetailBody").innerHTML = html;
|
document.getElementById("projectDetailBody").innerHTML = html;
|
||||||
ModalManager.open("projectDetailModal");
|
ModalManager.open("projectDetailModal");
|
||||||
@@ -5259,6 +5400,7 @@
|
|||||||
});
|
});
|
||||||
ModalManager.close("addProductModal");
|
ModalManager.close("addProductModal");
|
||||||
refreshUI();
|
refreshUI();
|
||||||
|
ToolbarManager.populateOwnerFilter();
|
||||||
showToast(`"${title}" ایجاد شد`, "success");
|
showToast(`"${title}" ایجاد شد`, "success");
|
||||||
},
|
},
|
||||||
openDependencyManager(productId, projId) {
|
openDependencyManager(productId, projId) {
|
||||||
@@ -5449,6 +5591,7 @@
|
|||||||
prod.metadata.updatedAt = new Date().toISOString();
|
prod.metadata.updatedAt = new Date().toISOString();
|
||||||
ModalManager.close("productEditModal");
|
ModalManager.close("productEditModal");
|
||||||
refreshUI();
|
refreshUI();
|
||||||
|
ToolbarManager.populateOwnerFilter();
|
||||||
this.showDetail(pid);
|
this.showDetail(pid);
|
||||||
showToast(`"${prod.title}" ویرایش شد`, "success");
|
showToast(`"${prod.title}" ویرایش شد`, "success");
|
||||||
},
|
},
|
||||||
@@ -5460,6 +5603,7 @@
|
|||||||
document.getElementById("newProjectTitle").value = "";
|
document.getElementById("newProjectTitle").value = "";
|
||||||
document.getElementById("newProjectType").value = "";
|
document.getElementById("newProjectType").value = "";
|
||||||
document.getElementById("newProjectFramework").value = "";
|
document.getElementById("newProjectFramework").value = "";
|
||||||
|
document.getElementById("newProjectOwner").value = ""; // 🆕
|
||||||
document.getElementById("newProjectLanguage").value = "";
|
document.getElementById("newProjectLanguage").value = "";
|
||||||
document.getElementById("newProjectRepo").value = "";
|
document.getElementById("newProjectRepo").value = "";
|
||||||
document.getElementById("newProjectDescription").value = "";
|
document.getElementById("newProjectDescription").value = "";
|
||||||
@@ -5482,20 +5626,18 @@
|
|||||||
const np = {
|
const np = {
|
||||||
id: `proj-${Date.now()}`,
|
id: `proj-${Date.now()}`,
|
||||||
title,
|
title,
|
||||||
description:
|
description: document.getElementById("newProjectDescription").value.trim() || "",
|
||||||
document.getElementById("newProjectDescription").value.trim() ||
|
|
||||||
"",
|
|
||||||
type,
|
type,
|
||||||
repositoryUrl: document
|
repositoryUrl: document.getElementById("newProjectRepo").value.trim(),
|
||||||
.getElementById("newProjectRepo")
|
metadata: {
|
||||||
.value.trim(),
|
createdAt: new Date().toISOString(),
|
||||||
metadata: { createdAt: new Date().toISOString(), customFields: {} },
|
owner: document.getElementById("newProjectOwner").value.trim() || "", // 🆕
|
||||||
|
customFields: {}
|
||||||
|
},
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
deployments: [],
|
deployments: [],
|
||||||
};
|
};
|
||||||
const fw = document
|
const fw = document.getElementById("newProjectFramework").value.trim();
|
||||||
.getElementById("newProjectFramework")
|
|
||||||
.value.trim();
|
|
||||||
if (fw) np.metadata.framework = fw;
|
if (fw) np.metadata.framework = fw;
|
||||||
const lg = document.getElementById("newProjectLanguage").value.trim();
|
const lg = document.getElementById("newProjectLanguage").value.trim();
|
||||||
if (lg) np.metadata.language = lg;
|
if (lg) np.metadata.language = lg;
|
||||||
@@ -5506,9 +5648,7 @@
|
|||||||
showToast(`"${title}" ایجاد شد`, "success");
|
showToast(`"${title}" ایجاد شد`, "success");
|
||||||
},
|
},
|
||||||
openProjectEditModal(productId, projId) {
|
openProjectEditModal(productId, projId) {
|
||||||
const prod = projectData.appData.products.find(
|
const prod = projectData.appData.products.find((p) => p.id === productId);
|
||||||
(p) => p.id === productId,
|
|
||||||
);
|
|
||||||
if (!prod) return;
|
if (!prod) return;
|
||||||
const pr = prod.projects.find((p) => p.id === projId);
|
const pr = prod.projects.find((p) => p.id === projId);
|
||||||
if (!pr) return;
|
if (!pr) return;
|
||||||
@@ -5517,14 +5657,11 @@
|
|||||||
document.getElementById("editProjId").value = pr.id;
|
document.getElementById("editProjId").value = pr.id;
|
||||||
document.getElementById("editProjTitle").value = pr.title;
|
document.getElementById("editProjTitle").value = pr.title;
|
||||||
document.getElementById("editProjType").value = pr.type;
|
document.getElementById("editProjType").value = pr.type;
|
||||||
document.getElementById("editProjFramework").value =
|
document.getElementById("editProjFramework").value = pr.metadata?.framework || "";
|
||||||
pr.metadata?.framework || "";
|
document.getElementById("editProjOwner").value = pr.metadata?.owner || ""; // 🆕
|
||||||
document.getElementById("editProjLanguage").value =
|
document.getElementById("editProjLanguage").value = pr.metadata?.language || "";
|
||||||
pr.metadata?.language || "";
|
document.getElementById("editProjRepo").value = pr.repositoryUrl || "";
|
||||||
document.getElementById("editProjRepo").value =
|
document.getElementById("editProjDescription").value = pr.description || "";
|
||||||
pr.repositoryUrl || "";
|
|
||||||
document.getElementById("editProjDescription").value =
|
|
||||||
pr.description || "";
|
|
||||||
ModalManager.open("editProjectModal");
|
ModalManager.open("editProjectModal");
|
||||||
},
|
},
|
||||||
saveProjectEdit() {
|
saveProjectEdit() {
|
||||||
@@ -5545,15 +5682,15 @@
|
|||||||
AutosaveManager.scheduleSave();
|
AutosaveManager.scheduleSave();
|
||||||
pr.title = nt;
|
pr.title = nt;
|
||||||
pr.type = nty;
|
pr.type = nty;
|
||||||
pr.description =
|
pr.description = document.getElementById("editProjDescription").value.trim() || "";
|
||||||
document.getElementById("editProjDescription").value.trim() || "";
|
pr.repositoryUrl = document.getElementById("editProjRepo").value.trim();
|
||||||
pr.repositoryUrl = document
|
|
||||||
.getElementById("editProjRepo")
|
|
||||||
.value.trim();
|
|
||||||
if (!pr.metadata) pr.metadata = {};
|
if (!pr.metadata) pr.metadata = {};
|
||||||
const fw = document.getElementById("editProjFramework").value.trim();
|
const fw = document.getElementById("editProjFramework").value.trim();
|
||||||
if (fw) pr.metadata.framework = fw;
|
if (fw) pr.metadata.framework = fw;
|
||||||
else delete pr.metadata.framework;
|
else delete pr.metadata.framework;
|
||||||
|
const owner = document.getElementById("editProjOwner").value.trim(); // 🆕
|
||||||
|
if (owner) pr.metadata.owner = owner;
|
||||||
|
else delete pr.metadata.owner;
|
||||||
const lg = document.getElementById("editProjLanguage").value.trim();
|
const lg = document.getElementById("editProjLanguage").value.trim();
|
||||||
if (lg) pr.metadata.language = lg;
|
if (lg) pr.metadata.language = lg;
|
||||||
else delete pr.metadata.language;
|
else delete pr.metadata.language;
|
||||||
@@ -5565,36 +5702,39 @@
|
|||||||
},
|
},
|
||||||
openAddDeploymentModal(productId, projId) {
|
openAddDeploymentModal(productId, projId) {
|
||||||
if (!productId || !projId) return;
|
if (!productId || !projId) return;
|
||||||
const prod = projectData.appData.products.find(
|
const prod = projectData.appData.products.find((p) => p.id === productId);
|
||||||
(p) => p.id === productId,
|
|
||||||
);
|
|
||||||
if (!prod) return;
|
if (!prod) return;
|
||||||
const pr = prod.projects.find((p) => p.id === projId);
|
const pr = prod.projects.find((p) => p.id === projId);
|
||||||
if (!pr) return;
|
if (!pr) return;
|
||||||
const emptyEnvs = this.getEnvironmentsWithoutDeployment(pr);
|
|
||||||
if (emptyEnvs.length === 0) {
|
const allEnvs = projectData.appData.environments;
|
||||||
showToast("همه محیطها استقرار دارند", "warning");
|
if (allEnvs.length === 0) {
|
||||||
|
showToast("ابتدا یک محیط عملیاتی ایجاد کنید", "warning");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentDeploymentProjId = projId;
|
this.currentDeploymentProjId = projId;
|
||||||
this.currentDeploymentMode = "add";
|
this.currentDeploymentMode = "add";
|
||||||
this.currentDeploymentEnvId = null;
|
this.currentDeploymentEnvId = null;
|
||||||
this.currentEditDeploymentId = null;
|
this.currentEditDeploymentId = null;
|
||||||
|
|
||||||
document.getElementById("deploymentModalTitle").innerHTML =
|
document.getElementById("deploymentModalTitle").innerHTML =
|
||||||
"<span>🚀</span><span>افزودن نسخه استقرار</span>";
|
"<span>🚀</span><span>افزودن نسخه استقرار</span>";
|
||||||
|
|
||||||
const envGroup = document.getElementById("deploymentEnvGroup");
|
const envGroup = document.getElementById("deploymentEnvGroup");
|
||||||
const envs = projectData.appData.environments.filter((e) =>
|
const envs = allEnvs;
|
||||||
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
|
envGroup.innerHTML = `<label class="form-label">محیط استقرار <span class="required">*</span></label><select class="form-select" id="depEnvironment" onchange="ProductManager.onEnvChange()">${envs
|
||||||
.map((e) => {
|
.map((e) => {
|
||||||
const cl = this.getEnvironmentColors(e.id);
|
const cl = this.getEnvironmentColors(e.id);
|
||||||
return `<option value="${e.id}" style="background-color:${cl.backgroundColor};color:${cl.textColor}">${e.name} - ${e.description}</option>`;
|
return `<option value="${e.id}" style="background-color:${cl.backgroundColor};color:${cl.textColor}">${e.name} - ${e.description}</option>`;
|
||||||
})
|
})
|
||||||
.join("")}</select>`;
|
.join("")}</select>`;
|
||||||
|
|
||||||
document.getElementById("depVersion").value = "";
|
document.getElementById("depVersion").value = "";
|
||||||
document.getElementById("depDate").value = "";
|
document.getElementById("depDate").value = "";
|
||||||
document.getElementById("depReleaseNotes").value = "";
|
document.getElementById("depReleaseNotes").value = "";
|
||||||
|
|
||||||
this.renderRequiredDeploymentsSection();
|
this.renderRequiredDeploymentsSection();
|
||||||
ModalManager.open("deploymentModal");
|
ModalManager.open("deploymentModal");
|
||||||
},
|
},
|
||||||
@@ -5762,7 +5902,26 @@
|
|||||||
this.showDetail(pid);
|
this.showDetail(pid);
|
||||||
},
|
},
|
||||||
removeDeployment(productId, projId, depId) {
|
removeDeployment(productId, projId, depId) {
|
||||||
if (!confirm("حذف این استقرار؟")) return;
|
if (!confirm("⚠️ حذف کامل این استقرار؟\n\nاین عمل غیرقابل بازگشت است.\nاگر میخواهید فقط از لیست فعال خارج شود، از دکمه «آرشیو» استفاده کنید.")) 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;
|
||||||
|
|
||||||
|
HistoryManager.pushState();
|
||||||
|
AutosaveManager.scheduleSave();
|
||||||
|
|
||||||
|
pr.deployments = (pr.deployments || []).filter((d) => d.id !== depId);
|
||||||
|
|
||||||
|
refreshUI();
|
||||||
|
this.showDetail(productId);
|
||||||
|
showToast("🗑️ استقرار بهطور کامل حذف شد", "warning");
|
||||||
|
},
|
||||||
|
archiveDeployment(productId, projId, depId) {
|
||||||
|
if (!confirm("📦 انتقال این استقرار به تاریخچه؟\n\nاستقرار از لیست فعال خارج شده و در بخش تاریخچه نگهداری میشود.")) return;
|
||||||
|
|
||||||
const prod = projectData.appData.products.find(
|
const prod = projectData.appData.products.find(
|
||||||
(p) => p.id === productId,
|
(p) => p.id === productId,
|
||||||
);
|
);
|
||||||
@@ -5771,12 +5930,16 @@
|
|||||||
if (!pr) return;
|
if (!pr) return;
|
||||||
const dep = (pr.deployments || []).find((d) => d.id === depId);
|
const dep = (pr.deployments || []).find((d) => d.id === depId);
|
||||||
if (!dep) return;
|
if (!dep) return;
|
||||||
|
|
||||||
HistoryManager.pushState();
|
HistoryManager.pushState();
|
||||||
AutosaveManager.scheduleSave();
|
AutosaveManager.scheduleSave();
|
||||||
dep.status = "removed";
|
|
||||||
|
dep.status = "archived";
|
||||||
|
dep.archivedAt = new Date().toISOString();
|
||||||
|
|
||||||
refreshUI();
|
refreshUI();
|
||||||
this.showDetail(productId);
|
this.showDetail(productId);
|
||||||
showToast("استقرار حذف شد", "warning");
|
showToast("📦 استقرار به تاریخچه منتقل شد", "success");
|
||||||
},
|
},
|
||||||
removeProduct(productId) {
|
removeProduct(productId) {
|
||||||
const prod = projectData.appData.products.find(
|
const prod = projectData.appData.products.find(
|
||||||
@@ -5790,6 +5953,7 @@
|
|||||||
(p) => p.id !== productId,
|
(p) => p.id !== productId,
|
||||||
);
|
);
|
||||||
refreshUI();
|
refreshUI();
|
||||||
|
ToolbarManager.populateOwnerFilter();
|
||||||
ModalManager.close("projectDetailModal");
|
ModalManager.close("projectDetailModal");
|
||||||
showToast(`"${prod.title}" حذف شد`, "warning");
|
showToast(`"${prod.title}" حذف شد`, "warning");
|
||||||
},
|
},
|
||||||
@@ -5809,6 +5973,35 @@
|
|||||||
this.showDetail(productId);
|
this.showDetail(productId);
|
||||||
showToast(`"${pr.title}" حذف شد`, "warning");
|
showToast(`"${pr.title}" حذف شد`, "warning");
|
||||||
},
|
},
|
||||||
|
toggleArchiveVisibility(projId) {
|
||||||
|
const list = document.getElementById(`archiveList-${projId}`);
|
||||||
|
if (!list) return;
|
||||||
|
const isHidden = list.style.display === "none";
|
||||||
|
list.style.display = isHidden ? "flex" : "none";
|
||||||
|
},
|
||||||
|
restoreDeployment(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();
|
||||||
|
AutosaveManager.scheduleSave();
|
||||||
|
|
||||||
|
// ✅ بازگرداندن به وضعیت active
|
||||||
|
dep.status = "active";
|
||||||
|
delete dep.archivedAt;
|
||||||
|
|
||||||
|
refreshUI();
|
||||||
|
this.showDetail(productId);
|
||||||
|
showToast("↩️ استقرار به لیست فعال بازگردانده شد", "success");
|
||||||
|
},
|
||||||
};
|
};
|
||||||
/* ==================== DASHBOARD MANAGER ==================== */
|
/* ==================== DASHBOARD MANAGER ==================== */
|
||||||
const DashboardManager = {
|
const DashboardManager = {
|
||||||
@@ -6340,6 +6533,35 @@
|
|||||||
const fm = gf.append("feMerge");
|
const fm = gf.append("feMerge");
|
||||||
fm.append("feMergeNode").attr("in", "blur");
|
fm.append("feMergeNode").attr("in", "blur");
|
||||||
fm.append("feMergeNode").attr("in", "SourceGraphic");
|
fm.append("feMergeNode").attr("in", "SourceGraphic");
|
||||||
|
|
||||||
|
defs.append("marker")
|
||||||
|
.attr("id", "arrowDependency")
|
||||||
|
.attr("viewBox", "0 -5 10 10")
|
||||||
|
.attr("refX", 10) // نقطه مرجع در نوک فلش
|
||||||
|
.attr("refY", 0)
|
||||||
|
.attr("markerWidth", 8) // عرض فلش
|
||||||
|
.attr("markerHeight", 8) // ارتفاع فلش
|
||||||
|
.attr("orient", "auto")
|
||||||
|
.attr("markerUnits", "userSpaceOnUse")
|
||||||
|
.append("path")
|
||||||
|
.attr("d", "M0,-5L10,0L0,5") // شکل مثلثی فلش
|
||||||
|
.attr("fill", "#e0c789"); // رنگ طلایی هماهنگ با لینک وابستگی
|
||||||
|
|
||||||
|
// 🆕 Marker برای لینکهای ownership (اختیاری - بدون فلش یا فلش کوچکتر)
|
||||||
|
defs.append("marker")
|
||||||
|
.attr("id", "arrowOwnership")
|
||||||
|
.attr("viewBox", "0 -5 10 10")
|
||||||
|
.attr("refX", 10)
|
||||||
|
.attr("refY", 0)
|
||||||
|
.attr("markerWidth", 6)
|
||||||
|
.attr("markerHeight", 6)
|
||||||
|
.attr("orient", "auto")
|
||||||
|
.attr("markerUnits", "userSpaceOnUse")
|
||||||
|
.append("path")
|
||||||
|
.attr("d", "M0,-4L10,0L0,4")
|
||||||
|
.attr("fill", "#4a5568")
|
||||||
|
.attr("opacity", 0.7);
|
||||||
|
|
||||||
this.zoomGroup = svg.append("g");
|
this.zoomGroup = svg.append("g");
|
||||||
this.zoomBehavior = d3
|
this.zoomBehavior = d3
|
||||||
.zoom()
|
.zoom()
|
||||||
@@ -6370,10 +6592,38 @@
|
|||||||
d.type === "dependency" ? "#e0c789" : "#4a5568",
|
d.type === "dependency" ? "#e0c789" : "#4a5568",
|
||||||
)
|
)
|
||||||
.attr("stroke-width", (d) => (d.type === "dependency" ? 2 : 1))
|
.attr("stroke-width", (d) => (d.type === "dependency" ? 2 : 1))
|
||||||
.attr("stroke-opacity", 0.6)
|
.attr("stroke-opacity", (d) => (d.type === "dependency" ? 0.85 : 0.4))
|
||||||
.attr("stroke-dasharray", (d) =>
|
.attr("stroke-dasharray", (d) =>
|
||||||
d.type === "dependency" ? "5,3" : "none",
|
d.type === "dependency" ? "5,3" : "none",
|
||||||
|
)
|
||||||
|
.attr("marker-end", (d) =>
|
||||||
|
d.type === "dependency" ? "url(#arrowDependency)" : "url(#arrowOwnership)"
|
||||||
);
|
);
|
||||||
|
this.linkElements
|
||||||
|
.style("cursor", "pointer")
|
||||||
|
.on("mouseover", function (e, d) {
|
||||||
|
d3.select(this)
|
||||||
|
.attr("stroke-opacity", 1)
|
||||||
|
.attr("stroke-width", d.type === "dependency" ? 3 : 2);
|
||||||
|
|
||||||
|
const sourceNode = this.__data__.source;
|
||||||
|
const targetNode = this.__data__.target;
|
||||||
|
const sourceLabel = sourceNode.label || sourceNode.id;
|
||||||
|
const targetLabel = targetNode.label || targetNode.id;
|
||||||
|
|
||||||
|
const tooltipText = d.type === "dependency"
|
||||||
|
? `🔗 ${sourceLabel} → ${targetLabel}`
|
||||||
|
: `📦 ${targetLabel} شامل ${sourceLabel}`;
|
||||||
|
|
||||||
|
showD3Tooltip(e, tooltipText);
|
||||||
|
})
|
||||||
|
.on("mousemove", (e) => showD3Tooltip(e, ""))
|
||||||
|
.on("mouseout", function (e, d) {
|
||||||
|
d3.select(this)
|
||||||
|
.attr("stroke-opacity", d.type === "dependency" ? 0.85 : 0.4)
|
||||||
|
.attr("stroke-width", d.type === "dependency" ? 2 : 1);
|
||||||
|
hideD3Tooltip();
|
||||||
|
});
|
||||||
const node = this.zoomGroup
|
const node = this.zoomGroup
|
||||||
.append("g")
|
.append("g")
|
||||||
.selectAll("g")
|
.selectAll("g")
|
||||||
@@ -6437,14 +6687,46 @@
|
|||||||
this.toggleCollapseProduct(d.id);
|
this.toggleCollapseProduct(d.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getEdgePoint = (source, target, targetRadius) => {
|
||||||
|
const dx = target.x - source.x;
|
||||||
|
const dy = target.y - source.y;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy) || 1;
|
||||||
|
const offset = targetRadius + 10;
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: target.x - (dx / distance) * offset,
|
||||||
|
y: target.y - (dy / distance) * offset,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
sim.on("tick", () => {
|
sim.on("tick", () => {
|
||||||
this.linkElements
|
this.linkElements
|
||||||
.attr("x1", (d) => d.source.x)
|
.attr("x1", (d) => {
|
||||||
.attr("y1", (d) => d.source.y)
|
// نقطه شروع: روی مرز گره منبع
|
||||||
.attr("x2", (d) => d.target.x)
|
const dx = d.target.x - d.source.x;
|
||||||
.attr("y2", (d) => d.target.y);
|
const dy = d.target.y - d.source.y;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy) || 1;
|
||||||
|
return d.source.x + (dx / distance) * d.source.radius;
|
||||||
|
})
|
||||||
|
.attr("y1", (d) => {
|
||||||
|
const dx = d.target.x - d.source.x;
|
||||||
|
const dy = d.target.y - d.source.y;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy) || 1;
|
||||||
|
return d.source.y + (dy / distance) * d.source.radius;
|
||||||
|
})
|
||||||
|
.attr("x2", (d) => {
|
||||||
|
// نقطه پایان: روی مرز گره هدف (با offset برای فلش)
|
||||||
|
const edge = getEdgePoint(d.source, d.target, d.target.radius);
|
||||||
|
return edge.x;
|
||||||
|
})
|
||||||
|
.attr("y2", (d) => {
|
||||||
|
const edge = getEdgePoint(d.source, d.target, d.target.radius);
|
||||||
|
return edge.y;
|
||||||
|
});
|
||||||
node.attr("transform", (d) => `translate(${d.x},${d.y})`);
|
node.attr("transform", (d) => `translate(${d.x},${d.y})`);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.renderLegend();
|
this.renderLegend();
|
||||||
this.updateStats(nodes, links);
|
this.updateStats(nodes, links);
|
||||||
this.setupSearch();
|
this.setupSearch();
|
||||||
@@ -6710,6 +6992,7 @@
|
|||||||
updateProjectUI();
|
updateProjectUI();
|
||||||
renderToolGroups();
|
renderToolGroups();
|
||||||
ToolbarManager.populateEnvironmentFilter();
|
ToolbarManager.populateEnvironmentFilter();
|
||||||
|
ToolbarManager.populateOwnerFilter();
|
||||||
refreshUI();
|
refreshUI();
|
||||||
if (ReleaseBoardsManager) {
|
if (ReleaseBoardsManager) {
|
||||||
ReleaseBoardsManager.render(true);
|
ReleaseBoardsManager.render(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user