diff --git a/.gitignore b/.gitignore
index e1d1e52..9744485 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-/.vscode
+*.vscode
/dist
/hetty
/cmd/hetty/admin
diff --git a/admin/.eslintrc.json b/admin/.eslintrc.json
index e2acf76..a901afe 100644
--- a/admin/.eslintrc.json
+++ b/admin/.eslintrc.json
@@ -1,18 +1,51 @@
{
- "extends": ["next/core-web-vitals", "prettier"],
+ "root": true,
+ "extends": ["next/core-web-vitals", "prettier", "plugin:@typescript-eslint/recommended", "plugin:import/typescript"],
+ "plugins": ["prettier", "@typescript-eslint", "import"],
+ "ignorePatterns": ["next*", "src/lib/graphql/generated.tsx"],
+ "settings": {
+ "import/parsers": {
+ "@typescript-eslint/parser": [".ts", ".tsx"]
+ },
+ "import/resolver": {
+ "typescript": {
+ "alwaysTryTypes": true
+ }
+ }
+ },
"rules": {
+ "prettier/prettier": ["error"],
"@next/next/no-css-tags": "off",
- "@typescript-eslint/no-unused-vars": "off",
- "unused-imports/no-unused-imports": "error",
- "unused-imports/no-unused-vars": [
+ "no-unused-vars": "off",
+ "@typescript-eslint/no-unused-vars": "error",
+
+ "import/default": "off",
+
+ "import/no-unresolved": "error",
+ "import/named": "error",
+ "import/namespace": "error",
+ "import/export": "error",
+ "import/no-deprecated": "error",
+ "import/no-cycle": "error",
+
+ "import/no-named-as-default": "warn",
+ "import/no-named-as-default-member": "warn",
+ "import/no-duplicates": "warn",
+ "import/newline-after-import": "warn",
+ "import/order": [
"warn",
{
- "vars": "all",
- "varsIgnorePattern": "^_",
- "args": "after-used",
- "argsIgnorePattern": "^_"
+ "alphabetize": { "order": "asc", "caseInsensitive": false },
+ "newlines-between": "always",
+ "groups": ["builtin", "external", "parent", "sibling", "index"]
+ }
+ ],
+ "import/no-unused-modules": [
+ "error",
+ {
+ "missingExports": true,
+ "ignoreExports": ["./src/pages"]
}
]
- },
- "plugins": ["unused-imports", "prettier"]
+ }
}
diff --git a/admin/gqlcodegen.yml b/admin/gqlcodegen.yml
index dbba8a0..5a9f02d 100644
--- a/admin/gqlcodegen.yml
+++ b/admin/gqlcodegen.yml
@@ -2,7 +2,7 @@ overwrite: true
schema: "../pkg/api/schema.graphql"
documents: "src/**/*.graphql"
generates:
- src/generated/graphql.tsx:
+ src/lib/graphql/generated.tsx:
plugins:
- "typescript"
- "typescript-operations"
diff --git a/admin/package.json b/admin/package.json
index 4c95497..59c313d 100644
--- a/admin/package.json
+++ b/admin/package.json
@@ -44,8 +44,9 @@
"eslint": "^8.7.0",
"eslint-config-next": "12.0.8",
"eslint-config-prettier": "^8.3.0",
+ "eslint-import-resolver-typescript": "^2.5.0",
+ "eslint-plugin-import": "^2.25.4",
"eslint-plugin-prettier": "^4.0.0",
- "eslint-plugin-unused-imports": "^2.0.0",
"prettier": "^2.1.2",
"typescript": "^4.0.3",
"webpack": "^5.67.0"
diff --git a/admin/public/android-chrome-192x192.png b/admin/public/android-chrome-192x192.png
new file mode 100644
index 0000000..5bc3e6d
Binary files /dev/null and b/admin/public/android-chrome-192x192.png differ
diff --git a/admin/public/android-chrome-512x512.png b/admin/public/android-chrome-512x512.png
new file mode 100644
index 0000000..9ecc403
Binary files /dev/null and b/admin/public/android-chrome-512x512.png differ
diff --git a/admin/public/apple-touch-icon.png b/admin/public/apple-touch-icon.png
new file mode 100644
index 0000000..ee0d915
Binary files /dev/null and b/admin/public/apple-touch-icon.png differ
diff --git a/admin/public/favicon-16x16.png b/admin/public/favicon-16x16.png
new file mode 100644
index 0000000..61b96e5
Binary files /dev/null and b/admin/public/favicon-16x16.png differ
diff --git a/admin/public/favicon-32x32.png b/admin/public/favicon-32x32.png
new file mode 100644
index 0000000..54d2538
Binary files /dev/null and b/admin/public/favicon-32x32.png differ
diff --git a/admin/public/favicon.ico b/admin/public/favicon.ico
index 4965832..51fec0c 100644
Binary files a/admin/public/favicon.ico and b/admin/public/favicon.ico differ
diff --git a/admin/public/site.webmanifest b/admin/public/site.webmanifest
new file mode 100644
index 0000000..45dc8a2
--- /dev/null
+++ b/admin/public/site.webmanifest
@@ -0,0 +1 @@
+{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
\ No newline at end of file
diff --git a/admin/src/components/projects/NewProject.tsx b/admin/src/components/projects/NewProject.tsx
deleted file mode 100644
index f61c832..0000000
--- a/admin/src/components/projects/NewProject.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { gql, useMutation } from "@apollo/client";
-import { Box, Button, CircularProgress, TextField, Typography } from "@mui/material";
-import AddIcon from "@mui/icons-material/Add";
-import React, { useState } from "react";
-
-const CREATE_PROJECT = gql`
- mutation CreateProject($name: String!) {
- createProject(name: $name) {
- id
- name
- }
- }
-`;
-
-const OPEN_PROJECT = gql`
- mutation OpenProject($id: ID!) {
- openProject(id: $id) {
- id
- name
- isActive
- }
- }
-`;
-
-function NewProject(): JSX.Element {
- const [name, setName] = useState("");
-
- const [createProject, { error: createProjErr, loading: createProjLoading }] = useMutation(CREATE_PROJECT, {
- onError: () => {},
- onCompleted(data) {
- setName("");
- openProject({ variables: { id: data.createProject.id } });
- },
- });
- const [openProject, { error: openProjErr, loading: openProjLoading }] = useMutation(OPEN_PROJECT, {
- onError: () => {},
- update(cache, { data: { openProject } }) {
- cache.modify({
- fields: {
- activeProject() {
- const activeProjRef = cache.writeFragment({
- id: openProject.id,
- data: openProject,
- fragment: gql`
- fragment ActiveProject on Project {
- id
- name
- isActive
- type
- }
- `,
- });
- return activeProjRef;
- },
- projects(_, { DELETE }) {
- cache.writeFragment({
- id: openProject.id,
- data: openProject,
- fragment: gql`
- fragment OpenProject on Project {
- id
- name
- isActive
- type
- }
- `,
- });
- return DELETE;
- },
- },
- });
- },
- });
-
- const handleCreateAndOpenProjectForm = (e: React.SyntheticEvent) => {
- e.preventDefault();
- createProject({ variables: { name } });
- };
-
- return (
-
- );
-}
-
-export default NewProject;
diff --git a/admin/src/components/reqlog/hooks/useClearHTTPRequestLog.ts b/admin/src/components/reqlog/hooks/useClearHTTPRequestLog.ts
deleted file mode 100644
index 2870712..0000000
--- a/admin/src/components/reqlog/hooks/useClearHTTPRequestLog.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { gql, useMutation } from "@apollo/client";
-import { HTTP_REQUEST_LOGS } from "./useHttpRequestLogs";
-
-const CLEAR_HTTP_REQUEST_LOG = gql`
- mutation ClearHTTPRequestLog {
- clearHTTPRequestLog {
- success
- }
- }
-`;
-
-export function useClearHTTPRequestLog() {
- return useMutation(CLEAR_HTTP_REQUEST_LOG, {
- refetchQueries: [{ query: HTTP_REQUEST_LOGS }],
- });
-}
diff --git a/admin/src/components/reqlog/hooks/useCreateSenderRequest.ts b/admin/src/components/reqlog/hooks/useCreateSenderRequest.ts
deleted file mode 100644
index aa81efe..0000000
--- a/admin/src/components/reqlog/hooks/useCreateSenderRequest.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { gql, useMutation } from "@apollo/client";
-
-const CREATE_SENDER_REQUEST = gql`
- mutation CreateSenderRequest($request: SenderRequestInput!) {
- createSenderRequest(request: $request) {
- id
- }
- }
-`;
-
-export default function useCreateSenderRequest() {
- return useMutation(CREATE_SENDER_REQUEST);
-}
diff --git a/admin/src/components/reqlog/hooks/useHttpRequestLogs.ts b/admin/src/components/reqlog/hooks/useHttpRequestLogs.ts
deleted file mode 100644
index f426702..0000000
--- a/admin/src/components/reqlog/hooks/useHttpRequestLogs.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { gql, useQuery } from "@apollo/client";
-
-export const HTTP_REQUEST_LOGS = gql`
- query HttpRequestLogs {
- httpRequestLogs {
- id
- method
- url
- timestamp
- response {
- statusCode
- statusReason
- }
- }
- }
-`;
-
-export function useHttpRequestLogs() {
- return useQuery(HTTP_REQUEST_LOGS, {
- pollInterval: 1000,
- });
-}
diff --git a/admin/src/components/Layout.tsx b/admin/src/features/Layout.tsx
similarity index 98%
rename from admin/src/components/Layout.tsx
rename to admin/src/features/Layout.tsx
index 0c6302c..5368793 100644
--- a/admin/src/components/Layout.tsx
+++ b/admin/src/features/Layout.tsx
@@ -1,4 +1,11 @@
-import React from "react";
+import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
+import ChevronRightIcon from "@mui/icons-material/ChevronRight";
+import FolderIcon from "@mui/icons-material/Folder";
+import HomeIcon from "@mui/icons-material/Home";
+import LocationSearchingIcon from "@mui/icons-material/LocationSearching";
+import MenuIcon from "@mui/icons-material/Menu";
+import SendIcon from "@mui/icons-material/Send";
+import SettingsEthernetIcon from "@mui/icons-material/SettingsEthernet";
import {
Theme,
useTheme,
@@ -18,14 +25,7 @@ import MuiDrawer from "@mui/material/Drawer";
import MuiListItemButton, { ListItemButtonProps } from "@mui/material/ListItemButton";
import MuiListItemIcon, { ListItemIconProps } from "@mui/material/ListItemIcon";
import Link from "next/link";
-import MenuIcon from "@mui/icons-material/Menu";
-import HomeIcon from "@mui/icons-material/Home";
-import SettingsEthernetIcon from "@mui/icons-material/SettingsEthernet";
-import SendIcon from "@mui/icons-material/Send";
-import FolderIcon from "@mui/icons-material/Folder";
-import LocationSearchingIcon from "@mui/icons-material/LocationSearching";
-import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
-import ChevronRightIcon from "@mui/icons-material/ChevronRight";
+import React, { useState } from "react";
export enum Page {
Home,
@@ -133,7 +133,7 @@ interface Props {
export function Layout({ title, page, children }: Props): JSX.Element {
const theme = useTheme();
- const [open, setOpen] = React.useState(false);
+ const [open, setOpen] = useState(false);
const handleDrawerOpen = () => {
setOpen(true);
@@ -247,5 +247,3 @@ export function Layout({ title, page, children }: Props): JSX.Element {
);
}
-
-export default Layout;
diff --git a/admin/src/features/projects/components/NewProject.tsx b/admin/src/features/projects/components/NewProject.tsx
new file mode 100644
index 0000000..550b04e
--- /dev/null
+++ b/admin/src/features/projects/components/NewProject.tsx
@@ -0,0 +1,67 @@
+import AddIcon from "@mui/icons-material/Add";
+import { Box, Button, CircularProgress, TextField, Typography } from "@mui/material";
+import React, { useState } from "react";
+
+import useOpenProjectMutation from "../hooks/useOpenProjectMutation";
+
+import { useCreateProjectMutation } from "lib/graphql/generated";
+
+function NewProject(): JSX.Element {
+ const [name, setName] = useState("");
+
+ const [createProject, createProjResult] = useCreateProjectMutation({
+ onCompleted(data) {
+ setName("");
+ if (data?.createProject) {
+ openProject({ variables: { id: data.createProject?.id } });
+ }
+ },
+ });
+ const [openProject, openProjResult] = useOpenProjectMutation();
+
+ const handleCreateAndOpenProjectForm = (e: React.SyntheticEvent) => {
+ e.preventDefault();
+ createProject({ variables: { name } });
+ };
+
+ return (
+
+ );
+}
+
+export default NewProject;
diff --git a/admin/src/components/projects/ProjectList.tsx b/admin/src/features/projects/components/ProjectList.tsx
similarity index 63%
rename from admin/src/components/projects/ProjectList.tsx
rename to admin/src/features/projects/components/ProjectList.tsx
index e3a6987..3ad2dd0 100644
--- a/admin/src/components/projects/ProjectList.tsx
+++ b/admin/src/features/projects/components/ProjectList.tsx
@@ -1,4 +1,8 @@
-import { gql, useMutation, useQuery } from "@apollo/client";
+import CloseIcon from "@mui/icons-material/Close";
+import DeleteIcon from "@mui/icons-material/Delete";
+import DescriptionIcon from "@mui/icons-material/Description";
+import LaunchIcon from "@mui/icons-material/Launch";
+import { Alert } from "@mui/lab";
import {
Avatar,
Box,
@@ -21,110 +25,25 @@ import {
Typography,
useTheme,
} from "@mui/material";
-import CloseIcon from "@mui/icons-material/Close";
-import DescriptionIcon from "@mui/icons-material/Description";
-import DeleteIcon from "@mui/icons-material/Delete";
-import LaunchIcon from "@mui/icons-material/Launch";
-import { Alert } from "@mui/lab";
import React, { useState } from "react";
-import { Project } from "../../lib/Project";
+import useOpenProjectMutation from "../hooks/useOpenProjectMutation";
-const PROJECTS = gql`
- query Projects {
- projects {
- id
- name
- isActive
- }
- }
-`;
-
-const OPEN_PROJECT = gql`
- mutation OpenProject($id: ID!) {
- openProject(id: $id) {
- id
- name
- isActive
- }
- }
-`;
-
-const CLOSE_PROJECT = gql`
- mutation CloseProject {
- closeProject {
- success
- }
- }
-`;
-
-const DELETE_PROJECT = gql`
- mutation DeleteProject($id: ID!) {
- deleteProject(id: $id) {
- success
- }
- }
-`;
+import {
+ ProjectsQuery,
+ useCloseProjectMutation,
+ useDeleteProjectMutation,
+ useProjectsQuery,
+} from "lib/graphql/generated";
function ProjectList(): JSX.Element {
const theme = useTheme();
- const {
- loading: projLoading,
- error: projErr,
- data: projData,
- } = useQuery<{ projects: Project[] }>(PROJECTS, {
- fetchPolicy: "network-only",
- });
- const [openProject, { error: openProjErr, loading: openProjLoading }] = useMutation<{ openProject: Project }>(
- OPEN_PROJECT,
- {
- errorPolicy: "all",
- onError: () => {},
- update(cache, { data }) {
- cache.modify({
- fields: {
- activeProject() {
- const activeProjRef = cache.writeFragment({
- data: data?.openProject,
- fragment: gql`
- fragment ActiveProject on Project {
- id
- name
- isActive
- type
- }
- `,
- });
- return activeProjRef;
- },
- projects(_, { DELETE }) {
- cache.writeFragment({
- id: data?.openProject.id,
- data: openProject,
- fragment: gql`
- fragment OpenProject on Project {
- id
- name
- isActive
- type
- }
- `,
- });
- return DELETE;
- },
- httpRequestLogFilter(_, { DELETE }) {
- return DELETE;
- },
- },
- });
- },
- }
- );
- const [closeProject, { error: closeProjErr, client }] = useMutation(CLOSE_PROJECT, {
+ const projResult = useProjectsQuery({ fetchPolicy: "network-only" });
+ const [openProject, openProjResult] = useOpenProjectMutation();
+ const [closeProject, closeProjResult] = useCloseProjectMutation({
errorPolicy: "all",
- onError: () => {},
onCompleted() {
- client.resetStore();
+ closeProjResult.client.resetStore();
},
update(cache) {
cache.modify({
@@ -142,9 +61,8 @@ function ProjectList(): JSX.Element {
});
},
});
- const [deleteProject, { loading: deleteProjLoading, error: deleteProjErr }] = useMutation(DELETE_PROJECT, {
+ const [deleteProject, deleteProjResult] = useDeleteProjectMutation({
errorPolicy: "all",
- onError: () => {},
update(cache) {
cache.modify({
fields: {
@@ -158,14 +76,16 @@ function ProjectList(): JSX.Element {
},
});
- const [deleteProj, setDeleteProj] = useState();
+ const [deleteProj, setDeleteProj] = useState();
const [deleteDiagOpen, setDeleteDiagOpen] = useState(false);
- const handleDeleteButtonClick = (project: any) => {
+ const handleDeleteButtonClick = (project: ProjectsQuery["projects"][number]) => {
setDeleteProj(project);
setDeleteDiagOpen(true);
};
const handleDeleteConfirm = () => {
- deleteProject({ variables: { id: deleteProj?.id } });
+ if (deleteProj) {
+ deleteProject({ variables: { id: deleteProj.id } });
+ }
};
const handleDeleteCancel = () => {
setDeleteDiagOpen(false);
@@ -189,7 +109,9 @@ function ProjectList(): JSX.Element {
Deleting a project permanently removes all its data from the database. This action is irreversible.
- {deleteProjErr && Error closing project: {deleteProjErr.message}}
+ {deleteProjResult.error && (
+ Error closing project: {deleteProjResult.error.message}
+ )}