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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ flowchart TD

➡️ Resolve owners from configurable Azure tags such as `ownerGroup`,
`costCenter`, and `owner`. Configure tag names and confidence levels in
[src/core/config.ts](src/core/config.ts).
`data/config.json`.

➡️ Review ownership confidence and supporting evidence.

Expand Down
252 changes: 252 additions & 0 deletions contracts/runtime.openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,258 @@
}
}
},
"/api/data/runtime/config": {
"get": {
"operationId": "readRuntimeAppConfig",
"tags": [
"Runtime"
],
"summary": "Read OwnerLens runtime configuration.",
"parameters": [],
"responses": {
"200": {
"description": "Successful runtime response",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"features",
"azure"
],
"additionalProperties": false,
"properties": {
"features": {
"type": "object",
"required": [
"zeroTrustAssessment"
],
"additionalProperties": false,
"properties": {
"zeroTrustAssessment": {
"type": "boolean"
}
}
},
"azure": {
"type": "object",
"required": [
"ownership"
],
"additionalProperties": false,
"properties": {
"ownership": {
"type": "object",
"required": [
"ownerTags"
],
"additionalProperties": false,
"properties": {
"ownerTags": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"confidence",
"type"
],
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
},
"confidence": {
"enum": [
"high",
"medium",
"low"
]
},
"type": {
"enum": [
"ownerUser",
"ownerGroup",
"ownerTag",
"application",
"unknown"
]
}
}
}
}
}
}
}
}
}
}
}
}
},
"400": {
"description": "Runtime error response",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"additionalProperties": false,
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"additionalProperties": false,
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
}
}
}
},
"401": {
"description": "Runtime error response",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"additionalProperties": false,
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"additionalProperties": false,
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
}
}
}
},
"404": {
"description": "Runtime error response",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"additionalProperties": false,
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"additionalProperties": false,
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
}
}
}
},
"409": {
"description": "Runtime error response",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"additionalProperties": false,
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"additionalProperties": false,
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
}
}
}
},
"500": {
"description": "Runtime error response",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"additionalProperties": false,
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"additionalProperties": false,
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
},
"/api/data/entra/servicePrincipals": {
"get": {
"operationId": "queryEntraServicePrincipals",
Expand Down
73 changes: 49 additions & 24 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,61 @@
import { useEffect, useState } from "react";

import { AzureComponent } from "./components/azure/AzureComponent";
import { AzureInventoryStats } from "./components/azure/AzureInventoryStats";
import { AppConfigProvider } from "./components/azure/AppConfigContext";
import { readAppConfig } from "./components/azure/api";
import { ownerLensVersion } from "./core/buildInfo";
import { appConfig, type AppConfig } from "./core/config";
import { RuntimeErrorToast } from "./components/azure/RuntimeErrorToast";

export default function App() {
const [runtimeConfig, setRuntimeConfig] = useState<AppConfig>(appConfig);

useEffect(() => {
const abortController = new AbortController();

readAppConfig({ signal: abortController.signal })
.then(setRuntimeConfig)
.catch((error: unknown) => {
if (error instanceof DOMException && error.name === "AbortError") {
return;
}

window.dispatchEvent(new CustomEvent("ownerlens:runtimeApiError", { detail: error }));
});

return () => abortController.abort();
}, []);

return (
<main className="min-h-screen bg-background text-foreground">
<RuntimeErrorToast />
<div className="mx-auto flex min-h-screen w-full min-w-0 max-w-none flex-col gap-4 py-4 min-[1920px]:w-[80vw]">
<header className="sticky top-0 z-20 flex flex-wrap items-center justify-between gap-4 border-b border-border bg-background/90 px-4 py-3 backdrop-blur md:px-6">
<div className="shrink-0">
<div className="flex flex-wrap items-baseline gap-2">
<h1 className="text-3xl font-semibold tracking-tight">OwnerLens</h1>
<span
aria-label={`OwnerLens version ${ownerLensVersion}`}
className="rounded border border-border bg-muted px-1.5 py-0.5 text-xs font-medium text-muted-foreground"
title={`OwnerLens version ${ownerLensVersion}`}
>
{ownerLensVersion}
</span>
<AppConfigProvider value={runtimeConfig}>
<main className="min-h-screen bg-background text-foreground">
<RuntimeErrorToast />
<div className="mx-auto flex min-h-screen w-full min-w-0 max-w-none flex-col gap-4 py-4 min-[1920px]:w-[80vw]">
<header className="sticky top-0 z-20 flex flex-wrap items-center justify-between gap-4 border-b border-border bg-background/90 px-4 py-3 backdrop-blur md:px-6">
<div className="shrink-0">
<div className="flex flex-wrap items-baseline gap-2">
<h1 className="text-3xl font-semibold tracking-tight">OwnerLens</h1>
<span
aria-label={`OwnerLens version ${ownerLensVersion}`}
className="rounded border border-border bg-muted px-1.5 py-0.5 text-xs font-medium text-muted-foreground"
title={`OwnerLens version ${ownerLensVersion}`}
>
{ownerLensVersion}
</span>
</div>
<p className="mt-1 text-sm text-muted-foreground">Azure inventory</p>
</div>
<p className="mt-1 text-sm text-muted-foreground">Azure inventory</p>
</div>
<div className="ml-auto min-w-0 max-w-full">
<AzureInventoryStats />
</div>
</header>
<div className="ml-auto min-w-0 max-w-full">
<AzureInventoryStats />
</div>
</header>

<div className="p-[5px]">
<AzureComponent />
<div className="p-[5px]">
<AzureComponent />
</div>
</div>
</div>
</main>
</main>
</AppConfigProvider>
);
}
11 changes: 11 additions & 0 deletions src/components/azure/AppConfigContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createContext, useContext } from "react";

import { appConfig, type AppConfig } from "../../core/config";

const AppConfigContext = createContext<AppConfig>(appConfig);

export const AppConfigProvider = AppConfigContext.Provider;

export function useAppConfig(): AppConfig {
return useContext(AppConfigContext);
}
Loading
Loading