Add script review page (#304)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Closes !2 Added review dashboard button as well. <img width="1313" alt="image.png" src="attachments/a2abd430-7ff6-431a-9261-82e026de58f5">  Reviewed-on: #304 Reviewed-by: Rhys Lloyd <quaternions@noreply@itzana.me> Co-authored-by: itzaname <me@sliving.io> Co-committed-by: itzaname <me@sliving.io>
This commit was merged in pull request #304.
This commit is contained in:
@@ -17,7 +17,7 @@ type ScriptPolicy struct {
|
||||
// Hash of the source code that leads to this policy.
|
||||
// If this is a replacement mapping, the original source may not be pointed to by any policy.
|
||||
// The original source should still exist in the scripts table, which can be located by the same hash.
|
||||
FromScriptHash int64 // postgres does not support unsigned integers, so we have to pretend
|
||||
FromScriptHash int64 `gorm:"uniqueIndex"` // postgres does not support unsigned integers, so we have to pretend
|
||||
// The ID of the replacement source (ScriptPolicyReplace)
|
||||
// or verbatim source (ScriptPolicyAllowed)
|
||||
// or 0 (other)
|
||||
|
||||
@@ -26,7 +26,7 @@ func HashParse(hash string) (uint64, error){
|
||||
type Script struct {
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
Name string
|
||||
Hash int64 // postgres does not support unsigned integers, so we have to pretend
|
||||
Hash int64 `gorm:"uniqueIndex"` // postgres does not support unsigned integers, so we have to pretend
|
||||
Source string
|
||||
ResourceType ResourceType // is this a submission or is it a mapfix
|
||||
ResourceID int64 // which submission / mapfix did this script first appear in
|
||||
|
||||
@@ -36,10 +36,28 @@ func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*a
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash := int64(model.HashSource(req.Source))
|
||||
|
||||
// Check if a script with this hash already exists
|
||||
filter := service.NewScriptFilter()
|
||||
filter.SetHash(hash)
|
||||
existingScripts, err := svc.inner.ListScripts(ctx, filter, model.Page{Number: 1, Size: 1})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If script with this hash exists, return existing script ID
|
||||
if len(existingScripts) > 0 {
|
||||
return &api.ScriptID{
|
||||
ScriptID: existingScripts[0].ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Create new script
|
||||
script, err := svc.inner.CreateScript(ctx, model.Script{
|
||||
ID: 0,
|
||||
Name: req.Name,
|
||||
Hash: int64(model.HashSource(req.Source)),
|
||||
Hash: hash,
|
||||
Source: req.Source,
|
||||
ResourceType: model.ResourceType(req.ResourceType),
|
||||
ResourceID: req.ResourceID.Or(0),
|
||||
|
||||
15
web/bun.lock
15
web/bun.lock
@@ -7,6 +7,7 @@
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@mui/icons-material": "^7.3.6",
|
||||
"@mui/material": "^7.3.6",
|
||||
"@tanstack/react-query": "^5.90.12",
|
||||
@@ -185,6 +186,10 @@
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@monaco-editor/loader": ["@monaco-editor/loader@1.7.0", "", { "dependencies": { "state-local": "^1.0.6" } }, "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA=="],
|
||||
|
||||
"@monaco-editor/react": ["@monaco-editor/react@4.7.0", "", { "dependencies": { "@monaco-editor/loader": "^1.5.0" }, "peerDependencies": { "monaco-editor": ">= 0.25.0 < 1", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA=="],
|
||||
|
||||
"@mui/core-downloads-tracker": ["@mui/core-downloads-tracker@7.3.6", "", {}, "sha512-QaYtTHlr8kDFN5mE1wbvVARRKH7Fdw1ZuOjBJcFdVpfNfRYKF3QLT4rt+WaB6CKJvpqxRsmEo0kpYinhH5GeHg=="],
|
||||
|
||||
"@mui/icons-material": ["@mui/icons-material@7.3.6", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "@mui/material": "^7.3.6", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-0FfkXEj22ysIq5pa41A2NbcAhJSvmcZQ/vcTIbjDsd6hlslG82k5BEBqqS0ZJprxwIL3B45qpJ+bPHwJPlF7uQ=="],
|
||||
@@ -305,6 +310,8 @@
|
||||
|
||||
"@types/react-transition-group": ["@types/react-transition-group@4.4.12", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w=="],
|
||||
|
||||
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
|
||||
|
||||
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
|
||||
@@ -365,6 +372,8 @@
|
||||
|
||||
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
|
||||
|
||||
"dompurify": ["dompurify@3.2.7", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.266", "", {}, "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg=="],
|
||||
|
||||
"error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="],
|
||||
@@ -477,10 +486,14 @@
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
@@ -563,6 +576,8 @@
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"state-local": ["state-local@1.0.7", "", {}, "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],
|
||||
|
||||
64
web/package-lock.json
generated
64
web/package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@mui/icons-material": "^7.3.6",
|
||||
"@mui/material": "^7.3.6",
|
||||
"@tanstack/react-query": "^5.90.12",
|
||||
@@ -1148,6 +1149,29 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@monaco-editor/loader": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz",
|
||||
"integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"state-local": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@monaco-editor/react": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz",
|
||||
"integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@monaco-editor/loader": "^1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"monaco-editor": ">= 0.25.0 < 1",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/core-downloads-tracker": {
|
||||
"version": "7.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.6.tgz",
|
||||
@@ -2531,6 +2555,16 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
|
||||
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"peer": true,
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.267",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
|
||||
@@ -3202,6 +3236,19 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
|
||||
"integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
@@ -3229,6 +3276,17 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/monaco-editor": {
|
||||
"version": "0.55.1",
|
||||
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
|
||||
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"dompurify": "3.2.7",
|
||||
"marked": "14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -3742,6 +3800,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/state-local": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
|
||||
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@mui/icons-material": "^7.3.6",
|
||||
"@mui/material": "^7.3.6",
|
||||
"@tanstack/react-query": "^5.90.12",
|
||||
|
||||
@@ -16,6 +16,7 @@ import AdminSubmitPage from '@/app/admin-submit/page'
|
||||
import OperationPage from '@/app/operations/[operationId]/page'
|
||||
import ReviewerDashboardPage from '@/app/reviewer-dashboard/page'
|
||||
import UserDashboardPage from '@/app/user-dashboard/page'
|
||||
import ScriptReviewPage from '@/app/script-review/page'
|
||||
import NotFound from '@/app/not-found/page'
|
||||
|
||||
function App() {
|
||||
@@ -35,6 +36,7 @@ function App() {
|
||||
<Route path="/operations/:operationId" element={<OperationPage />} />
|
||||
<Route path="/review" element={<ReviewerDashboardPage />} />
|
||||
<Route path="/dashboard" element={<UserDashboardPage />} />
|
||||
<Route path="/script-review" element={<ScriptReviewPage />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</ThemeProvider>
|
||||
|
||||
@@ -26,6 +26,8 @@ import NavigateNextIcon from "@mui/icons-material/NavigateNext";
|
||||
import { useTitle } from "@/app/hooks/useTitle";
|
||||
import AssignmentIcon from "@mui/icons-material/Assignment";
|
||||
import BuildIcon from "@mui/icons-material/Build";
|
||||
import CodeIcon from "@mui/icons-material/Code";
|
||||
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
@@ -169,6 +171,8 @@ export default function ReviewerDashboardPage() {
|
||||
const [isLoadingMapfixes, setIsLoadingMapfixes] = useState(false);
|
||||
const [tabValue, setTabValue] = useState(0);
|
||||
const [userRoles, setUserRoles] = useState<number | null>(null);
|
||||
const [scriptPoliciesCount, setScriptPoliciesCount] = useState<number>(0);
|
||||
const [isLoadingScripts, setIsLoadingScripts] = useState(false);
|
||||
|
||||
// Fetch user roles
|
||||
useEffect(() => {
|
||||
@@ -361,6 +365,45 @@ export default function ReviewerDashboardPage() {
|
||||
return () => controller.abort();
|
||||
}, [userRoles]);
|
||||
|
||||
// Fetch script policies needing review
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
|
||||
async function fetchScriptPolicies() {
|
||||
if (!userRoles || !hasRole(userRoles, RolesConstants.ScriptWrite)) return;
|
||||
|
||||
setIsLoadingScripts(true);
|
||||
try {
|
||||
const res = await fetch(
|
||||
'/v1/script-policy?Page=1&Limit=50&Policy=0',
|
||||
{ signal: controller.signal }
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
console.error('Failed to fetch script policies:', res.status);
|
||||
setIsLoadingScripts(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const policies = await res.json();
|
||||
// The API does not provide total count, so we use the length of returned policies
|
||||
setScriptPoliciesCount(Array.isArray(policies) ? policies.length : 0);
|
||||
} catch (error) {
|
||||
if (!(error instanceof DOMException && error.name === 'AbortError')) {
|
||||
console.error("Error fetching script policies:", error);
|
||||
}
|
||||
} finally {
|
||||
setIsLoadingScripts(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (userRoles) {
|
||||
fetchScriptPolicies();
|
||||
}
|
||||
|
||||
return () => controller.abort();
|
||||
}, [userRoles]);
|
||||
|
||||
const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => {
|
||||
setTabValue(newValue);
|
||||
};
|
||||
@@ -414,6 +457,7 @@ export default function ReviewerDashboardPage() {
|
||||
hasRole(userRoles, RolesConstants.MapfixUpload) ||
|
||||
hasRole(userRoles, RolesConstants.ScriptWrite)
|
||||
);
|
||||
const canReviewScripts = hasRole(userRoles, RolesConstants.ScriptWrite);
|
||||
|
||||
if (!hasAnyReviewerRole(userRoles)) {
|
||||
return (
|
||||
@@ -470,7 +514,7 @@ export default function ReviewerDashboardPage() {
|
||||
{/* Summary Cards */}
|
||||
<Box sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },
|
||||
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr', md: canReviewScripts ? '1fr 1fr 1fr' : '1fr 1fr' },
|
||||
gap: 3,
|
||||
mb: 4
|
||||
}}>
|
||||
@@ -521,6 +565,43 @@ export default function ReviewerDashboardPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{canReviewScripts && (
|
||||
<Card
|
||||
component={Link}
|
||||
to="/script-review"
|
||||
sx={{
|
||||
textDecoration: 'none',
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.2s, box-shadow 0.2s',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-2px)',
|
||||
boxShadow: 4
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<CodeIcon sx={{ fontSize: 40, color: 'success.main' }} />
|
||||
<Box>
|
||||
<Typography variant="h4" fontWeight="bold">
|
||||
{isLoadingScripts ? (
|
||||
<Skeleton width={50} />
|
||||
) : (
|
||||
scriptPoliciesCount >= 50 ? '50+' : scriptPoliciesCount
|
||||
)}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Scripts Pending Review
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<ArrowForwardIcon sx={{ fontSize: 24, color: 'text.secondary' }} />
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Tabs */}
|
||||
|
||||
1165
web/src/app/script-review/page.tsx
Normal file
1165
web/src/app/script-review/page.tsx
Normal file
File diff suppressed because it is too large
Load Diff
23
web/src/app/ts/Script.ts
Normal file
23
web/src/app/ts/Script.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export interface Script {
|
||||
ID: number;
|
||||
Name: string;
|
||||
Hash: string;
|
||||
Source: string;
|
||||
ResourceType: number;
|
||||
ResourceID: number;
|
||||
}
|
||||
|
||||
export interface ScriptCreate {
|
||||
Name: string;
|
||||
Source: string;
|
||||
ResourceType: number;
|
||||
ResourceID?: number;
|
||||
}
|
||||
|
||||
export interface ScriptUpdate {
|
||||
ID: number;
|
||||
Name?: string;
|
||||
Source?: string;
|
||||
ResourceType?: number;
|
||||
ResourceID?: number;
|
||||
}
|
||||
35
web/src/app/ts/ScriptPolicy.ts
Normal file
35
web/src/app/ts/ScriptPolicy.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export interface ScriptPolicy {
|
||||
ID: number;
|
||||
FromScriptHash: string;
|
||||
ToScriptID: number;
|
||||
Policy: PolicyType;
|
||||
}
|
||||
|
||||
export interface ScriptPolicyCreate {
|
||||
FromScriptID: number;
|
||||
ToScriptID: number;
|
||||
Policy: PolicyType;
|
||||
}
|
||||
|
||||
export interface ScriptPolicyUpdate {
|
||||
ID: number;
|
||||
FromScriptID?: number;
|
||||
ToScriptID?: number;
|
||||
Policy?: PolicyType;
|
||||
}
|
||||
|
||||
export enum PolicyType {
|
||||
None = 0, // not yet reviewed
|
||||
Allowed = 1,
|
||||
Blocked = 2,
|
||||
Delete = 3,
|
||||
Replace = 4,
|
||||
}
|
||||
|
||||
export const PolicyLabels: Record<PolicyType, string> = {
|
||||
[PolicyType.None]: "None (Unreviewed)",
|
||||
[PolicyType.Allowed]: "Allowed",
|
||||
[PolicyType.Blocked]: "Blocked",
|
||||
[PolicyType.Delete]: "Delete",
|
||||
[PolicyType.Replace]: "Replace",
|
||||
};
|
||||
Reference in New Issue
Block a user