@meshxdata/fops 0.1.52 → 0.1.53
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/CHANGELOG.md +372 -0
- package/package.json +2 -6
- package/src/agent/agent.js +6 -0
- package/src/commands/setup.js +34 -0
- package/src/fleet-registry.js +38 -2
- package/src/plugins/__test-fixtures__/fake-plugin.js +2 -0
- package/src/plugins/__test-fixtures__/no-register-plugin.js +2 -0
- package/src/plugins/__test-fixtures__/with-register/index.js +2 -0
- package/src/plugins/__test-fixtures__/without-register/index.js +2 -0
- package/src/plugins/api.js +4 -0
- package/src/plugins/builtins/docker-compose.js +59 -0
- package/src/plugins/bundled/fops-plugin-azure/index.js +4 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-core.js +44 -53
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-storage.js +2 -2
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-cost.js +52 -22
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-helpers.js +6 -2
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-ops.js +113 -7
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-provision-init.js +13 -4
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-provision.js +91 -14
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-service.js +507 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-sync.js +146 -7
- package/src/plugins/bundled/fops-plugin-azure/lib/azure.js +1 -1
- package/src/plugins/bundled/fops-plugin-azure/lib/commands/vm-cmds.js +61 -0
- package/src/plugins/bundled/fops-plugin-cloud/api.js +712 -0
- package/src/plugins/bundled/fops-plugin-cloud/fops.plugin.json +6 -0
- package/src/plugins/bundled/fops-plugin-cloud/index.js +208 -0
- package/src/plugins/bundled/fops-plugin-cloud/lib/azure-provider.js +81 -0
- package/src/plugins/bundled/fops-plugin-cloud/lib/provider.js +50 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/dist/assets/favicon-C49brna2.svg +15 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/dist/assets/index-CVqQ_kKW.js +65 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/dist/assets/index-DZetahP3.css +1 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/dist/index.html +28 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/index.html +27 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/package-lock.json +2634 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/package.json +29 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/postcss.config.cjs +5 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/App.jsx +32 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/api/client.js +114 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/api/queries.js +111 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/components/LogPanel.jsx +162 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/components/ThemeToggle.jsx +46 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/css/additional-styles/utility-patterns.css +147 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/css/style.css +138 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/favicon.svg +15 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/lib/utils.ts +19 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/main.jsx +25 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/Audit.jsx +164 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/Costs.jsx +305 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/CreateResource.jsx +285 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/Fleet.jsx +307 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/Resources.jsx +229 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/partials/Header.jsx +132 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/partials/Sidebar.jsx +174 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/partials/SidebarLinkGroup.jsx +21 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/AuthContext.jsx +170 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/Info.jsx +49 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/ThemeContext.jsx +37 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/Transition.jsx +116 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/Utils.js +63 -0
- package/src/plugins/bundled/fops-plugin-cloud/ui/vite.config.js +23 -0
- package/src/plugins/bundled/fops-plugin-foundation/test-helpers.js +65 -0
- package/src/plugins/loader.js +34 -1
- package/src/plugins/registry.js +15 -0
- package/src/plugins/schemas.js +17 -0
- package/src/project.js +1 -1
- package/src/serve.js +196 -2
- package/src/shell.js +21 -1
- package/src/web/admin.html.js +236 -0
- package/src/web/api.js +73 -0
- package/src/web/dist/assets/index-BphVaAUd.css +1 -0
- package/src/web/dist/assets/index-CSckLzuG.js +129 -0
- package/src/web/dist/index.html +2 -2
- package/src/web/frontend/index.html +16 -0
- package/src/web/frontend/src/App.jsx +445 -0
- package/src/web/frontend/src/components/ChatView.jsx +910 -0
- package/src/web/frontend/src/components/InputBox.jsx +523 -0
- package/src/web/frontend/src/components/Sidebar.jsx +410 -0
- package/src/web/frontend/src/components/StatusBar.jsx +37 -0
- package/src/web/frontend/src/components/TabBar.jsx +87 -0
- package/src/web/frontend/src/hooks/useWebSocket.js +412 -0
- package/src/web/frontend/src/index.css +78 -0
- package/src/web/frontend/src/main.jsx +6 -0
- package/src/web/frontend/vite.config.js +21 -0
- package/src/web/server.js +64 -1
- package/src/web/dist/assets/index-NXC8Hvnp.css +0 -1
- package/src/web/dist/assets/index-QH1N4ejK.js +0 -112
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=fallback');
|
|
2
|
+
|
|
3
|
+
@import 'tailwindcss';
|
|
4
|
+
@import './additional-styles/utility-patterns.css' layer(components);
|
|
5
|
+
|
|
6
|
+
@plugin "@tailwindcss/forms" {
|
|
7
|
+
strategy: base;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@custom-variant dark (&:is(.dark *));
|
|
11
|
+
@custom-variant sidebar-expanded (&:is(.sidebar-expanded *));
|
|
12
|
+
|
|
13
|
+
@theme {
|
|
14
|
+
--shadow-sm: 0 1px 1px 0 rgb(0 0 0 / 0.05), 0 1px 2px 0 rgb(0 0 0 / 0.02);
|
|
15
|
+
|
|
16
|
+
--color-gray-50: #f9fafb;
|
|
17
|
+
--color-gray-100: #f3f4f6;
|
|
18
|
+
--color-gray-200: #e5e7eb;
|
|
19
|
+
--color-gray-300: #bfc4cd;
|
|
20
|
+
--color-gray-400: #9ca3af;
|
|
21
|
+
--color-gray-500: #6b7280;
|
|
22
|
+
--color-gray-600: #4b5563;
|
|
23
|
+
--color-gray-700: #374151;
|
|
24
|
+
--color-gray-800: #1f2937;
|
|
25
|
+
--color-gray-900: #111827;
|
|
26
|
+
--color-gray-950: #030712;
|
|
27
|
+
|
|
28
|
+
--color-violet-50: #f1eeff;
|
|
29
|
+
--color-violet-100: #e6e1ff;
|
|
30
|
+
--color-violet-200: #d2cbff;
|
|
31
|
+
--color-violet-300: #b7acff;
|
|
32
|
+
--color-violet-400: #9c8cff;
|
|
33
|
+
--color-violet-500: #8470ff;
|
|
34
|
+
--color-violet-600: #755ff8;
|
|
35
|
+
--color-violet-700: #5d47de;
|
|
36
|
+
--color-violet-800: #4634b1;
|
|
37
|
+
--color-violet-900: #2f227c;
|
|
38
|
+
--color-violet-950: #1c1357;
|
|
39
|
+
|
|
40
|
+
--color-sky-50: #e3f3ff;
|
|
41
|
+
--color-sky-100: #d1ecff;
|
|
42
|
+
--color-sky-200: #b6e1ff;
|
|
43
|
+
--color-sky-300: #a0d7ff;
|
|
44
|
+
--color-sky-400: #7bc8ff;
|
|
45
|
+
--color-sky-500: #67bfff;
|
|
46
|
+
--color-sky-600: #56b1f3;
|
|
47
|
+
--color-sky-700: #3193da;
|
|
48
|
+
--color-sky-800: #1c71ae;
|
|
49
|
+
--color-sky-900: #124d79;
|
|
50
|
+
--color-sky-950: #0b324f;
|
|
51
|
+
|
|
52
|
+
--color-green-50: #d2ffe2;
|
|
53
|
+
--color-green-100: #b1fdcd;
|
|
54
|
+
--color-green-200: #8bf0b0;
|
|
55
|
+
--color-green-300: #67e294;
|
|
56
|
+
--color-green-400: #4bd37d;
|
|
57
|
+
--color-green-500: #3ec972;
|
|
58
|
+
--color-green-600: #34bd68;
|
|
59
|
+
--color-green-700: #239f52;
|
|
60
|
+
--color-green-800: #15773a;
|
|
61
|
+
--color-green-900: #0f5429;
|
|
62
|
+
--color-green-950: #0a3f1e;
|
|
63
|
+
|
|
64
|
+
--color-red-50: #ffe8e8;
|
|
65
|
+
--color-red-100: #ffd1d1;
|
|
66
|
+
--color-red-200: #ffb2b2;
|
|
67
|
+
--color-red-300: #ff9494;
|
|
68
|
+
--color-red-400: #ff7474;
|
|
69
|
+
--color-red-500: #ff5656;
|
|
70
|
+
--color-red-600: #fa4949;
|
|
71
|
+
--color-red-700: #e63939;
|
|
72
|
+
--color-red-800: #c52727;
|
|
73
|
+
--color-red-900: #941818;
|
|
74
|
+
--color-red-950: #600f0f;
|
|
75
|
+
|
|
76
|
+
--color-yellow-50: #fff2c9;
|
|
77
|
+
--color-yellow-100: #ffe7a0;
|
|
78
|
+
--color-yellow-200: #ffe081;
|
|
79
|
+
--color-yellow-300: #ffd968;
|
|
80
|
+
--color-yellow-400: #f7cd4c;
|
|
81
|
+
--color-yellow-500: #f0bb33;
|
|
82
|
+
--color-yellow-600: #dfad2b;
|
|
83
|
+
--color-yellow-700: #bc9021;
|
|
84
|
+
--color-yellow-800: #816316;
|
|
85
|
+
--color-yellow-900: #4f3d0e;
|
|
86
|
+
--color-yellow-950: #342809;
|
|
87
|
+
|
|
88
|
+
--font-inter: "Inter", "sans-serif";
|
|
89
|
+
|
|
90
|
+
--text-xs: 0.75rem;
|
|
91
|
+
--text-xs--line-height: 1.5;
|
|
92
|
+
--text-sm: 0.875rem;
|
|
93
|
+
--text-sm--line-height: 1.5715;
|
|
94
|
+
--text-base: 1rem;
|
|
95
|
+
--text-base--line-height: 1.5;
|
|
96
|
+
--text-base--letter-spacing: -0.01em;
|
|
97
|
+
--text-lg: 1.125rem;
|
|
98
|
+
--text-lg--line-height: 1.5;
|
|
99
|
+
--text-lg--letter-spacing: -0.01em;
|
|
100
|
+
--text-xl: 1.25rem;
|
|
101
|
+
--text-xl--line-height: 1.5;
|
|
102
|
+
--text-xl--letter-spacing: -0.01em;
|
|
103
|
+
--text-2xl: 1.5rem;
|
|
104
|
+
--text-2xl--line-height: 1.33;
|
|
105
|
+
--text-2xl--letter-spacing: -0.01em;
|
|
106
|
+
--text-3xl: 1.88rem;
|
|
107
|
+
--text-3xl--line-height: 1.33;
|
|
108
|
+
--text-3xl--letter-spacing: -0.01em;
|
|
109
|
+
--text-4xl: 2.25rem;
|
|
110
|
+
--text-4xl--line-height: 1.25;
|
|
111
|
+
--text-4xl--letter-spacing: -0.02em;
|
|
112
|
+
--text-5xl: 3rem;
|
|
113
|
+
--text-5xl--line-height: 1.25;
|
|
114
|
+
--text-5xl--letter-spacing: -0.02em;
|
|
115
|
+
--text-6xl: 3.75rem;
|
|
116
|
+
--text-6xl--line-height: 1.2;
|
|
117
|
+
--text-6xl--letter-spacing: -0.02em;
|
|
118
|
+
|
|
119
|
+
--breakpoint-xs: 480px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/*
|
|
123
|
+
The default border color has changed to `currentColor` in Tailwind CSS v4,
|
|
124
|
+
so we've added these compatibility styles to make sure everything still
|
|
125
|
+
looks the same as it did with Tailwind CSS v3.
|
|
126
|
+
|
|
127
|
+
If we ever want to remove these styles, we need to add an explicit border
|
|
128
|
+
color utility to any element that depends on these defaults.
|
|
129
|
+
*/
|
|
130
|
+
@layer base {
|
|
131
|
+
*,
|
|
132
|
+
::after,
|
|
133
|
+
::before,
|
|
134
|
+
::backdrop,
|
|
135
|
+
::file-selector-button {
|
|
136
|
+
border-color: var(--color-gray-200, currentColor);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
|
|
3
|
+
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
|
|
6
|
+
<stop stop-color="#41D1FF"/>
|
|
7
|
+
<stop offset="1" stop-color="#BD34FE"/>
|
|
8
|
+
</linearGradient>
|
|
9
|
+
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
|
|
10
|
+
<stop stop-color="#FFEA83"/>
|
|
11
|
+
<stop offset="0.0833333" stop-color="#FFDD35"/>
|
|
12
|
+
<stop offset="1" stop-color="#FFA800"/>
|
|
13
|
+
</linearGradient>
|
|
14
|
+
</defs>
|
|
15
|
+
</svg>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from "clsx"
|
|
2
|
+
import { twMerge } from "tailwind-merge"
|
|
3
|
+
|
|
4
|
+
export function cn(...inputs: ClassValue[]) {
|
|
5
|
+
return twMerge(clsx(inputs))
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function formatDate(input: string | number): string {
|
|
9
|
+
const date = new Date(input)
|
|
10
|
+
return date.toLocaleDateString("en-US", {
|
|
11
|
+
month: "long",
|
|
12
|
+
day: "numeric",
|
|
13
|
+
year: "numeric",
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function absoluteUrl(path: string) {
|
|
18
|
+
return `${process.env.NEXT_PUBLIC_APP_URL}${path}`
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactDOM from "react-dom/client";
|
|
3
|
+
import { BrowserRouter as Router } from "react-router-dom";
|
|
4
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
|
+
import ThemeProvider from "./utils/ThemeContext";
|
|
6
|
+
import AuthProvider from "./utils/AuthContext";
|
|
7
|
+
import App from "./App";
|
|
8
|
+
|
|
9
|
+
const queryClient = new QueryClient({
|
|
10
|
+
defaultOptions: { queries: { staleTime: 5000, retry: 1 } },
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
ReactDOM.createRoot(document.getElementById("root")).render(
|
|
14
|
+
<React.StrictMode>
|
|
15
|
+
<Router basename="/cloud">
|
|
16
|
+
<ThemeProvider>
|
|
17
|
+
<AuthProvider>
|
|
18
|
+
<QueryClientProvider client={queryClient}>
|
|
19
|
+
<App />
|
|
20
|
+
</QueryClientProvider>
|
|
21
|
+
</AuthProvider>
|
|
22
|
+
</ThemeProvider>
|
|
23
|
+
</Router>
|
|
24
|
+
</React.StrictMode>,
|
|
25
|
+
);
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import Sidebar from "../partials/Sidebar";
|
|
3
|
+
import Header from "../partials/Header";
|
|
4
|
+
import { useAudit } from "../api/queries";
|
|
5
|
+
|
|
6
|
+
function SeverityBadge({ severity }) {
|
|
7
|
+
const cls = severity === "warn"
|
|
8
|
+
? "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
|
|
9
|
+
: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400";
|
|
10
|
+
return <span className={`inline-flex px-2 py-0.5 rounded-full text-xs font-medium ${cls}`}>{severity}</span>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function FindingsTable({ findings }) {
|
|
14
|
+
if (!findings?.length) return <p className="text-sm text-green-500 py-4 text-center">No findings</p>;
|
|
15
|
+
const sorted = [...findings].sort((a, b) => (a.severity === "warn" ? -1 : 1) - (b.severity === "warn" ? -1 : 1));
|
|
16
|
+
return (
|
|
17
|
+
<table className="w-full text-sm">
|
|
18
|
+
<thead>
|
|
19
|
+
<tr className="text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase border-b border-gray-200 dark:border-gray-700">
|
|
20
|
+
<th className="px-4 py-2 w-20">Severity</th>
|
|
21
|
+
<th className="px-4 py-2 w-40">Check</th>
|
|
22
|
+
<th className="px-4 py-2">Message</th>
|
|
23
|
+
<th className="px-4 py-2">Fix</th>
|
|
24
|
+
</tr>
|
|
25
|
+
</thead>
|
|
26
|
+
<tbody>
|
|
27
|
+
{sorted.map((f, i) => (
|
|
28
|
+
<tr key={i} className="border-b border-gray-100 dark:border-gray-700/50">
|
|
29
|
+
<td className="px-4 py-2"><SeverityBadge severity={f.severity} /></td>
|
|
30
|
+
<td className="px-4 py-2"><code className="text-xs bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded">{f.check}</code></td>
|
|
31
|
+
<td className="px-4 py-2 text-gray-700 dark:text-gray-300">{f.message}</td>
|
|
32
|
+
<td className="px-4 py-2"><code className="text-xs text-gray-500 break-all">{f.fix || ""}</code></td>
|
|
33
|
+
</tr>
|
|
34
|
+
))}
|
|
35
|
+
</tbody>
|
|
36
|
+
</table>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function ResourceGroup({ title, resources, nameKey }) {
|
|
41
|
+
const [expanded, setExpanded] = useState(null);
|
|
42
|
+
if (!resources?.length) return null;
|
|
43
|
+
return (
|
|
44
|
+
<div className="mb-8">
|
|
45
|
+
<h2 className="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-3">{title}</h2>
|
|
46
|
+
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
47
|
+
<table className="w-full text-sm">
|
|
48
|
+
<thead>
|
|
49
|
+
<tr className="text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase border-b border-gray-200 dark:border-gray-700">
|
|
50
|
+
<th className="px-5 py-3">Name</th>
|
|
51
|
+
<th className="px-5 py-3">Location</th>
|
|
52
|
+
<th className="px-5 py-3 w-24">Warnings</th>
|
|
53
|
+
<th className="px-5 py-3 w-24">Info</th>
|
|
54
|
+
</tr>
|
|
55
|
+
</thead>
|
|
56
|
+
<tbody>
|
|
57
|
+
{resources.map((r, i) => {
|
|
58
|
+
const name = r[nameKey] || "?";
|
|
59
|
+
const warns = (r.findings || []).filter((f) => f.severity === "warn").length;
|
|
60
|
+
const infos = (r.findings || []).filter((f) => f.severity === "info").length;
|
|
61
|
+
return (
|
|
62
|
+
<React.Fragment key={i}>
|
|
63
|
+
<tr
|
|
64
|
+
className="border-b border-gray-100 dark:border-gray-700/50 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/30"
|
|
65
|
+
onClick={() => setExpanded(expanded === i ? null : i)}
|
|
66
|
+
>
|
|
67
|
+
<td className="px-5 py-3 font-medium text-gray-800 dark:text-gray-200">{name}</td>
|
|
68
|
+
<td className="px-5 py-3 text-gray-600 dark:text-gray-400">{r.location || "-"}</td>
|
|
69
|
+
<td className="px-5 py-3">
|
|
70
|
+
{warns > 0 ? <><SeverityBadge severity="warn" /><span className="ml-1 text-xs">{warns > 1 ? `x${warns}` : ""}</span></> : <span className="text-gray-400">0</span>}
|
|
71
|
+
</td>
|
|
72
|
+
<td className="px-5 py-3 text-gray-500">{infos}</td>
|
|
73
|
+
</tr>
|
|
74
|
+
{expanded === i && (
|
|
75
|
+
<tr>
|
|
76
|
+
<td colSpan={4} className="p-0">
|
|
77
|
+
<div className="bg-gray-50 dark:bg-gray-900/50 p-4">
|
|
78
|
+
<FindingsTable findings={r.findings} />
|
|
79
|
+
</div>
|
|
80
|
+
</td>
|
|
81
|
+
</tr>
|
|
82
|
+
)}
|
|
83
|
+
</React.Fragment>
|
|
84
|
+
);
|
|
85
|
+
})}
|
|
86
|
+
</tbody>
|
|
87
|
+
</table>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function Audit() {
|
|
94
|
+
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
95
|
+
const { data, isLoading, refetch, isFetching } = useAudit();
|
|
96
|
+
|
|
97
|
+
const vmData = data?.vms || {};
|
|
98
|
+
const aksData = data?.aks || {};
|
|
99
|
+
const storageData = data?.storage || {};
|
|
100
|
+
const allFindings = [...(vmData.findings || []), ...(aksData.findings || []), ...(storageData.findings || [])];
|
|
101
|
+
const totalWarns = allFindings.filter((f) => f.severity === "warn").length;
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div className="flex h-screen overflow-hidden">
|
|
105
|
+
<Sidebar sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
|
|
106
|
+
<div className="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden">
|
|
107
|
+
<Header sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
|
|
108
|
+
<main className="grow">
|
|
109
|
+
<div className="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
|
110
|
+
<div className="sm:flex sm:justify-between sm:items-center mb-8">
|
|
111
|
+
<div>
|
|
112
|
+
<h1 className="text-2xl md:text-3xl text-gray-800 dark:text-gray-100 font-bold">Security Audit</h1>
|
|
113
|
+
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">Azure infrastructure security posture</p>
|
|
114
|
+
</div>
|
|
115
|
+
<button
|
|
116
|
+
className="btn bg-gray-900 text-gray-100 hover:bg-gray-800 dark:bg-gray-100 dark:text-gray-800 dark:hover:bg-white disabled:opacity-50"
|
|
117
|
+
onClick={() => refetch()}
|
|
118
|
+
disabled={isFetching}
|
|
119
|
+
>
|
|
120
|
+
{isFetching ? "Scanning..." : "Run Audit"}
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div className="grid grid-cols-4 gap-4 mb-8">
|
|
125
|
+
{[
|
|
126
|
+
{ label: "Total Findings", value: allFindings.length },
|
|
127
|
+
{ label: "Warnings", value: totalWarns, color: "text-yellow-500" },
|
|
128
|
+
{ label: "Resources", value: (vmData.vms?.length || 0) + (aksData.clusters?.length || 0) + (storageData.accounts?.length || 0) },
|
|
129
|
+
{ label: "Status", value: (isLoading || isFetching) ? "Scanning..." : !data ? "Not scanned" : totalWarns === 0 ? "Clean" : `${totalWarns} issues` },
|
|
130
|
+
].map((c, i) => (
|
|
131
|
+
<div key={i} className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4">
|
|
132
|
+
<p className="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{c.label}</p>
|
|
133
|
+
<p className={`text-2xl font-semibold ${c.color || "text-gray-800 dark:text-gray-100"}`}>{c.value}</p>
|
|
134
|
+
</div>
|
|
135
|
+
))}
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
{!data && !isLoading && !isFetching ? (
|
|
139
|
+
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-12 text-center">
|
|
140
|
+
<p className="text-sm text-gray-500 mb-4">Click "Run Audit" to scan Azure infrastructure for security issues.</p>
|
|
141
|
+
<button
|
|
142
|
+
className="btn bg-gray-900 text-gray-100 hover:bg-gray-800 dark:bg-gray-100 dark:text-gray-800 dark:hover:bg-white"
|
|
143
|
+
onClick={() => refetch()}
|
|
144
|
+
>Run Audit</button>
|
|
145
|
+
</div>
|
|
146
|
+
) : isLoading || isFetching ? (
|
|
147
|
+
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-12 text-center">
|
|
148
|
+
<p className="text-sm text-gray-500">Running security audit across all Azure resources...</p>
|
|
149
|
+
</div>
|
|
150
|
+
) : (
|
|
151
|
+
<>
|
|
152
|
+
<ResourceGroup title="Virtual Machines" resources={vmData.vms} nameKey="vm" />
|
|
153
|
+
<ResourceGroup title="AKS Clusters" resources={aksData.clusters} nameKey="cluster" />
|
|
154
|
+
<ResourceGroup title="Storage Accounts" resources={storageData.accounts} nameKey="account" />
|
|
155
|
+
</>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
</main>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default Audit;
|