Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,28 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

## [Unreleased]

## [0.82.0] - 2026-10-28

Needs DirXMLDev 0.58.0.

### Added

- Library objects that live in another Library of the vault (DirXMLDev #111) are marked
**read-only**:
- **Outline and Developer view:** a badge, with the Library's DN on hover.
- **The object's page:** the source only. There's no Rename, Delete or structured editor, and a
note says where the object lives.

Links to it keep its DN, compare and deploy leave it alone, and the core refuses an edit to it.

### Changed

- Pins DirXMLDev 0.58.0:
- `vault.deploy-schema --yes` is behind the agent write gate;
- the `idm` CLI in the image refuses arguments a command doesn't take, and a flag no longer
takes the next word;
- `driver.query` is gated, and `driver.migrate --dry-run` isn't.

## [0.81.0] - 2026-10-28

Needs DirXMLDev 0.57.0.
Expand Down
4 changes: 3 additions & 1 deletion docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ Every view of the page, by area, in every install; the items marked *local and s
engine on request (`DriverGetSchema`, the driver stopped) and kept in the tree; the
application side of the schema map offers its names.
- **Outline** of the driver set: Library, drivers, policies by channel, configuration files,
entitlements, forms, PRDs.
entitlements, forms, PRDs. A Library object that lives in another Library of the vault is
marked *read-only*. Its page shows the source and where it lives, and offers no edits; links
to it keep its DN, and compare and deploy leave it alone.
- **Policy flow**: the fishbone the VS Code extension draws, from the same model. Hover a policy
for its description; click it to open it. Click a policy set to open it beside the picture:
reorder its policies (▲ ▼), unlink one, or link an existing policy or resource — one reviewed
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.pointblue.dirxml</groupId>
<artifactId>dirxml-dev-web</artifactId>
<version>0.81.0</version>
<version>0.82.0</version>
<licenses>
<license>
<name>MIT License</name>
Expand All @@ -23,7 +23,7 @@
<!-- The DirXMLDev release this server is written against. Install it once with
`mvn -o install -DskipTests` in the DirXMLDev checkout; bin/idmweb then runs
against that checkout's classes, engine jars and simulator. -->
<dirxmldev.version>0.57.0</dirxmldev.version>
<dirxmldev.version>0.58.0</dirxmldev.version>
<simulator.version>1.7.0</simulator.version>
<!-- the DirXML Trace Viewer's library: parser, highlighter, LDAP trace source (its release attaches the jar) -->
<!-- Directory Console's shared libraries (console-auth: OIDC sign-in, TLS trust). Install with
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/pointblue/dirxml/web/Serve.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*/
public final class Serve {

public static final String VERSION = "0.81.0";
public static final String VERSION = "0.82.0";

private Serve() {
}
Expand Down
35 changes: 23 additions & 12 deletions src/main/java/com/pointblue/dirxml/web/TreeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ public synchronized Map<String, Object> outline() throws IOException {
set.put("schema", ds.schema == null ? null : ds.schema.summary());
o.put("driverSet", set);
Map<String, Object> lib = new LinkedHashMap<>();
lib.put("policies", nodes(ds.library.policies, files));
lib.put("resources", nodes(ds.library.resources, files));
lib.put("policies", nodes(ds, ds.library.policies, files));
lib.put("resources", nodes(ds, ds.library.resources, files));
o.put("library", lib);
List<Object> drivers = new ArrayList<>();
for (Driver d : ds.drivers) {
Expand All @@ -153,11 +153,11 @@ public synchronized Map<String, Object> outline() throws IOException {
}
m.put("config", existing(config));
Map<String, Object> policies = new LinkedHashMap<>();
policies.put("driver", nodes(d.policies, files));
policies.put("subscriber", nodes(d.subscriber.policies, files));
policies.put("publisher", nodes(d.publisher.policies, files));
policies.put("driver", nodes(ds, d.policies, files));
policies.put("subscriber", nodes(ds, d.subscriber.policies, files));
policies.put("publisher", nodes(ds, d.publisher.policies, files));
m.put("policies", policies);
m.put("resources", nodes(d.resources, files));
m.put("resources", nodes(ds, d.resources, files));
List<Object> ents = new ArrayList<>();
for (Entitlement e : d.entitlements) {
ents.add(fileNode(e.name, dir + "/entitlements/" + AsCodeWriter.fileSafe(e.name) + ".xml"));
Expand Down Expand Up @@ -203,14 +203,19 @@ public synchronized Map<String, Object> outline() throws IOException {
return o;
}

private List<Object> nodes(List<? extends Artifact> artifacts, Map<String, String> files) {
private List<Object> nodes(DriverSet ds, List<? extends Artifact> artifacts, Map<String, String> files) {
List<Object> out = new ArrayList<>();
for (Artifact a : artifacts) {
Map<String, Object> n = new LinkedHashMap<>();
n.put("path", a.path());
n.put("name", a.name);
n.put("kind", kindOf(a));
n.put("file", files.get(a.path()));
// a Library object from another Library: a read-only reference (DirXMLDev 0.58.0, #111)
String other = com.pointblue.dirxml.dev.model.Libraries.outside(ds, a);
if (other != null) {
n.put("readOnly", other);
}
out.add(n);
}
return out;
Expand Down Expand Up @@ -262,7 +267,9 @@ public synchronized Map<String, Object> artifact(String path) throws IOException
m.put("file", file);
m.put("language", language(file));
m.put("content", file == null ? "" : Files.readString(tree.resolve(file), StandardCharsets.UTF_8));
m.put("editable", file != null);
String other = com.pointblue.dirxml.dev.model.Libraries.outside(ds, a);
m.put("editable", file != null && other == null);
m.put("readOnly", other);
m.put("packaged", a.meta.keySet().stream().anyMatch(k -> k.toLowerCase().startsWith("dirxml-pkg") || k.equals("package-id")));
List<Object> refs = new ArrayList<>();
// a mapping table also names the drivers that run a policy reading it (DirXMLDev 0.34.0)
Expand Down Expand Up @@ -410,10 +417,10 @@ public synchronized Map<String, Object> developer() throws IOException {
// the library
Map<String, Object> lib = node("library", "Library", "folder", null);
List<Object> lk = kids(lib);
for (Object n : nodes(ds.library.policies, files)) {
for (Object n : nodes(ds, ds.library.policies, files)) {
lk.add(artifactLeaf("lib", n));
}
for (Object n : nodes(ds.library.resources, files)) {
for (Object n : nodes(ds, ds.library.resources, files)) {
lk.add(artifactLeaf("lib", n));
}
rootKids.add(lib);
Expand Down Expand Up @@ -458,7 +465,7 @@ public synchronized Map<String, Object> developer() throws IOException {
rk.add(boneNode(d.name, b));
}
}
for (Object n : nodes(d.resources, files)) {
for (Object n : nodes(ds, d.resources, files)) {
rk.add(artifactLeaf("res:" + d.name, n));
}
dkids.add(res);
Expand Down Expand Up @@ -551,7 +558,11 @@ private static Map<String, Object> boneNode(String driver, Map<String, Object> b
@SuppressWarnings("unchecked")
private static Map<String, Object> artifactLeaf(String prefix, Object nodeMap) {
Map<String, Object> n = (Map<String, Object>) nodeMap;
return leaf(prefix + ":" + n.get("path"), String.valueOf(n.get("name")), String.valueOf(n.get("kind")), Map.of("type", "artifact", "path", String.valueOf(n.get("path"))));
Map<String, Object> l = leaf(prefix + ":" + n.get("path"), String.valueOf(n.get("name")), String.valueOf(n.get("kind")), Map.of("type", "artifact", "path", String.valueOf(n.get("path"))));
if (n.get("readOnly") != null) {
l.put("readOnly", n.get("readOnly"));
}
return l;
}

private static String configLabel(String kind) {
Expand Down
65 changes: 65 additions & 0 deletions src/test/java/com/pointblue/dirxml/web/ReadOnlyLibraryTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.pointblue.dirxml.web;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import com.pointblue.dirxml.dev.ascode.AsCodeWriter;
import com.pointblue.dirxml.dev.model.DriverSet;
import com.pointblue.dirxml.dev.model.DuplicateArtifacts;
import com.pointblue.dirxml.dev.model.Resource;
import com.pointblue.dirxml.dev.model.Scope;

import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

/** A Library object from another Library is shown read-only: the outline, the Developer tree, the artifact (DirXMLDev #111). */
public class ReadOnlyLibraryTest {

@Rule
public TemporaryFolder tmp = new TemporaryFolder();

private static final String SHARED = "cn=Library,ou=shared,o=system";

private static Resource js(String name) {
Resource r = new Resource(name, Scope.LIBRARY, null, Resource.ECMASCRIPT);
r.text = "function " + name.replace('-', '_') + "(){}";
return r;
}

@Test
@SuppressWarnings("unchecked")
public void theOtherLibrarysObjectIsReadOnly() throws Exception {
DriverSet ds = new DriverSet("DS");
ds.dn = "cn=DS,o=system";
ds.library.resources.add(js("es-own"));
Resource shared = js("es-shared");
shared.meta.put(DuplicateArtifacts.LIBRARY_DN, SHARED);
ds.library.resources.add(shared);
Path tree = tmp.newFolder("tree").toPath();
AsCodeWriter.write(ds, tree);
try (Watcher w = new Watcher(tree, 10_000)) {
TreeService service = new TreeService(tree, w);

Map<String, Object> other = service.artifact("library/es-shared");
assertEquals(SHARED, other.get("readOnly"));
assertEquals(false, other.get("editable"));
Map<String, Object> own = service.artifact("library/es-own");
assertNull(own.get("readOnly"));
assertEquals(true, own.get("editable"));

List<Map<String, Object>> resources = (List<Map<String, Object>>) ((Map<String, Object>) service.outline().get("library")).get("resources");
for (Map<String, Object> n : resources) {
assertEquals(n.get("name").equals("es-shared") ? SHARED : null, n.get("readOnly"));
}
String dev = String.valueOf(service.developer());
assertTrue(dev, dev.contains("readOnly=" + SHARED));
assertFalse(dev.contains("readOnly=null"));
}
}
}
13 changes: 10 additions & 3 deletions web/src/components/ArtifactView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function ArtifactView({ path, revision, onOpen, onSaved, onGone, onMoved
setError(null);
setChangedOnDisk(false);
setDraft(null);
setTab((t) => (t === "structured" && !STRUCTURED[a.kind] ? "source" : t));
setTab((t) => (t === "structured" && (a.readOnly || !STRUCTURED[a.kind]) ? "source" : t));
})
.catch((e: Error) => !gone && setError(e.message));
return () => {
Expand Down Expand Up @@ -119,7 +119,8 @@ export function ArtifactView({ path, revision, onOpen, onSaved, onGone, onMoved
setChangedOnDisk(false);
};

const structuredLabel = STRUCTURED[artifact.kind];
// a Library object from another Library is shown, not edited: the source only (#111)
const structuredLabel = artifact.readOnly ? undefined : STRUCTURED[artifact.kind];

return (
<div className="view">
Expand All @@ -129,12 +130,18 @@ export function ArtifactView({ path, revision, onOpen, onSaved, onGone, onMoved
{artifact.kind} · {artifact.scope}
{artifact.driver ? ` · ${artifact.driver}` : ""}
{artifact.packaged ? " · packaged" : ""}
{artifact.readOnly ? " · read-only" : ""}
</div>
<h2>{artifact.name}</h2>
<div className="muted small">{artifact.file ?? artifact.path}</div>
{artifact.readOnly && (
<div className="muted small readonly-note" data-testid="artifact-readonly">
Lives in <span title={artifact.readOnly}>{artifact.readOnly}</span>, outside the driver set's Library. Drivers' links to it keep its DN; compare and deploy leave it alone. Change it in Designer, or in that Library.
</div>
)}
</div>
<div className="actions">
{!editing && pending.ops.length === 0 && (
{!editing && pending.ops.length === 0 && !artifact.readOnly && (
<>
<button onClick={() => (setNewName(artifact.name), setRenaming(true))} title="Rename it, and rewrite every reference to it (links, includes, Map tokens)" data-testid="artifact-rename">
Rename…
Expand Down
5 changes: 5 additions & 0 deletions web/src/components/DeveloperView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ export function DeveloperView({ revision, onSelect }: { revision: string; onSele
)}
{n.kind === "driver" && n.typeName && n.typeName !== n.label && <span className="muted small"> {n.typeName}</span>}
{n.unresolved && <span className="pill warn">unresolved</span>}
{n.readOnly && (
<span className="pill" title={`Lives in ${n.readOnly}, outside the driver set's Library; read-only here`}>
read-only
</span>
)}
{st && <span className={"pill " + (st.state.toLowerCase().includes("running") ? "ok" : st.state.toLowerCase().includes("disabled") ? "err" : "")}>{st.state}</span>}
{folder && n.count !== undefined && <span className="muted small count">{n.count}</span>}
</div>
Expand Down
11 changes: 10 additions & 1 deletion web/src/components/OutlineTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,17 @@ export function OutlineTree({ outline, selection, onSelect }: Props) {
<ul>
{nodes.map((n) => (
<li key={n.path}>
<button className={isArtifact(n.path) ? "leaf active" : "leaf"} onClick={() => onSelect({ type: "artifact", path: n.path })} title={n.path}>
<button
className={isArtifact(n.path) ? "leaf active" : "leaf"}
onClick={() => onSelect({ type: "artifact", path: n.path })}
title={n.readOnly ? `${n.path}: lives in ${n.readOnly}, outside the driver set's Library; read-only here` : n.path}
>
<span className="badge">{KIND_BADGE[n.kind] ?? "·"}</span>
{n.readOnly && (
<span className="pill ro" data-testid={`readonly-${n.path}`}>
read-only
</span>
)}
{n.name}
</button>
</li>
Expand Down
10 changes: 10 additions & 0 deletions web/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1533,3 +1533,13 @@ a.button { display: inline-block; padding: 5px 10px; border: 1px solid var(--bor
height: 60vh;
}
}

/* a Library object from another Library (#111) */
.leaf .pill.ro {
margin-right: 6px;
font-size: 0.85em;
}
.readonly-note {
margin-top: 4px;
max-width: 60em;
}
5 changes: 5 additions & 0 deletions web/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export interface ArtifactNode {
name: string;
kind: string;
file: string | null;
/** A Library object from another Library: the DN of the Library it lives in; read-only here. */
readOnly?: string;
}

export interface FileNode {
Expand Down Expand Up @@ -174,6 +176,8 @@ export interface Artifact {
language: string;
content: string;
editable: boolean;
/** A Library object from another Library: the DN of the Library it lives in; read-only here. */
readOnly?: string | null;
packaged: boolean;
refs: Ref[];
}
Expand Down Expand Up @@ -1188,6 +1192,7 @@ export interface DevNode {
shimClass?: string | null;
icon?: boolean;
unresolved?: boolean;
readOnly?: string;
}

/** A project put away (#76): its bundle and what moved aside. */
Expand Down
Loading