feat(plugin): add future runtime distribution controls
Some checks failed
Go / build (.exe, 386, windows, windows-386) (push) Has been cancelled
Go / build (.exe, amd64, windows, windows-amd64) (push) Has been cancelled
Go / build (.exe, arm64, windows, windows-arm64) (push) Has been cancelled
Go / build (386, freebsd, freebsd-386) (push) Has been cancelled
Go / build (386, linux, linux-386) (push) Has been cancelled
Go / build (386, netbsd, netbsd-386) (push) Has been cancelled
Go / build (386, openbsd, openbsd-386) (push) Has been cancelled
Go / build (386, plan9, plan9-386) (push) Has been cancelled
Go / build (amd64, darwin, darwin-amd64) (push) Has been cancelled
Go / build (amd64, dragonfly, dragonfly-amd64) (push) Has been cancelled
Go / build (amd64, freebsd, freebsd-amd64) (push) Has been cancelled
Go / build (amd64, illumos, illumos-amd64) (push) Has been cancelled
Go / build (amd64, linux, linux-amd64) (push) Has been cancelled
Go / build (amd64, netbsd, netbsd-amd64) (push) Has been cancelled
Go / build (amd64, openbsd, openbsd-amd64) (push) Has been cancelled
Go / build (amd64, plan9, plan9-amd64) (push) Has been cancelled
Go / build (amd64, solaris, solaris-amd64) (push) Has been cancelled
Go / build (arm, 6, linux, linux-armv6) (push) Has been cancelled
Go / build (arm, 7, linux, linux-armv7) (push) Has been cancelled
Go / build (arm, freebsd, freebsd-arm) (push) Has been cancelled
Go / build (arm, netbsd, netbsd-arm) (push) Has been cancelled
Go / build (arm, openbsd, openbsd-arm) (push) Has been cancelled
Go / build (arm, plan9, plan9-arm) (push) Has been cancelled
Go / build (arm64, darwin, darwin-arm64) (push) Has been cancelled
Go / build (arm64, freebsd, freebsd-arm64) (push) Has been cancelled
Go / build (arm64, linux, linux-arm64) (push) Has been cancelled
Go / build (arm64, netbsd, netbsd-arm64) (push) Has been cancelled
Go / build (arm64, openbsd, openbsd-arm64) (push) Has been cancelled
Go / build (loong64, linux, linux-loong64) (push) Has been cancelled
Go / build (mips, linux, linux-mips) (push) Has been cancelled
Go / build (mips64, linux, linux-mips64) (push) Has been cancelled
Go / build (mips64le, linux, linux-mips64le) (push) Has been cancelled
Go / build (mipsle, linux, linux-mipsle) (push) Has been cancelled
Go / build (ppc64, aix, aix-ppc64) (push) Has been cancelled
Go / build (ppc64, linux, linux-ppc64) (push) Has been cancelled
Go / build (ppc64, openbsd, openbsd-ppc64) (push) Has been cancelled
Go / build (ppc64le, linux, linux-ppc64le) (push) Has been cancelled
Go / build (riscv64, freebsd, freebsd-riscv64) (push) Has been cancelled
Go / build (riscv64, linux, linux-riscv64) (push) Has been cancelled
Go / build (riscv64, openbsd, openbsd-riscv64) (push) Has been cancelled
Go / build (s390x, linux, linux-s390x) (push) Has been cancelled
Go / merge-artifacts (push) Has been cancelled
Docker Image / docker (push) Has been cancelled
Some checks failed
Go / build (.exe, 386, windows, windows-386) (push) Has been cancelled
Go / build (.exe, amd64, windows, windows-amd64) (push) Has been cancelled
Go / build (.exe, arm64, windows, windows-arm64) (push) Has been cancelled
Go / build (386, freebsd, freebsd-386) (push) Has been cancelled
Go / build (386, linux, linux-386) (push) Has been cancelled
Go / build (386, netbsd, netbsd-386) (push) Has been cancelled
Go / build (386, openbsd, openbsd-386) (push) Has been cancelled
Go / build (386, plan9, plan9-386) (push) Has been cancelled
Go / build (amd64, darwin, darwin-amd64) (push) Has been cancelled
Go / build (amd64, dragonfly, dragonfly-amd64) (push) Has been cancelled
Go / build (amd64, freebsd, freebsd-amd64) (push) Has been cancelled
Go / build (amd64, illumos, illumos-amd64) (push) Has been cancelled
Go / build (amd64, linux, linux-amd64) (push) Has been cancelled
Go / build (amd64, netbsd, netbsd-amd64) (push) Has been cancelled
Go / build (amd64, openbsd, openbsd-amd64) (push) Has been cancelled
Go / build (amd64, plan9, plan9-amd64) (push) Has been cancelled
Go / build (amd64, solaris, solaris-amd64) (push) Has been cancelled
Go / build (arm, 6, linux, linux-armv6) (push) Has been cancelled
Go / build (arm, 7, linux, linux-armv7) (push) Has been cancelled
Go / build (arm, freebsd, freebsd-arm) (push) Has been cancelled
Go / build (arm, netbsd, netbsd-arm) (push) Has been cancelled
Go / build (arm, openbsd, openbsd-arm) (push) Has been cancelled
Go / build (arm, plan9, plan9-arm) (push) Has been cancelled
Go / build (arm64, darwin, darwin-arm64) (push) Has been cancelled
Go / build (arm64, freebsd, freebsd-arm64) (push) Has been cancelled
Go / build (arm64, linux, linux-arm64) (push) Has been cancelled
Go / build (arm64, netbsd, netbsd-arm64) (push) Has been cancelled
Go / build (arm64, openbsd, openbsd-arm64) (push) Has been cancelled
Go / build (loong64, linux, linux-loong64) (push) Has been cancelled
Go / build (mips, linux, linux-mips) (push) Has been cancelled
Go / build (mips64, linux, linux-mips64) (push) Has been cancelled
Go / build (mips64le, linux, linux-mips64le) (push) Has been cancelled
Go / build (mipsle, linux, linux-mipsle) (push) Has been cancelled
Go / build (ppc64, aix, aix-ppc64) (push) Has been cancelled
Go / build (ppc64, linux, linux-ppc64) (push) Has been cancelled
Go / build (ppc64, openbsd, openbsd-ppc64) (push) Has been cancelled
Go / build (ppc64le, linux, linux-ppc64le) (push) Has been cancelled
Go / build (riscv64, freebsd, freebsd-riscv64) (push) Has been cancelled
Go / build (riscv64, linux, linux-riscv64) (push) Has been cancelled
Go / build (riscv64, openbsd, openbsd-riscv64) (push) Has been cancelled
Go / build (s390x, linux, linux-s390x) (push) Has been cancelled
Go / merge-artifacts (push) Has been cancelled
Docker Image / docker (push) Has been cancelled
This commit is contained in:
@@ -29,24 +29,28 @@ func newAdminAPIHandler() http.HandlerFunc {
|
||||
|
||||
AuditLogs: handleAdminAuditLogs,
|
||||
|
||||
PluginArtifacts: handleAdminPluginArtifacts,
|
||||
PluginArtifact: handleAdminPluginArtifact,
|
||||
PluginSources: handleAdminPluginSources,
|
||||
PluginBuilds: handleAdminPluginBuilds,
|
||||
PluginBuild: handleAdminPluginBuild,
|
||||
PluginGC: handleAdminPluginGC,
|
||||
PluginOperationsGC: handleAdminPluginOperationsGC,
|
||||
PluginsList: handleAdminPluginsList,
|
||||
PluginItem: handleAdminPluginItem,
|
||||
PluginAction: handleAdminPluginAction,
|
||||
PluginConfig: handleAdminPluginConfig,
|
||||
PluginSecrets: handleAdminPluginSecrets,
|
||||
PluginRollback: handleAdminPluginRollback,
|
||||
PluginOperations: handleAdminPluginOperations,
|
||||
PluginDraining: handleAdminPluginDraining,
|
||||
PluginDispatch: handleAdminPluginDispatchPlan,
|
||||
PluginGovernance: handleAdminPluginGovernance,
|
||||
PluginAdvisories: handleAdminPluginAdvisories,
|
||||
PluginDiagnostics: handleAdminPluginDiagnostics,
|
||||
PluginArtifacts: handleAdminPluginArtifacts,
|
||||
PluginArtifact: handleAdminPluginArtifact,
|
||||
PluginSources: handleAdminPluginSources,
|
||||
PluginBuilds: handleAdminPluginBuilds,
|
||||
PluginBuild: handleAdminPluginBuild,
|
||||
PluginGC: handleAdminPluginGC,
|
||||
PluginOperationsGC: handleAdminPluginOperationsGC,
|
||||
PluginsList: handleAdminPluginsList,
|
||||
PluginItem: handleAdminPluginItem,
|
||||
PluginAction: handleAdminPluginAction,
|
||||
PluginConfig: handleAdminPluginConfig,
|
||||
PluginSecrets: handleAdminPluginSecrets,
|
||||
PluginRollback: handleAdminPluginRollback,
|
||||
PluginOperations: handleAdminPluginOperations,
|
||||
PluginDraining: handleAdminPluginDraining,
|
||||
PluginDispatch: handleAdminPluginDispatchPlan,
|
||||
PluginGovernance: handleAdminPluginGovernance,
|
||||
PluginAdvisories: handleAdminPluginAdvisories,
|
||||
PluginDiagnostics: handleAdminPluginDiagnostics,
|
||||
PluginService: handleAdminPluginService,
|
||||
PluginRepositories: handleAdminPluginRepositories,
|
||||
PluginSupplyChain: handleAdminPluginSupplyChain,
|
||||
PluginInstrumentation: handleAdminPluginInstrumentation,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PluginArtifact, PluginBuild, PluginView, RouteRecord, ServiceRecord, User } from "./types.js";
|
||||
import type { PluginArtifact, PluginBuild, PluginInstrumentation, PluginServiceStatus, PluginView, RouteRecord, ServiceRecord, User } from "./types.js";
|
||||
|
||||
export const tokenStorageKey = "mcGatewayAdminToken";
|
||||
export const languageStorageKey = "mcGatewayAdminLanguage";
|
||||
@@ -14,6 +14,8 @@ export interface AppState {
|
||||
plugins: PluginView[];
|
||||
pluginArtifacts: PluginArtifact[];
|
||||
pluginBuilds: PluginBuild[];
|
||||
pluginService: PluginServiceStatus | null;
|
||||
pluginInstrumentation: PluginInstrumentation[];
|
||||
selectedPluginID: string;
|
||||
selectedArtifactID: string;
|
||||
}
|
||||
@@ -29,6 +31,8 @@ export const state: AppState = {
|
||||
plugins: [],
|
||||
pluginArtifacts: [],
|
||||
pluginBuilds: [],
|
||||
pluginService: null,
|
||||
pluginInstrumentation: [],
|
||||
selectedPluginID: "",
|
||||
selectedArtifactID: "",
|
||||
};
|
||||
|
||||
@@ -117,6 +117,44 @@ export interface PluginProxyConnection {
|
||||
draining: boolean;
|
||||
}
|
||||
|
||||
export interface PluginServiceState {
|
||||
desired_mode: string;
|
||||
active_mode: string;
|
||||
applied_at?: number;
|
||||
restart_required: boolean;
|
||||
live_migration?: string;
|
||||
last_error?: string;
|
||||
updated_by?: string;
|
||||
updated_at?: number;
|
||||
}
|
||||
|
||||
export interface PluginHostRuntimeSummary {
|
||||
plugin_id: string;
|
||||
artifact_id: string;
|
||||
state: string;
|
||||
drain_mode: string;
|
||||
crash_loop: boolean;
|
||||
crash_count: number;
|
||||
last_error?: string;
|
||||
}
|
||||
|
||||
export interface PluginServiceStatus {
|
||||
service: PluginServiceState;
|
||||
hosts?: PluginHostRuntimeSummary[];
|
||||
}
|
||||
|
||||
export interface PluginInstrumentation {
|
||||
id: number;
|
||||
name: string;
|
||||
version: string;
|
||||
profile: string;
|
||||
generated_diff_hash: string;
|
||||
runbook_rollback: string;
|
||||
status: string;
|
||||
created_by?: string;
|
||||
created_at?: number;
|
||||
}
|
||||
|
||||
export interface GovernanceIssue {
|
||||
code: string;
|
||||
severity: string;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { showAlert } from "../alerts.js";
|
||||
import { badge, el, escapeAttr, escapeHTML, getFormInput } from "../dom.js";
|
||||
import { isAdmin } from "../session.js";
|
||||
import { state } from "../state.js";
|
||||
import type { PluginArtifact, PluginBuild, PluginDryRunResult, PluginOperations, PluginProxyConnection, PluginSecret, PluginSnapshot, PluginView } from "../types.js";
|
||||
import type { PluginArtifact, PluginBuild, PluginDryRunResult, PluginInstrumentation, PluginOperations, PluginProxyConnection, PluginSecret, PluginServiceStatus, PluginSnapshot, PluginView } from "../types.js";
|
||||
|
||||
interface PluginsResponse {
|
||||
plugins?: PluginView[];
|
||||
@@ -17,6 +17,14 @@ interface BuildsResponse {
|
||||
builds?: PluginBuild[];
|
||||
}
|
||||
|
||||
interface PluginServiceResponse {
|
||||
plugin_service?: PluginServiceStatus;
|
||||
}
|
||||
|
||||
interface InstrumentationResponse {
|
||||
instrumentation?: PluginInstrumentation[];
|
||||
}
|
||||
|
||||
interface PluginResponse {
|
||||
plugin?: PluginView;
|
||||
}
|
||||
@@ -42,14 +50,18 @@ interface OperationsResponse {
|
||||
|
||||
export async function loadPlugins(): Promise<void> {
|
||||
try {
|
||||
const [data, artifacts, builds] = await Promise.all([
|
||||
const [data, artifacts, builds, service, instrumentation] = await Promise.all([
|
||||
api<PluginsResponse>("/plugins"),
|
||||
api<ArtifactsResponse>("/plugin-artifacts"),
|
||||
api<BuildsResponse>("/plugin-builds"),
|
||||
api<PluginServiceResponse>("/plugin-service"),
|
||||
api<InstrumentationResponse>("/plugin-instrumentation"),
|
||||
]);
|
||||
state.plugins = data.plugins || [];
|
||||
state.pluginArtifacts = artifacts.artifacts || [];
|
||||
state.pluginBuilds = builds.builds || [];
|
||||
state.pluginService = service.plugin_service || null;
|
||||
state.pluginInstrumentation = instrumentation.instrumentation || [];
|
||||
const firstPlugin = state.plugins[0];
|
||||
if (!state.selectedPluginID && firstPlugin) {
|
||||
state.selectedPluginID = firstPlugin.id;
|
||||
@@ -68,6 +80,7 @@ export async function loadPlugins(): Promise<void> {
|
||||
export function renderPlugins(): void {
|
||||
const managed = new Set(state.plugins.map((plugin) => plugin.id));
|
||||
const unmanagedArtifacts = state.pluginArtifacts.filter((artifact) => !managed.has(artifact.plugin_id));
|
||||
renderPluginServicePanel();
|
||||
el("pluginsBody").innerHTML = state.plugins.map((plugin) => `
|
||||
<tr class="${plugin.id === state.selectedPluginID ? "selected" : ""}">
|
||||
<td><button class="link-button" type="button" data-plugin-detail="${escapeAttr(plugin.id)}">${escapeHTML(plugin.id)}</button></td>
|
||||
@@ -243,6 +256,83 @@ export function bindPluginEvents(): void {
|
||||
el<HTMLButtonElement>("refreshPluginsBtn").addEventListener("click", loadPlugins);
|
||||
}
|
||||
|
||||
function renderPluginServicePanel(): void {
|
||||
const container = document.getElementById("pluginServicePanel");
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
const service = state.pluginService?.service;
|
||||
const canWrite = isAdmin();
|
||||
container.innerHTML = `
|
||||
<section class="panel">
|
||||
<div class="detail-header compact">
|
||||
<div>
|
||||
<h3>Plugin Service</h3>
|
||||
<p>${service ? `active ${escapeHTML(service.active_mode)} · desired ${escapeHTML(service.desired_mode)}` : "not loaded"}</p>
|
||||
</div>
|
||||
${service ? badge(service.restart_required ? "restart required" : "applied", service.restart_required) : ""}
|
||||
</div>
|
||||
${service ? `
|
||||
<div class="status-grid dense">
|
||||
${detailStat("Desired mode", service.desired_mode)}
|
||||
${detailStat("Active mode", service.active_mode)}
|
||||
${detailStat("Migration", service.live_migration || "drain-only")}
|
||||
${detailStat("Restart", service.restart_required ? "required" : "not required")}
|
||||
</div>
|
||||
${service.last_error ? `<div class="alert inline-alert">${escapeHTML(service.last_error)}</div>` : ""}
|
||||
${canWrite ? `
|
||||
<form id="pluginServiceForm" class="inline-form">
|
||||
<select name="desired_mode">
|
||||
${["in-process", "go-plugin-process", "sandbox-process"].map((mode) => `<option value="${mode}" ${mode === service.desired_mode ? "selected" : ""}>${mode}</option>`).join("")}
|
||||
</select>
|
||||
<button type="submit">Set desired</button>
|
||||
</form>
|
||||
` : ""}
|
||||
` : ""}
|
||||
</section>
|
||||
<section class="panel">
|
||||
<h3>Build-Time Instrumentation</h3>
|
||||
${instrumentationList(state.pluginInstrumentation)}
|
||||
</section>
|
||||
`;
|
||||
const form = document.getElementById("pluginServiceForm");
|
||||
if (form instanceof HTMLFormElement) {
|
||||
form.addEventListener("submit", updatePluginServiceMode);
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePluginServiceMode(event: Event): Promise<void> {
|
||||
event.preventDefault();
|
||||
const form = event.currentTarget as HTMLFormElement;
|
||||
try {
|
||||
await api("/plugin-service", {
|
||||
method: "PUT",
|
||||
body: { desired_mode: getFormInput(form, "desired_mode") },
|
||||
});
|
||||
await loadPlugins();
|
||||
} catch (err) {
|
||||
showAlert((err as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
function instrumentationList(records: PluginInstrumentation[]): string {
|
||||
if (!records.length) {
|
||||
return `<p class="muted">No instrumentation metadata</p>`;
|
||||
}
|
||||
return `<table class="mini-table">
|
||||
<thead><tr><th>Name</th><th>Profile</th><th>Status</th><th>Diff</th><th>Rollback</th></tr></thead>
|
||||
<tbody>${records.map((record) => `
|
||||
<tr>
|
||||
<td>${escapeHTML(record.name)} ${escapeHTML(record.version || "")}</td>
|
||||
<td>${escapeHTML(record.profile || "")}</td>
|
||||
<td>${badge(record.status || "available", record.status === "blocked")}</td>
|
||||
<td>${escapeHTML(shortID(record.generated_diff_hash || ""))}</td>
|
||||
<td>${escapeHTML(record.runbook_rollback || "")}</td>
|
||||
</tr>
|
||||
`).join("")}</tbody>
|
||||
</table>`;
|
||||
}
|
||||
|
||||
async function uploadPluginPackage(event: Event): Promise<void> {
|
||||
const input = event.currentTarget as HTMLInputElement;
|
||||
const file = input.files?.[0];
|
||||
|
||||
@@ -1346,3 +1346,186 @@ func writePluginManagerError(w http.ResponseWriter, err error) {
|
||||
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func handleAdminPluginService(w http.ResponseWriter, r *http.Request) {
|
||||
session, ok := requireRole(w, r, adminRoleMember)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if pluginsManager == nil {
|
||||
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
status, err := pluginsManager.PluginServiceStatus(r.Context())
|
||||
if err != nil {
|
||||
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"plugin_service": status})
|
||||
case http.MethodPut:
|
||||
if session.Role != adminRoleAdmin {
|
||||
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
||||
return
|
||||
}
|
||||
var req adminhttp.PluginServiceRequest
|
||||
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
||||
return
|
||||
}
|
||||
state, err := pluginsManager.SetPluginServiceDesired(r.Context(), session.Username, req.DesiredMode)
|
||||
if err != nil {
|
||||
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_service_mode_update", "plugin_service", "", false, err.Error())
|
||||
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_service_mode_update", "plugin_service", "", true, "plugin service mode desired state updated", map[string]any{
|
||||
"desired_mode": state.DesiredMode,
|
||||
"active_mode": state.ActiveMode,
|
||||
"restart_required": state.RestartRequired,
|
||||
})
|
||||
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"plugin_service": state})
|
||||
case http.MethodPost:
|
||||
if session.Role != adminRoleAdmin {
|
||||
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
||||
return
|
||||
}
|
||||
if err := pluginsManager.ApplyPluginServiceMode(r.Context()); err != nil {
|
||||
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
status, err := pluginsManager.PluginServiceStatus(r.Context())
|
||||
if err != nil {
|
||||
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"plugin_service": status})
|
||||
default:
|
||||
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func handleAdminPluginRepositories(w http.ResponseWriter, r *http.Request) {
|
||||
session, ok := requireRole(w, r, adminRoleMember)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if pluginsManager == nil {
|
||||
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
imports, err := pluginsManager.ListRepositoryImports(r.Context())
|
||||
if err != nil {
|
||||
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"imports": imports})
|
||||
case http.MethodPost:
|
||||
if session.Role != adminRoleAdmin {
|
||||
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
||||
return
|
||||
}
|
||||
var req adminhttp.PluginRepositoryImportRequest
|
||||
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
||||
return
|
||||
}
|
||||
record, artifact, err := pluginsManager.ImportRepositoryArtifact(r.Context(), session.Username, pluginmanager.RepositoryImportRequest{
|
||||
RepositoryType: req.RepositoryType,
|
||||
IndexPath: req.IndexPath,
|
||||
ArtifactID: req.ArtifactID,
|
||||
PluginID: req.PluginID,
|
||||
Version: req.Version,
|
||||
TrustPolicy: req.TrustPolicy,
|
||||
})
|
||||
if err != nil {
|
||||
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_repository_import", "plugin_repository", req.IndexPath, false, err.Error())
|
||||
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_repository_import", "plugin_artifact", artifact.ID, true, "repository artifact imported locally", map[string]any{
|
||||
"plugin_id": artifact.PluginID,
|
||||
"version": artifact.Version,
|
||||
"auto_enable": false,
|
||||
"import_id": record.ID,
|
||||
})
|
||||
adminhttp.WriteJSON(w, http.StatusCreated, map[string]any{"import": record, "artifact": artifact})
|
||||
default:
|
||||
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func handleAdminPluginSupplyChain(w http.ResponseWriter, r *http.Request) {
|
||||
session, ok := requireRole(w, r, adminRoleMember)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if pluginsManager == nil {
|
||||
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
assessments, err := pluginsManager.ListSupplyChainAssessments(r.Context(), r.URL.Query().Get("plugin_id"), r.URL.Query().Get("artifact_id"))
|
||||
if err != nil {
|
||||
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"assessments": assessments})
|
||||
case http.MethodPost:
|
||||
if session.Role != adminRoleAdmin {
|
||||
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
||||
return
|
||||
}
|
||||
var req adminhttp.PluginSupplyChainRequest
|
||||
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
||||
return
|
||||
}
|
||||
assessment, err := pluginsManager.AssessSupplyChain(r.Context(), session.Username, req.PluginID, req.ArtifactID, req.Metadata)
|
||||
if err != nil {
|
||||
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
adminhttp.WriteJSON(w, http.StatusCreated, map[string]any{"assessment": assessment})
|
||||
default:
|
||||
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func handleAdminPluginInstrumentation(w http.ResponseWriter, r *http.Request) {
|
||||
session, ok := requireRole(w, r, adminRoleMember)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if pluginsManager == nil {
|
||||
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
records, err := pluginsManager.ListInstrumentation(r.Context())
|
||||
if err != nil {
|
||||
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"instrumentation": records})
|
||||
case http.MethodPost:
|
||||
if session.Role != adminRoleAdmin {
|
||||
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
|
||||
return
|
||||
}
|
||||
var req pluginmanager.InstrumentationRequest
|
||||
if !adminhttp.DecodeJSONRequest(w, r, &req) {
|
||||
return
|
||||
}
|
||||
record, err := pluginsManager.SaveInstrumentation(r.Context(), session.Username, req)
|
||||
if err != nil {
|
||||
adminhttp.WriteAPIError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
adminhttp.WriteJSON(w, http.StatusCreated, map[string]any{"instrumentation": record})
|
||||
default:
|
||||
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
</label>
|
||||
<button id="refreshPluginsBtn" class="secondary" type="button" data-i18n="refresh">Refresh</button>
|
||||
</div>
|
||||
<div id="pluginServicePanel" class="plugin-service-panel"></div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
Reference in New Issue
Block a user