Ajout : - e2e/csp_preview.spec.js — apercu CSP strict SANS deployer : le build officiel @alpinejs/csp (e2e/fixtures/alpine.csp.js, 0 eval) est servi a la place de alpine.min.js par interception Playwright ; toute expression que le parseur maison ne digere pas = pageerror (filet). Premiere surface VERTE : library (composant lie, icones SVG via Alpine.effect, recherche ouverte + focalisee, 0 erreur). Changed : - 16 composants x-data="fn()" enregistres via Alpine.data (registre = seule resolution du build CSP, probe « Undefined variable » ; scripts classiques executes pendant le parsing => alpine:init toujours joint) : appState, libraryPage, workspacesPage, editorState, board x4, settings/import/table_view/team_load/trash/workspace/welcome/accounts/ card_detail. - base.html (shell) migre : x-effect document.* -> syncSidebarClass(), $nextTick(arrow) -> initSidebarSort(), window.FlowDeck.* -> fdCreatePage/fdCreateFolder/fdGwRefresh, Object.keys/Math.min/ window.innerWidth dans x-for et :style -> sidebarSections()/ sectionMenuPos() — tout = simple appel de methode. - x-html restants du shell -> x-init + Alpine.effect : icone agent, carte projet, library x3 ; recherche library -> toggleSearch() (Alpine.nextTick) ; openMoveSelected() pour Object.keys en expression. - eslint : 70 warnings -> 0/0 (globals getCsrf depuis A38 ph1, /* exported openCardDetail */ + /* global owner, repo */, 3 ;; residuels). suite **1093/1093** · ruff OK · eslint 0/0 · E2E 3/3 (csp_preview + smoke x2) · docs a jour (ROADMAP ph3 LOT 1, CHANGELOG, WORKLOAD, VERSION)
160 lines
5.3 KiB
JavaScript
160 lines
5.3 KiB
JavaScript
/* exported workspacesPage -- appeles depuis les attributs HTML des templates */
|
|
|
|
// A20 phase 3 : registre Alpine.data (le build CSP ne résout pas les
|
|
|
|
// globales window — probe « Undefined variable: workspacesPage »).
|
|
|
|
document.addEventListener('alpine:init', function () { Alpine.data('workspacesPage', workspacesPage); });
|
|
function workspacesPage() {
|
|
return {
|
|
workspaces: [],
|
|
activeId: null,
|
|
showCreate: false,
|
|
showRename: false,
|
|
wsName: '',
|
|
renameTarget: null,
|
|
|
|
// Gitea
|
|
giteaStatus: 'loading', // loading|ok|not_linked|error
|
|
giteaGroups: [],
|
|
giteaTotal: 0,
|
|
activeOrg: 'all',
|
|
giteaSearch: '',
|
|
|
|
get filteredGroups() {
|
|
var self = this;
|
|
var q = (this.giteaSearch || '').toLowerCase();
|
|
var groups = this.activeOrg === 'all' ? this.giteaGroups : this.giteaGroups.filter(function(g) { return g.org === self.activeOrg; });
|
|
if (!q) return groups;
|
|
return groups.map(function(g) {
|
|
return {org: g.org, avatar: g.avatar, projects: g.projects.filter(function(p) {
|
|
return p.name.toLowerCase().includes(q) || (p.description||'').toLowerCase().includes(q);
|
|
})};
|
|
}).filter(function(g) { return g.projects.length > 0; });
|
|
},
|
|
|
|
async init() {
|
|
await this.load();
|
|
await this.loadGitea();
|
|
},
|
|
|
|
async load() {
|
|
const r = await fetch('/api/workspaces');
|
|
const d = await r.json();
|
|
this.workspaces = d.workspaces || [];
|
|
this.activeId = d.active_id;
|
|
},
|
|
|
|
async selectLocal(ws) {
|
|
await fetch(`/api/workspaces/${ws.id}/select`, {headers: {'X-CSRF-Token': getCsrf()}, method:'POST'});
|
|
window.location = '/local-workspace';
|
|
},
|
|
|
|
async doCreate() {
|
|
if (!this.wsName.trim()) return;
|
|
await fetch('/api/workspaces', {
|
|
method:'POST',
|
|
headers:{'X-CSRF-Token': getCsrf(), 'Content-Type':'application/json'},
|
|
body:JSON.stringify({name:this.wsName.trim()})
|
|
});
|
|
this.wsName = '';
|
|
this.showCreate = false;
|
|
await this.load();
|
|
},
|
|
|
|
renameWs(ws) {
|
|
this.renameTarget = ws;
|
|
this.wsName = ws.name;
|
|
this.showRename = true;
|
|
},
|
|
|
|
async doRename() {
|
|
if (!this.wsName.trim()||!this.renameTarget) return;
|
|
await fetch(`/api/workspaces/${this.renameTarget.id}`, {
|
|
method:'PUT',
|
|
headers:{'X-CSRF-Token': getCsrf(), 'Content-Type':'application/json'},
|
|
body:JSON.stringify({name:this.wsName.trim()})
|
|
});
|
|
this.wsName = '';
|
|
this.showRename = false;
|
|
this.renameTarget = null;
|
|
await this.load();
|
|
},
|
|
|
|
async deleteWs(ws) {
|
|
if (!confirm(`Delete workspace "${ws.name}" and all its pages?`)) return;
|
|
await fetch(`/api/workspaces/${ws.id}`, {headers: {'X-CSRF-Token': getCsrf()}, method:'DELETE'});
|
|
await this.load();
|
|
},
|
|
|
|
// ── Gitea ──
|
|
|
|
async loadGitea() {
|
|
this.giteaStatus = 'loading';
|
|
try {
|
|
// Get orgs
|
|
const orgsRes = await fetch('/api/gitea/orgs');
|
|
if (orgsRes.status === 401) { this.giteaStatus = 'not_linked'; return; }
|
|
if (!orgsRes.ok) { this.giteaStatus = 'error'; return; }
|
|
const orgsData = await orgsRes.json();
|
|
const orgs = orgsData.orgs || [];
|
|
|
|
// Fetch repos: user repos + org repos
|
|
const groups = [];
|
|
|
|
// Get username for the "personal" tab
|
|
let myLogin = 'Me';
|
|
try {
|
|
const meRes = await fetch('/auth/user');
|
|
if (meRes.ok) {
|
|
const meData = await meRes.json();
|
|
myLogin = meData.user?.login || 'Me';
|
|
if (myLogin.includes('_')) myLogin = myLogin.split('_').pop(); // strip gitea_ prefix if present
|
|
}
|
|
} catch { /* volontaire */ }
|
|
|
|
// User repos (no org filter)
|
|
try {
|
|
const userRes = await fetch('/api/gitea/projects');
|
|
if (userRes.ok) {
|
|
const d = await userRes.json();
|
|
const repos = d.projects || [];
|
|
if (repos.length) {
|
|
groups.push({org: myLogin, avatar: '', projects: repos.map(r => ({...r, owner: r.owner || {login: myLogin}}))});
|
|
}
|
|
}
|
|
} catch { /* volontaire */ }
|
|
|
|
// Org repos
|
|
for (const org of orgs) {
|
|
try {
|
|
const res = await fetch('/api/gitea/projects?org=' + encodeURIComponent(org.username || org.login || org.name));
|
|
if (res.ok) {
|
|
const d = await res.json();
|
|
if (d.projects && d.projects.length) {
|
|
groups.push({org: org.username || org.login || org.name, avatar: org.avatar_url || '', projects: d.projects});
|
|
}
|
|
}
|
|
} catch { /* volontaire */ }
|
|
}
|
|
|
|
this.giteaGroups = groups;
|
|
this.giteaTotal = groups.reduce((sum, g) => sum + g.projects.length, 0);
|
|
this.giteaStatus = 'ok';
|
|
} catch {
|
|
this.giteaStatus = 'error';
|
|
}
|
|
},
|
|
|
|
openGitea(proj) {
|
|
const owner = proj.owner?.login || proj.owner?.username || proj.full_name?.split('/')[0] || '';
|
|
const repo = proj.name;
|
|
if (owner && repo) {
|
|
// Set workspace cookie so sidebar shows tree
|
|
document.cookie = 'flowdeck_workspace=gitea:' + owner + ':' + repo + ';path=/;SameSite=Lax';
|
|
window.location = `/gitea-workspace?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}`;
|
|
}
|
|
}
|
|
};
|
|
}
|