Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8acc9e439 |
@@ -22,6 +22,11 @@ allow-listed, its API token comes from the process environment, requests use a
|
||||
bounded timeout, and callers are rate-limited. Discovered entries are merged
|
||||
atomically into inventory and begin with projection disabled.
|
||||
|
||||
Until inventory is wired into `BuildInputLoader`, mutation, discovery,
|
||||
connectivity-test, and content-scan endpoints return
|
||||
`409 E_SOURCE_MANAGEMENT_NOT_WIRED`. The UI publishes the resolved build source,
|
||||
labels inventory read-only, and disables the corresponding controls.
|
||||
|
||||
Source-management UX may add review and configuration workflows later, but any
|
||||
accepted change must be committed to the Git-owned configuration before it can
|
||||
affect a release.
|
||||
|
||||
@@ -68,7 +68,7 @@ Validation failures produce a failed build record but never publish a partial re
|
||||
## Current boundaries
|
||||
|
||||
- Theme packages are presentation data only; Node, shell, WASM, package-manager scripts, custom Liquid tags, and custom filters are not executed.
|
||||
- The reference loader resolves the configured site-definition repository and supports separately pinned source snapshots through the typed boundary; source-management UX remains follow-up work governed by [ADR 0001](.pandaos/adrs/0001-git-source-management-boundary.md).
|
||||
- The reference loader resolves the configured site-definition repository and supports separately pinned source snapshots through the typed boundary. Repository inventory is explicitly read-only: its mutation and content-scan APIs return `409 E_SOURCE_MANAGEMENT_NOT_WIRED`, and the UI disables those actions until inventory feeds `BuildInputLoader`; see [ADR 0001](.pandaos/adrs/0001-git-source-management-boundary.md).
|
||||
- Remote rsync deployment is not implemented. Push and connection-test endpoints fail closed with HTTP 501, and no configuration field can enable execution; see [ADR 0002](.pandaos/adrs/0002-remote-publication-not-implemented.md).
|
||||
|
||||
See [docs/OPERATIONS.md](docs/OPERATIONS.md) for snapshot, release, promotion, failure, and rollback procedures.
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
"preview": "vite preview",
|
||||
"clean": "rm -rf dist server.js",
|
||||
"lint": "tsc --noEmit",
|
||||
"test": "node --import tsx --test src/backend/buildEngine.test.ts src/backend/giteaDiscovery.test.ts src/backend/releaseLifecycle.test.ts src/backend/repositorySnapshot.test.ts"
|
||||
"test": "node --import tsx --test src/backend/buildEngine.test.ts src/backend/giteaDiscovery.test.ts src/backend/releaseLifecycle.test.ts src/backend/repositorySnapshot.test.ts src/backend/sourceManagement.test.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
|
||||
+21
@@ -14,6 +14,7 @@ import { ConfigAndAuditTab } from "./components/ConfigAndAuditTab";
|
||||
|
||||
import {
|
||||
GitSource,
|
||||
SourceManagementCapability,
|
||||
StyleConfigPackage,
|
||||
StyleConfigInstance,
|
||||
ContentItem,
|
||||
@@ -27,11 +28,26 @@ import {
|
||||
SiteConfig,
|
||||
} from "./types";
|
||||
|
||||
const sourceManagementFallback: SourceManagementCapability = {
|
||||
protocol: "labyricorn-source-management/v1",
|
||||
buildIntegration: "not-wired",
|
||||
mutationsEnabled: false,
|
||||
buildSource: {
|
||||
sourceId: "site-definition",
|
||||
repository: "unresolved",
|
||||
commit: null,
|
||||
},
|
||||
message:
|
||||
"Source-management status is unavailable; mutations remain disabled.",
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
const [activeTab, setActiveTab] = useState<string>("dashboard");
|
||||
|
||||
const [siteConfig, setSiteConfig] = useState<SiteConfig | null>(null);
|
||||
const [sources, setSources] = useState<GitSource[]>([]);
|
||||
const [sourceManagement, setSourceManagement] =
|
||||
useState<SourceManagementCapability>(sourceManagementFallback);
|
||||
const [stylePackages, setStylePackages] = useState<StyleConfigPackage[]>([]);
|
||||
const [styleInstances, setStyleInstances] = useState<StyleConfigInstance[]>(
|
||||
[],
|
||||
@@ -53,6 +69,7 @@ export default function App() {
|
||||
const [
|
||||
cfgRes,
|
||||
srcRes,
|
||||
srcMgmtRes,
|
||||
pkgRes,
|
||||
instRes,
|
||||
cntRes,
|
||||
@@ -66,6 +83,7 @@ export default function App() {
|
||||
] = await Promise.all([
|
||||
fetch("/api/site-config"),
|
||||
fetch("/api/git-sources"),
|
||||
fetch("/api/source-management"),
|
||||
fetch("/api/style-packages"),
|
||||
fetch("/api/style-instances"),
|
||||
fetch("/api/content"),
|
||||
@@ -80,6 +98,7 @@ export default function App() {
|
||||
|
||||
if (cfgRes.ok) setSiteConfig(await cfgRes.json());
|
||||
if (srcRes.ok) setSources(await srcRes.json());
|
||||
if (srcMgmtRes.ok) setSourceManagement(await srcMgmtRes.json());
|
||||
if (pkgRes.ok) setStylePackages(await pkgRes.json());
|
||||
if (instRes.ok) setStyleInstances(await instRes.json());
|
||||
if (cntRes.ok) setContentItems(await cntRes.json());
|
||||
@@ -238,6 +257,7 @@ export default function App() {
|
||||
<GitSourcesTab
|
||||
sources={sources}
|
||||
credentials={credentials}
|
||||
sourceManagement={sourceManagement}
|
||||
onAddSource={handleAddGitSource}
|
||||
onTestSource={handleTestGitSource}
|
||||
/>
|
||||
@@ -247,6 +267,7 @@ export default function App() {
|
||||
<ContentTab
|
||||
contentItems={contentItems}
|
||||
mediaAssets={mediaAssets}
|
||||
sourceManagement={sourceManagement}
|
||||
onTriggerDiscovery={handleTriggerContentDiscovery}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -2,6 +2,7 @@ import express from "express";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { cachedRepositoryPath, listRepositoryFiles } from "../gitOperations";
|
||||
import { requireSourceManagementBuildIntegration } from "../sourceManagement";
|
||||
import { store } from "../store";
|
||||
|
||||
const router = express.Router();
|
||||
@@ -43,7 +44,7 @@ router.get("/content", (_req, res) => {
|
||||
res.json(store.contentItems);
|
||||
});
|
||||
|
||||
router.post("/content/discover", (_req, res) => {
|
||||
router.post("/content/discover", requireSourceManagementBuildIntegration, (_req, res) => {
|
||||
store.addAudit(
|
||||
"Discover Content",
|
||||
"config",
|
||||
|
||||
@@ -8,6 +8,10 @@ import {
|
||||
synchronizeRepository,
|
||||
validateRepository,
|
||||
} from "../gitOperations";
|
||||
import {
|
||||
requireSourceManagementBuildIntegration,
|
||||
sourceManagementCapability,
|
||||
} from "../sourceManagement";
|
||||
import { store } from "../store";
|
||||
import {
|
||||
fetchGiteaRepositories,
|
||||
@@ -23,7 +27,11 @@ let discoveryInFlight: Promise<ReturnType<typeof toGitSource>[]> | null = null;
|
||||
const errorMessage = (error: unknown): string =>
|
||||
error instanceof Error ? error.message : String(error);
|
||||
|
||||
router.post("/gitea/discover", async (req, res) => {
|
||||
router.get("/source-management", (_req, res) => {
|
||||
res.json(sourceManagementCapability(store.siteConfig));
|
||||
});
|
||||
|
||||
router.post("/gitea/discover", requireSourceManagementBuildIntegration, async (req, res) => {
|
||||
const limit = discoveryLimiter.take(req.ip || req.socket.remoteAddress || "unknown");
|
||||
if (!limit.allowed) {
|
||||
res.setHeader("Retry-After", String(limit.retryAfter));
|
||||
@@ -75,7 +83,7 @@ router.get("/git-sources", (_req, res) => {
|
||||
res.json(store.gitSources);
|
||||
});
|
||||
|
||||
router.post("/git-sources", async (req, res) => {
|
||||
router.post("/git-sources", requireSourceManagementBuildIntegration, async (req, res) => {
|
||||
const body = req.body as Partial<GitSource>;
|
||||
if (
|
||||
typeof body.id !== "string" ||
|
||||
@@ -136,7 +144,7 @@ router.post("/git-sources", async (req, res) => {
|
||||
return res.json({ success: true, source });
|
||||
});
|
||||
|
||||
router.put("/git-sources/:id", (req, res) => {
|
||||
router.put("/git-sources/:id", requireSourceManagementBuildIntegration, (req, res) => {
|
||||
const index = store.gitSources.findIndex((source) => source.id === req.params.id);
|
||||
if (index === -1) return res.status(404).json({ error: "Source not found" });
|
||||
store.gitSources[index] = { ...store.gitSources[index], ...req.body };
|
||||
@@ -148,7 +156,7 @@ router.put("/git-sources/:id", (req, res) => {
|
||||
return res.json({ success: true, source: store.gitSources[index] });
|
||||
});
|
||||
|
||||
router.delete("/git-sources/:id", (req, res) => {
|
||||
router.delete("/git-sources/:id", requireSourceManagementBuildIntegration, (req, res) => {
|
||||
store.gitSources = store.gitSources.filter(
|
||||
(source) => source.id !== req.params.id,
|
||||
);
|
||||
@@ -160,7 +168,7 @@ router.delete("/git-sources/:id", (req, res) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
router.post("/git-sources/:id/test", async (req, res) => {
|
||||
router.post("/git-sources/:id/test", requireSourceManagementBuildIntegration, async (req, res) => {
|
||||
const source = store.gitSources.find((item) => item.id === req.params.id);
|
||||
if (!source) return res.status(404).json({ error: "Source not found" });
|
||||
source.status = "syncing";
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { Request, Response } from "express";
|
||||
import test from "node:test";
|
||||
import {
|
||||
requireSourceManagementBuildIntegration,
|
||||
SOURCE_MANAGEMENT_BUILD_INTEGRATION,
|
||||
sourceManagementCapability,
|
||||
} from "./sourceManagement";
|
||||
|
||||
test("source management reports a fail-closed not-wired build boundary", () => {
|
||||
const capability = sourceManagementCapability({
|
||||
sourceId: "site-definition",
|
||||
repository: "https://git.example.test/site.git",
|
||||
commit: "a".repeat(40),
|
||||
});
|
||||
assert.equal(SOURCE_MANAGEMENT_BUILD_INTEGRATION, false);
|
||||
assert.equal(capability.buildIntegration, "not-wired");
|
||||
assert.equal(capability.mutationsEnabled, false);
|
||||
assert.equal(capability.buildSource.commit, "a".repeat(40));
|
||||
assert.match(capability.message, /Builds use only/);
|
||||
});
|
||||
|
||||
test("source-management mutation middleware rejects direct API calls", () => {
|
||||
let status = 0;
|
||||
let payload: unknown;
|
||||
const response = {
|
||||
status(code: number) {
|
||||
status = code;
|
||||
return this;
|
||||
},
|
||||
json(body: unknown) {
|
||||
payload = body;
|
||||
return this;
|
||||
},
|
||||
} as Response;
|
||||
|
||||
requireSourceManagementBuildIntegration(
|
||||
{} as Request,
|
||||
response,
|
||||
() => assert.fail("disabled source management must not call next"),
|
||||
);
|
||||
assert.equal(status, 409);
|
||||
assert.deepEqual(payload, {
|
||||
code: "E_SOURCE_MANAGEMENT_NOT_WIRED",
|
||||
success: false,
|
||||
message:
|
||||
"Source-management mutations are disabled because repository inventory does not feed BuildInputLoader.",
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { SiteConfig, SourceManagementCapability } from "../types";
|
||||
|
||||
export const SOURCE_MANAGEMENT_BUILD_INTEGRATION = false as const;
|
||||
|
||||
export const sourceManagementCapability = (
|
||||
siteConfig: Pick<SiteConfig, "sourceId" | "repository" | "commit">,
|
||||
): SourceManagementCapability => ({
|
||||
protocol: "labyricorn-source-management/v1",
|
||||
buildIntegration: "not-wired",
|
||||
mutationsEnabled: SOURCE_MANAGEMENT_BUILD_INTEGRATION,
|
||||
buildSource: {
|
||||
sourceId: siteConfig.sourceId ?? "site-definition",
|
||||
repository: siteConfig.repository ?? "unresolved",
|
||||
commit: siteConfig.commit ?? null,
|
||||
},
|
||||
message:
|
||||
"Repository inventory is read-only. Builds use only the Git-owned site-definition snapshot; discovery, add, test, and content-scan actions are not wired to build inputs.",
|
||||
});
|
||||
|
||||
export const requireSourceManagementBuildIntegration: RequestHandler = (
|
||||
_req,
|
||||
res,
|
||||
) => {
|
||||
res.status(409).json({
|
||||
code: "E_SOURCE_MANAGEMENT_NOT_WIRED",
|
||||
success: false,
|
||||
message:
|
||||
"Source-management mutations are disabled because repository inventory does not feed BuildInputLoader.",
|
||||
});
|
||||
};
|
||||
@@ -15,17 +15,19 @@ import {
|
||||
Layers,
|
||||
Image as ImageIcon,
|
||||
} from "lucide-react";
|
||||
import { ContentItem, MediaAsset } from "../types";
|
||||
import { ContentItem, MediaAsset, SourceManagementCapability } from "../types";
|
||||
|
||||
interface ContentTabProps {
|
||||
contentItems: ContentItem[];
|
||||
mediaAssets: MediaAsset[];
|
||||
sourceManagement: SourceManagementCapability;
|
||||
onTriggerDiscovery: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const ContentTab: React.FC<ContentTabProps> = ({
|
||||
contentItems,
|
||||
mediaAssets,
|
||||
sourceManagement,
|
||||
onTriggerDiscovery,
|
||||
}) => {
|
||||
const [selectedItemId, setSelectedItemId] = useState<string>(
|
||||
@@ -50,6 +52,7 @@ export const ContentTab: React.FC<ContentTabProps> = ({
|
||||
item.path.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
item.tags.some((t) => t.toLowerCase().includes(searchTerm.toLowerCase())),
|
||||
);
|
||||
const discoveryEnabled = sourceManagement.mutationsEnabled;
|
||||
|
||||
const handleScan = async () => {
|
||||
setIsScanning(true);
|
||||
@@ -69,18 +72,17 @@ export const ContentTab: React.FC<ContentTabProps> = ({
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-neutral-400 mt-1">
|
||||
Discovers artifacts across repositories, parses front matter,
|
||||
validates YouTube directives, Wikipedia links, raw HTML policy, and
|
||||
media mappings under{" "}
|
||||
<code className="text-[#C5A059] font-mono">/media</code>.
|
||||
Shows artifacts resolved from the immutable site-definition
|
||||
snapshot used by the deterministic build engine.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<button
|
||||
onClick={handleScan}
|
||||
disabled={isScanning}
|
||||
className="px-4 py-2 bg-[#C5A059] hover:bg-[#b38f4a] text-black font-semibold rounded-sm text-xs transition-all flex items-center space-x-1.5 uppercase tracking-wider"
|
||||
disabled={!discoveryEnabled || isScanning}
|
||||
title={discoveryEnabled ? undefined : sourceManagement.message}
|
||||
className="px-4 py-2 bg-[#C5A059] hover:bg-[#b38f4a] text-black font-semibold rounded-sm text-xs transition-all flex items-center space-x-1.5 uppercase tracking-wider disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
<RefreshCw
|
||||
className={`w-3.5 h-3.5 ${isScanning ? "animate-spin" : ""}`}
|
||||
@@ -90,6 +92,16 @@ export const ContentTab: React.FC<ContentTabProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!discoveryEnabled && (
|
||||
<div className="p-4 rounded-sm border border-amber-500/30 bg-amber-950/30 text-amber-200 text-xs flex items-start space-x-3">
|
||||
<AlertTriangle className="w-4 h-4 text-amber-400 shrink-0 mt-0.5" />
|
||||
<p>
|
||||
Live inventory scans are disabled because their results do not feed
|
||||
BuildInputLoader. This view reflects the content resolved from the
|
||||
pinned build source.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{/* SUB-TABS: MARKDOWN VS MEDIA */}
|
||||
<div className="flex border-b border-[#1F1F1F] space-x-6 text-xs font-semibold">
|
||||
<button
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
FileText,
|
||||
CheckCircle2,
|
||||
XCircle,
|
||||
AlertTriangle,
|
||||
RefreshCw,
|
||||
Plus,
|
||||
Key,
|
||||
@@ -15,11 +16,12 @@ import {
|
||||
ChevronRight,
|
||||
Code,
|
||||
} from "lucide-react";
|
||||
import { GitSource, CredentialRef } from "../types";
|
||||
import { GitSource, CredentialRef, SourceManagementCapability } from "../types";
|
||||
|
||||
interface GitSourcesTabProps {
|
||||
sources: GitSource[];
|
||||
credentials: CredentialRef[];
|
||||
sourceManagement: SourceManagementCapability;
|
||||
onAddSource: (src: Partial<GitSource>) => void;
|
||||
onTestSource: (id: string) => Promise<any>;
|
||||
}
|
||||
@@ -27,6 +29,7 @@ interface GitSourcesTabProps {
|
||||
export const GitSourcesTab: React.FC<GitSourcesTabProps> = ({
|
||||
sources,
|
||||
credentials,
|
||||
sourceManagement,
|
||||
onAddSource,
|
||||
onTestSource,
|
||||
}) => {
|
||||
@@ -35,13 +38,7 @@ export const GitSourcesTab: React.FC<GitSourcesTabProps> = ({
|
||||
);
|
||||
const [treeFiles, setTreeFiles] = useState<
|
||||
Array<{ path: string; type: string; size: string }>
|
||||
>([
|
||||
{ path: ".devlogs/pack-validation.md", type: "file", size: "2.8 KB" },
|
||||
{ path: ".devlogs/schema-refactor.md", type: "file", size: "1.9 KB" },
|
||||
{ path: "media/screenshots/header.webp", type: "file", size: "142 KB" },
|
||||
{ path: "src/engine.ts", type: "file", size: "14.2 KB" },
|
||||
{ path: "package.json", type: "file", size: "1.1 KB" },
|
||||
]);
|
||||
>([]);
|
||||
const [isTesting, setIsTesting] = useState<string | null>(null);
|
||||
const [testResult, setTestResult] = useState<{
|
||||
id: string;
|
||||
@@ -127,7 +124,9 @@ export const GitSourcesTab: React.FC<GitSourcesTabProps> = ({
|
||||
setNewType("hybrid");
|
||||
};
|
||||
|
||||
const selectedSource = sources.find((s) => s.id === selectedSourceId);
|
||||
const selectedSource =
|
||||
sources.find((source) => source.id === selectedSourceId) ?? sources[0];
|
||||
const mutationsEnabled = sourceManagement.mutationsEnabled;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -143,16 +142,17 @@ export const GitSourcesTab: React.FC<GitSourcesTabProps> = ({
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-neutral-400 mt-1">
|
||||
Connect standard SSH, HTTPS, or local Git repositories. Repositories
|
||||
without .labyricorn/site.yml are ignored during build.
|
||||
Read-only view of the repository snapshot currently resolved by the
|
||||
deterministic build loader.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-2 self-start md:self-auto">
|
||||
<button
|
||||
onClick={handleDiscover}
|
||||
disabled={isDiscovering}
|
||||
className="px-4 py-2 bg-[#1A1A1A] hover:bg-[#2A2A2A] text-white font-semibold rounded-sm text-xs transition-all flex items-center space-x-1.5 uppercase tracking-wider border border-[#2A2A2A]"
|
||||
disabled={!mutationsEnabled || isDiscovering}
|
||||
title={mutationsEnabled ? undefined : sourceManagement.message}
|
||||
className="px-4 py-2 bg-[#1A1A1A] hover:bg-[#2A2A2A] text-white font-semibold rounded-sm text-xs transition-all flex items-center space-x-1.5 uppercase tracking-wider border border-[#2A2A2A] disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
<RefreshCw
|
||||
className={`w-4 h-4 ${isDiscovering ? "animate-spin" : ""}`}
|
||||
@@ -161,7 +161,9 @@ export const GitSourcesTab: React.FC<GitSourcesTabProps> = ({
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowAddModal(true)}
|
||||
className="px-4 py-2 bg-[#C5A059] hover:bg-[#b38f4a] text-black font-semibold rounded-sm text-xs transition-all flex items-center space-x-1.5 uppercase tracking-wider"
|
||||
disabled={!mutationsEnabled}
|
||||
title={mutationsEnabled ? undefined : sourceManagement.message}
|
||||
className="px-4 py-2 bg-[#C5A059] hover:bg-[#b38f4a] text-black font-semibold rounded-sm text-xs transition-all flex items-center space-x-1.5 uppercase tracking-wider disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
<span>Add Repository</span>
|
||||
@@ -169,6 +171,25 @@ export const GitSourcesTab: React.FC<GitSourcesTabProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!mutationsEnabled && (
|
||||
<div className="p-4 rounded-sm border border-amber-500/30 bg-amber-950/30 text-amber-200 text-xs flex items-start space-x-3">
|
||||
<AlertTriangle className="w-4 h-4 text-amber-400 shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<span className="font-bold block uppercase tracking-wider">
|
||||
Source management is not wired to builds
|
||||
</span>
|
||||
<p className="mt-1 text-amber-100/80">
|
||||
Discovery, add, connectivity test, and content-scan actions are
|
||||
disabled. The next build reads only{" "}
|
||||
<code className="font-mono">
|
||||
{sourceManagement.buildSource.repository}@
|
||||
{sourceManagement.buildSource.commit ?? "unresolved"}
|
||||
</code>{" "}
|
||||
through BuildInputLoader.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* TEST RESULT NOTIFICATION */}
|
||||
{testResult && (
|
||||
<div
|
||||
@@ -205,7 +226,7 @@ export const GitSourcesTab: React.FC<GitSourcesTabProps> = ({
|
||||
{/* LEFT COLUMN: SOURCES LIST (5 COLS) */}
|
||||
<div className="lg:col-span-5 space-y-3">
|
||||
<div className="text-[10px] font-bold text-neutral-500 uppercase tracking-widest px-1">
|
||||
Configured Repositories ({sources.length})
|
||||
Resolved Build Repositories ({sources.length})
|
||||
</div>
|
||||
|
||||
{sources.map((source) => {
|
||||
@@ -294,8 +315,9 @@ export const GitSourcesTab: React.FC<GitSourcesTabProps> = ({
|
||||
e.stopPropagation();
|
||||
handleTest(source.id);
|
||||
}}
|
||||
disabled={isTesting === source.id}
|
||||
className="px-2.5 py-1 bg-[#1A1A1A] hover:bg-[#262626] border border-[#2A2A2A] rounded-xs text-neutral-300 text-[11px] font-medium transition-colors flex items-center space-x-1"
|
||||
disabled={!mutationsEnabled || isTesting === source.id}
|
||||
title={mutationsEnabled ? undefined : sourceManagement.message}
|
||||
className="px-2.5 py-1 bg-[#1A1A1A] hover:bg-[#262626] border border-[#2A2A2A] rounded-xs text-neutral-300 text-[11px] font-medium transition-colors flex items-center space-x-1 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
<RefreshCw
|
||||
className={`w-3 h-3 ${isTesting === source.id ? "animate-spin text-[#C5A059]" : ""}`}
|
||||
|
||||
@@ -24,6 +24,18 @@ export interface GitSource {
|
||||
isMountedLocal?: boolean;
|
||||
}
|
||||
|
||||
export interface SourceManagementCapability {
|
||||
protocol: "labyricorn-source-management/v1";
|
||||
buildIntegration: "not-wired";
|
||||
mutationsEnabled: boolean;
|
||||
buildSource: {
|
||||
sourceId: string;
|
||||
repository: string;
|
||||
commit: string | null;
|
||||
};
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface StyleConfigPackage {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
Reference in New Issue
Block a user