clay-server 2.22.0-beta.2 → 2.22.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/daemon.js +11 -21
- package/lib/project.js +9 -0
- package/lib/public/app.js +3 -0
- package/lib/public/css/icon-strip.css +107 -0
- package/lib/public/modules/sidebar.js +169 -0
- package/lib/server.js +31 -12
- package/package.json +1 -1
package/lib/daemon.js
CHANGED
|
@@ -160,11 +160,10 @@ var relay = createServer({
|
|
|
160
160
|
var slugs = config.projects.map(function (p) { return p.slug; });
|
|
161
161
|
var slug = generateSlug(absPath, slugs);
|
|
162
162
|
relay.addProject(absPath, slug);
|
|
163
|
-
var projectEntry = { path: absPath, slug: slug, addedAt: Date.now() };
|
|
164
|
-
//
|
|
165
|
-
if (wsUser && wsUser.id
|
|
163
|
+
var projectEntry = { path: absPath, slug: slug, addedAt: Date.now(), visibility: "private" };
|
|
164
|
+
// The user who adds a project always becomes the owner
|
|
165
|
+
if (wsUser && wsUser.id) {
|
|
166
166
|
projectEntry.ownerId = wsUser.id;
|
|
167
|
-
projectEntry.visibility = "private";
|
|
168
167
|
}
|
|
169
168
|
config.projects.push(projectEntry);
|
|
170
169
|
// Remove from removedProjects if present
|
|
@@ -237,15 +236,10 @@ var relay = createServer({
|
|
|
237
236
|
try { fs.rmSync(targetDir, { recursive: true, force: true }); } catch (ce) {}
|
|
238
237
|
return { ok: false, error: "Failed to create project: " + e.message };
|
|
239
238
|
}
|
|
240
|
-
// Register project
|
|
241
|
-
var projectEntry = { path: targetDir, slug: slug, addedAt: Date.now() };
|
|
239
|
+
// Register project - creator always becomes owner, default private
|
|
240
|
+
var projectEntry = { path: targetDir, slug: slug, addedAt: Date.now(), visibility: "private" };
|
|
242
241
|
if (wsUser && wsUser.id) {
|
|
243
|
-
|
|
244
|
-
projectEntry.ownerId = wsUser.id;
|
|
245
|
-
}
|
|
246
|
-
if (wsUser.role !== "admin") {
|
|
247
|
-
projectEntry.visibility = "private";
|
|
248
|
-
}
|
|
242
|
+
projectEntry.ownerId = wsUser.id;
|
|
249
243
|
}
|
|
250
244
|
relay.addProject(targetDir, slug);
|
|
251
245
|
config.projects.push(projectEntry);
|
|
@@ -312,15 +306,11 @@ var relay = createServer({
|
|
|
312
306
|
execSync("chown -R " + wsUser.linuxUser + ":" + wsUser.linuxUser + " " + JSON.stringify(targetDir));
|
|
313
307
|
} catch (e) {}
|
|
314
308
|
}
|
|
315
|
-
// Register project
|
|
316
|
-
|
|
309
|
+
// Register project - creator always becomes owner
|
|
310
|
+
// Creator always becomes owner, default private
|
|
311
|
+
var projectEntry = { path: targetDir, slug: slug, addedAt: Date.now(), visibility: "private" };
|
|
317
312
|
if (wsUser && wsUser.id) {
|
|
318
|
-
|
|
319
|
-
projectEntry.ownerId = wsUser.id;
|
|
320
|
-
}
|
|
321
|
-
if (wsUser.role !== "admin") {
|
|
322
|
-
projectEntry.visibility = "private";
|
|
323
|
-
}
|
|
313
|
+
projectEntry.ownerId = wsUser.id;
|
|
324
314
|
}
|
|
325
315
|
relay.addProject(targetDir, slug);
|
|
326
316
|
config.projects.push(projectEntry);
|
|
@@ -1041,7 +1031,7 @@ var ipc = createIPCServer(socketPath(), function (msg) {
|
|
|
1041
1031
|
var slugs = config.projects.map(function (p) { return p.slug; });
|
|
1042
1032
|
var slug = generateSlug(absPath, slugs);
|
|
1043
1033
|
relay.addProject(absPath, slug);
|
|
1044
|
-
config.projects.push({ path: absPath, slug: slug, addedAt: Date.now() });
|
|
1034
|
+
config.projects.push({ path: absPath, slug: slug, addedAt: Date.now(), visibility: "private" });
|
|
1045
1035
|
saveConfig(config);
|
|
1046
1036
|
try { syncClayrc(config.projects); } catch (e) {}
|
|
1047
1037
|
console.log("[daemon] Added project:", slug, "→", absPath);
|
package/lib/project.js
CHANGED
|
@@ -1171,6 +1171,15 @@ function createProjectContext(opts) {
|
|
|
1171
1171
|
setTimeout(function() { resumeLoop(); }, 500);
|
|
1172
1172
|
}
|
|
1173
1173
|
|
|
1174
|
+
// Auto-assign owner if project has none and a user connects (e.g. IPC-added projects)
|
|
1175
|
+
if (!projectOwnerId && ws._clayUser && ws._clayUser.id && !isMate) {
|
|
1176
|
+
projectOwnerId = ws._clayUser.id;
|
|
1177
|
+
if (opts.onProjectOwnerChanged) {
|
|
1178
|
+
opts.onProjectOwnerChanged(slug, projectOwnerId);
|
|
1179
|
+
}
|
|
1180
|
+
console.log("[project] Auto-assigned owner for " + slug + ": " + projectOwnerId);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1174
1183
|
// Send cached state
|
|
1175
1184
|
var _userId = ws._clayUser ? ws._clayUser.id : null;
|
|
1176
1185
|
var _filteredProjects = getProjectList(_userId);
|
package/lib/public/app.js
CHANGED
|
@@ -2212,6 +2212,9 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
|
|
|
2212
2212
|
sendBtn.disabled = false;
|
|
2213
2213
|
setSendBtnMode("send");
|
|
2214
2214
|
connectOverlay.classList.add("hidden");
|
|
2215
|
+
// Hide update banner on reconnect; server will re-send update_available if still needed
|
|
2216
|
+
var updPill = $("update-pill-wrap");
|
|
2217
|
+
if (updPill) updPill.classList.add("hidden");
|
|
2215
2218
|
stopVerbCycle();
|
|
2216
2219
|
} else if (status === "processing") {
|
|
2217
2220
|
if (dot) { dot.classList.add("connected"); dot.classList.add("processing"); }
|
|
@@ -498,6 +498,113 @@
|
|
|
498
498
|
.project-ctx-item.project-ctx-delete { color: var(--error); }
|
|
499
499
|
.project-ctx-item.project-ctx-delete:hover { background: var(--error-8); }
|
|
500
500
|
|
|
501
|
+
/* --- Project Access Popover --- */
|
|
502
|
+
.project-access-popover {
|
|
503
|
+
position: fixed;
|
|
504
|
+
background: var(--sidebar-bg);
|
|
505
|
+
border: 1px solid var(--border);
|
|
506
|
+
border-radius: 12px;
|
|
507
|
+
padding: 0;
|
|
508
|
+
width: 260px;
|
|
509
|
+
box-shadow: 0 8px 30px rgba(var(--shadow-rgb), 0.35);
|
|
510
|
+
z-index: 9999;
|
|
511
|
+
animation: ctxMenuAppear 0.12s ease-out;
|
|
512
|
+
overflow: hidden;
|
|
513
|
+
}
|
|
514
|
+
.project-access-header {
|
|
515
|
+
display: flex;
|
|
516
|
+
align-items: center;
|
|
517
|
+
justify-content: space-between;
|
|
518
|
+
padding: 12px 14px 8px;
|
|
519
|
+
border-bottom: 1px solid var(--border);
|
|
520
|
+
}
|
|
521
|
+
.project-access-title {
|
|
522
|
+
font-size: 13px;
|
|
523
|
+
font-weight: 600;
|
|
524
|
+
color: var(--text);
|
|
525
|
+
}
|
|
526
|
+
.project-access-close {
|
|
527
|
+
background: none;
|
|
528
|
+
border: none;
|
|
529
|
+
color: var(--text-secondary);
|
|
530
|
+
font-size: 18px;
|
|
531
|
+
cursor: pointer;
|
|
532
|
+
padding: 0 2px;
|
|
533
|
+
line-height: 1;
|
|
534
|
+
}
|
|
535
|
+
.project-access-close:hover { color: var(--text); }
|
|
536
|
+
.project-access-section {
|
|
537
|
+
padding: 10px 14px;
|
|
538
|
+
}
|
|
539
|
+
.project-access-label {
|
|
540
|
+
display: block;
|
|
541
|
+
font-size: 11px;
|
|
542
|
+
font-weight: 600;
|
|
543
|
+
color: var(--text-secondary);
|
|
544
|
+
text-transform: uppercase;
|
|
545
|
+
letter-spacing: 0.04em;
|
|
546
|
+
margin-bottom: 6px;
|
|
547
|
+
}
|
|
548
|
+
.project-access-vis-row {
|
|
549
|
+
display: flex;
|
|
550
|
+
gap: 6px;
|
|
551
|
+
}
|
|
552
|
+
.project-access-vis-btn {
|
|
553
|
+
flex: 1;
|
|
554
|
+
display: flex;
|
|
555
|
+
align-items: center;
|
|
556
|
+
justify-content: center;
|
|
557
|
+
gap: 6px;
|
|
558
|
+
padding: 7px 0;
|
|
559
|
+
font-size: 12px;
|
|
560
|
+
font-family: inherit;
|
|
561
|
+
color: var(--text-secondary);
|
|
562
|
+
background: rgba(var(--overlay-rgb), 0.04);
|
|
563
|
+
border: 1px solid var(--border);
|
|
564
|
+
border-radius: 8px;
|
|
565
|
+
cursor: pointer;
|
|
566
|
+
transition: all 0.15s;
|
|
567
|
+
}
|
|
568
|
+
.project-access-vis-btn .lucide { width: 14px; height: 14px; }
|
|
569
|
+
.project-access-vis-btn:hover { background: rgba(var(--overlay-rgb), 0.08); }
|
|
570
|
+
.project-access-vis-btn.active {
|
|
571
|
+
background: var(--accent-8, rgba(99, 102, 241, 0.08));
|
|
572
|
+
border-color: var(--accent, #6366f1);
|
|
573
|
+
color: var(--accent, #6366f1);
|
|
574
|
+
font-weight: 500;
|
|
575
|
+
}
|
|
576
|
+
.project-access-user-list {
|
|
577
|
+
max-height: 200px;
|
|
578
|
+
overflow-y: auto;
|
|
579
|
+
}
|
|
580
|
+
.project-access-user-item {
|
|
581
|
+
display: flex;
|
|
582
|
+
align-items: center;
|
|
583
|
+
gap: 8px;
|
|
584
|
+
padding: 5px 0;
|
|
585
|
+
font-size: 13px;
|
|
586
|
+
color: var(--text);
|
|
587
|
+
cursor: pointer;
|
|
588
|
+
}
|
|
589
|
+
.project-access-user-item input[type="checkbox"] {
|
|
590
|
+
accent-color: var(--accent, #6366f1);
|
|
591
|
+
width: 15px;
|
|
592
|
+
height: 15px;
|
|
593
|
+
cursor: pointer;
|
|
594
|
+
}
|
|
595
|
+
.project-access-user-item:hover { color: var(--text); }
|
|
596
|
+
.project-access-empty {
|
|
597
|
+
font-size: 12px;
|
|
598
|
+
color: var(--text-tertiary);
|
|
599
|
+
padding: 8px 0;
|
|
600
|
+
}
|
|
601
|
+
.project-access-loading {
|
|
602
|
+
padding: 20px 14px;
|
|
603
|
+
text-align: center;
|
|
604
|
+
font-size: 12px;
|
|
605
|
+
color: var(--text-secondary);
|
|
606
|
+
}
|
|
607
|
+
|
|
501
608
|
/* --- Emoji picker popover --- */
|
|
502
609
|
.emoji-picker {
|
|
503
610
|
position: fixed;
|
|
@@ -2672,6 +2672,158 @@ var EMOJI_CATEGORIES = [
|
|
|
2672
2672
|
]},
|
|
2673
2673
|
];
|
|
2674
2674
|
|
|
2675
|
+
// --- Project Access Popover ---
|
|
2676
|
+
var projectAccessPopover = null;
|
|
2677
|
+
|
|
2678
|
+
function closeProjectAccessPopover() {
|
|
2679
|
+
if (projectAccessPopover) {
|
|
2680
|
+
projectAccessPopover.remove();
|
|
2681
|
+
projectAccessPopover = null;
|
|
2682
|
+
document.removeEventListener("click", closeAccessOnOutside);
|
|
2683
|
+
document.removeEventListener("keydown", closeAccessOnEscape);
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
function closeAccessOnOutside(e) {
|
|
2688
|
+
if (projectAccessPopover && !projectAccessPopover.contains(e.target)) closeProjectAccessPopover();
|
|
2689
|
+
}
|
|
2690
|
+
function closeAccessOnEscape(e) {
|
|
2691
|
+
if (e.key === "Escape") closeProjectAccessPopover();
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
function showProjectAccessPopover(anchorEl, slug) {
|
|
2695
|
+
closeProjectAccessPopover();
|
|
2696
|
+
|
|
2697
|
+
var popover = document.createElement("div");
|
|
2698
|
+
popover.className = "project-access-popover";
|
|
2699
|
+
popover.innerHTML = '<div class="project-access-loading">Loading...</div>';
|
|
2700
|
+
popover.addEventListener("click", function (e) { e.stopPropagation(); });
|
|
2701
|
+
document.body.appendChild(popover);
|
|
2702
|
+
projectAccessPopover = popover;
|
|
2703
|
+
|
|
2704
|
+
// Position near anchor
|
|
2705
|
+
requestAnimationFrame(function () {
|
|
2706
|
+
var rect = anchorEl.getBoundingClientRect();
|
|
2707
|
+
popover.style.position = "fixed";
|
|
2708
|
+
popover.style.left = (rect.right + 8) + "px";
|
|
2709
|
+
popover.style.top = rect.top + "px";
|
|
2710
|
+
popover.style.zIndex = "9999";
|
|
2711
|
+
var popRect = popover.getBoundingClientRect();
|
|
2712
|
+
if (popRect.right > window.innerWidth - 8) {
|
|
2713
|
+
popover.style.left = (rect.left - popRect.width - 8) + "px";
|
|
2714
|
+
}
|
|
2715
|
+
if (popRect.bottom > window.innerHeight - 8) {
|
|
2716
|
+
popover.style.top = (window.innerHeight - popRect.height - 8) + "px";
|
|
2717
|
+
}
|
|
2718
|
+
});
|
|
2719
|
+
|
|
2720
|
+
setTimeout(function () {
|
|
2721
|
+
document.addEventListener("click", closeAccessOnOutside);
|
|
2722
|
+
document.addEventListener("keydown", closeAccessOnEscape);
|
|
2723
|
+
}, 0);
|
|
2724
|
+
|
|
2725
|
+
// Fetch access info and user list in parallel
|
|
2726
|
+
Promise.all([
|
|
2727
|
+
fetch("/api/admin/projects/" + encodeURIComponent(slug) + "/access").then(function (r) { return r.json(); }),
|
|
2728
|
+
fetch("/api/admin/users").then(function (r) { return r.json(); }),
|
|
2729
|
+
]).then(function (results) {
|
|
2730
|
+
var access = results[0];
|
|
2731
|
+
var usersData = results[1];
|
|
2732
|
+
if (access.error || usersData.error) {
|
|
2733
|
+
popover.innerHTML = '<div class="project-access-loading">Failed to load</div>';
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
renderAccessPopover(popover, slug, access, usersData.users || []);
|
|
2737
|
+
}).catch(function () {
|
|
2738
|
+
popover.innerHTML = '<div class="project-access-loading">Failed to load</div>';
|
|
2739
|
+
});
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
function renderAccessPopover(popover, slug, access, allUsers) {
|
|
2743
|
+
var visibility = access.visibility || "public";
|
|
2744
|
+
var allowedUsers = access.allowedUsers || [];
|
|
2745
|
+
var ownerId = access.ownerId;
|
|
2746
|
+
|
|
2747
|
+
// Filter out the owner from the user list (owner always has access)
|
|
2748
|
+
var selectableUsers = allUsers.filter(function (u) { return u.id !== ownerId; });
|
|
2749
|
+
|
|
2750
|
+
var html = '';
|
|
2751
|
+
html += '<div class="project-access-header">';
|
|
2752
|
+
html += '<span class="project-access-title">Project Access</span>';
|
|
2753
|
+
html += '<button class="project-access-close">×</button>';
|
|
2754
|
+
html += '</div>';
|
|
2755
|
+
|
|
2756
|
+
// Visibility toggle
|
|
2757
|
+
html += '<div class="project-access-section">';
|
|
2758
|
+
html += '<label class="project-access-label">Visibility</label>';
|
|
2759
|
+
html += '<div class="project-access-vis-row">';
|
|
2760
|
+
html += '<button class="project-access-vis-btn' + (visibility === "private" ? ' active' : '') + '" data-vis="private">';
|
|
2761
|
+
html += iconHtml("lock") + ' Private';
|
|
2762
|
+
html += '</button>';
|
|
2763
|
+
html += '<button class="project-access-vis-btn' + (visibility === "public" ? ' active' : '') + '" data-vis="public">';
|
|
2764
|
+
html += iconHtml("globe") + ' Public';
|
|
2765
|
+
html += '</button>';
|
|
2766
|
+
html += '</div>';
|
|
2767
|
+
html += '</div>';
|
|
2768
|
+
|
|
2769
|
+
// Allowed users (only when private)
|
|
2770
|
+
html += '<div class="project-access-section project-access-users-section"' + (visibility !== "private" ? ' style="display:none"' : '') + '>';
|
|
2771
|
+
html += '<label class="project-access-label">Allowed Users</label>';
|
|
2772
|
+
html += '<div class="project-access-user-list">';
|
|
2773
|
+
for (var i = 0; i < selectableUsers.length; i++) {
|
|
2774
|
+
var u = selectableUsers[i];
|
|
2775
|
+
var checked = allowedUsers.indexOf(u.id) !== -1 ? " checked" : "";
|
|
2776
|
+
html += '<label class="project-access-user-item">';
|
|
2777
|
+
html += '<input type="checkbox" data-uid="' + u.id + '"' + checked + '>';
|
|
2778
|
+
html += '<span>' + escapeHtml(u.displayName || u.username || u.id) + '</span>';
|
|
2779
|
+
html += '</label>';
|
|
2780
|
+
}
|
|
2781
|
+
if (selectableUsers.length === 0) {
|
|
2782
|
+
html += '<div class="project-access-empty">No other users</div>';
|
|
2783
|
+
}
|
|
2784
|
+
html += '</div>';
|
|
2785
|
+
html += '</div>';
|
|
2786
|
+
|
|
2787
|
+
popover.innerHTML = html;
|
|
2788
|
+
refreshIcons();
|
|
2789
|
+
|
|
2790
|
+
// Close button
|
|
2791
|
+
popover.querySelector(".project-access-close").addEventListener("click", function () {
|
|
2792
|
+
closeProjectAccessPopover();
|
|
2793
|
+
});
|
|
2794
|
+
|
|
2795
|
+
// Visibility toggle
|
|
2796
|
+
popover.querySelectorAll(".project-access-vis-btn").forEach(function (btn) {
|
|
2797
|
+
btn.addEventListener("click", function () {
|
|
2798
|
+
var newVis = btn.dataset.vis;
|
|
2799
|
+
popover.querySelectorAll(".project-access-vis-btn").forEach(function (b) { b.classList.remove("active"); });
|
|
2800
|
+
btn.classList.add("active");
|
|
2801
|
+
var usersSection = popover.querySelector(".project-access-users-section");
|
|
2802
|
+
if (usersSection) usersSection.style.display = newVis === "private" ? "" : "none";
|
|
2803
|
+
fetch("/api/admin/projects/" + encodeURIComponent(slug) + "/visibility", {
|
|
2804
|
+
method: "PUT",
|
|
2805
|
+
headers: { "Content-Type": "application/json" },
|
|
2806
|
+
body: JSON.stringify({ visibility: newVis }),
|
|
2807
|
+
});
|
|
2808
|
+
});
|
|
2809
|
+
});
|
|
2810
|
+
|
|
2811
|
+
// User checkboxes
|
|
2812
|
+
popover.querySelectorAll('.project-access-user-item input[type="checkbox"]').forEach(function (cb) {
|
|
2813
|
+
cb.addEventListener("change", function () {
|
|
2814
|
+
var selected = [];
|
|
2815
|
+
popover.querySelectorAll('.project-access-user-item input[type="checkbox"]:checked').forEach(function (c) {
|
|
2816
|
+
selected.push(c.dataset.uid);
|
|
2817
|
+
});
|
|
2818
|
+
fetch("/api/admin/projects/" + encodeURIComponent(slug) + "/users", {
|
|
2819
|
+
method: "PUT",
|
|
2820
|
+
headers: { "Content-Type": "application/json" },
|
|
2821
|
+
body: JSON.stringify({ allowedUsers: selected }),
|
|
2822
|
+
});
|
|
2823
|
+
});
|
|
2824
|
+
});
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2675
2827
|
function closeProjectCtxMenu() {
|
|
2676
2828
|
if (projectCtxMenu) {
|
|
2677
2829
|
projectCtxMenu.remove();
|
|
@@ -2797,6 +2949,23 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
|
|
|
2797
2949
|
});
|
|
2798
2950
|
menu.appendChild(shareItem);
|
|
2799
2951
|
|
|
2952
|
+
// --- Manage Access (owner or admin, multi-user only) ---
|
|
2953
|
+
if (ctx.multiUser && slug.indexOf("--") === -1) {
|
|
2954
|
+
var isProjectOwner = ctx.myUserId && ctx.projectOwnerId && ctx.myUserId === ctx.projectOwnerId;
|
|
2955
|
+
var isAdmin = ctx.permissions && ctx.permissions.projectSettings !== false;
|
|
2956
|
+
if (isProjectOwner || isAdmin) {
|
|
2957
|
+
var accessItem = document.createElement("button");
|
|
2958
|
+
accessItem.className = "project-ctx-item";
|
|
2959
|
+
accessItem.innerHTML = iconHtml("users") + " <span>Manage Access</span>";
|
|
2960
|
+
accessItem.addEventListener("click", function (e) {
|
|
2961
|
+
e.stopPropagation();
|
|
2962
|
+
closeProjectCtxMenu();
|
|
2963
|
+
showProjectAccessPopover(anchorEl, slug);
|
|
2964
|
+
});
|
|
2965
|
+
menu.appendChild(accessItem);
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2800
2969
|
if (!ctx.permissions || ctx.permissions.deleteProject !== false) {
|
|
2801
2970
|
// --- Separator ---
|
|
2802
2971
|
var sep = document.createElement("div");
|
package/lib/server.js
CHANGED
|
@@ -1512,13 +1512,23 @@ function createServer(opts) {
|
|
|
1512
1512
|
return;
|
|
1513
1513
|
}
|
|
1514
1514
|
var mu = getMultiUserFromReq(req);
|
|
1515
|
-
if (!mu
|
|
1516
|
-
res.writeHead(
|
|
1517
|
-
res.end('{"error":"
|
|
1515
|
+
if (!mu) {
|
|
1516
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
1517
|
+
res.end('{"error":"Authentication required"}');
|
|
1518
1518
|
return;
|
|
1519
1519
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1520
|
+
// Admins get full user list; project owners get limited list (id, displayName, username)
|
|
1521
|
+
if (mu.role === "admin") {
|
|
1522
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1523
|
+
res.end(JSON.stringify({ users: users.getAllUsers() }));
|
|
1524
|
+
} else {
|
|
1525
|
+
var allU = users.getAllUsers();
|
|
1526
|
+
var safeUsers = allU.map(function (u) {
|
|
1527
|
+
return { id: u.id, displayName: u.displayName, username: u.username };
|
|
1528
|
+
});
|
|
1529
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1530
|
+
res.end(JSON.stringify({ users: safeUsers }));
|
|
1531
|
+
}
|
|
1522
1532
|
return;
|
|
1523
1533
|
}
|
|
1524
1534
|
|
|
@@ -1990,9 +2000,12 @@ function createServer(opts) {
|
|
|
1990
2000
|
return;
|
|
1991
2001
|
}
|
|
1992
2002
|
var mu = getMultiUserFromReq(req);
|
|
1993
|
-
|
|
2003
|
+
var _visSlug = fullUrl.split("/")[4];
|
|
2004
|
+
var _visAccess = onGetProjectAccess ? onGetProjectAccess(_visSlug) : null;
|
|
2005
|
+
var _isOwner = mu && _visAccess && _visAccess.ownerId && mu.id === _visAccess.ownerId;
|
|
2006
|
+
if (!mu || (mu.role !== "admin" && !_isOwner)) {
|
|
1994
2007
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
1995
|
-
res.end('{"error":"Admin access required"}');
|
|
2008
|
+
res.end('{"error":"Admin or project owner access required"}');
|
|
1996
2009
|
return;
|
|
1997
2010
|
}
|
|
1998
2011
|
var projSlug = fullUrl.split("/")[4];
|
|
@@ -2092,9 +2105,12 @@ function createServer(opts) {
|
|
|
2092
2105
|
return;
|
|
2093
2106
|
}
|
|
2094
2107
|
var mu = getMultiUserFromReq(req);
|
|
2095
|
-
|
|
2108
|
+
var _usrSlug = fullUrl.split("/")[4];
|
|
2109
|
+
var _usrAccess = onGetProjectAccess ? onGetProjectAccess(_usrSlug) : null;
|
|
2110
|
+
var _isOwnerU = mu && _usrAccess && _usrAccess.ownerId && mu.id === _usrAccess.ownerId;
|
|
2111
|
+
if (!mu || (mu.role !== "admin" && !_isOwnerU)) {
|
|
2096
2112
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
2097
|
-
res.end('{"error":"Admin access required"}');
|
|
2113
|
+
res.end('{"error":"Admin or project owner access required"}');
|
|
2098
2114
|
return;
|
|
2099
2115
|
}
|
|
2100
2116
|
var projSlug = fullUrl.split("/")[4];
|
|
@@ -2134,7 +2150,7 @@ function createServer(opts) {
|
|
|
2134
2150
|
return;
|
|
2135
2151
|
}
|
|
2136
2152
|
|
|
2137
|
-
// Get project access info (admin
|
|
2153
|
+
// Get project access info (admin or project owner)
|
|
2138
2154
|
if (req.method === "GET" && /^\/api\/admin\/projects\/[a-z0-9_-]+\/access$/.test(fullUrl)) {
|
|
2139
2155
|
if (!users.isMultiUser()) {
|
|
2140
2156
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
@@ -2142,9 +2158,12 @@ function createServer(opts) {
|
|
|
2142
2158
|
return;
|
|
2143
2159
|
}
|
|
2144
2160
|
var mu = getMultiUserFromReq(req);
|
|
2145
|
-
|
|
2161
|
+
var _accSlug = fullUrl.split("/")[4];
|
|
2162
|
+
var _accAccess = onGetProjectAccess ? onGetProjectAccess(_accSlug) : null;
|
|
2163
|
+
var _isOwnerA = mu && _accAccess && _accAccess.ownerId && mu.id === _accAccess.ownerId;
|
|
2164
|
+
if (!mu || (mu.role !== "admin" && !_isOwnerA)) {
|
|
2146
2165
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
2147
|
-
res.end('{"error":"Admin access required"}');
|
|
2166
|
+
res.end('{"error":"Admin or project owner access required"}');
|
|
2148
2167
|
return;
|
|
2149
2168
|
}
|
|
2150
2169
|
var projSlug = fullUrl.split("/")[4];
|