-
{props.label}
+
diff --git a/frontend/src/components/pages/login/index.tsx b/frontend/src/components/pages/login/index.tsx
new file mode 100644
index 0000000..e7208b6
--- /dev/null
+++ b/frontend/src/components/pages/login/index.tsx
@@ -0,0 +1,90 @@
+import { useEffect, useState } from "react";
+import { Controller, useForm } from "react-hook-form";
+import { useNavigate } from "react-router-dom";
+import { useLoginMutation } from "../../../generated/graphql";
+
+import Button from "../../atoms/Button";
+import TextField from "../../molecules/TextField";
+
+const LoginPage = () => {
+ const {
+ handleSubmit,
+ control,
+ formState: { errors },
+ } = useForm();
+ const navigator = useNavigate();
+ const loginMutation = useLoginMutation();
+ const [submitError, setSubmitError] = useState("");
+
+ const onSubmit = ({ name, password }: any) => {
+ loginMutation.mutate({ credentials: { name, password } });
+ };
+
+ useEffect(() => {
+ if (!loginMutation.data) return;
+ if (Object.hasOwn(loginMutation.data.login, "message")) {
+ setSubmitError(loginMutation.data.login.message);
+ return;
+ }
+ if (Object.hasOwn(loginMutation.data.login, "token")) {
+ localStorage.setItem("token", loginMutation.data.login.token);
+ navigator("/services");
+ }
+ }, [loginMutation.data]);
+
+ return (
+
+
+
+
+
+
+
Traefik
+
+ Confman
+
+
+
+
+
+
+
+ );
+};
+
+export default LoginPage;
diff --git a/frontend/src/components/pages/services/index.tsx b/frontend/src/components/pages/services/index.tsx
index 2ccc901..18d886f 100644
--- a/frontend/src/components/pages/services/index.tsx
+++ b/frontend/src/components/pages/services/index.tsx
@@ -1,9 +1,104 @@
+import { PlusIcon } from "@heroicons/react/outline";
+import clsx from "clsx";
+import { useState } from "react";
+import useServices from "../../../hooks/useServices";
+import Button from "../../atoms/Button";
+import InlineLink from "../../atoms/InlineLink";
+import Container from "../../layouts/Container";
import DashboardLayout from "../../layouts/Dashboard";
+import TextField from "../../molecules/TextField";
const ServicesPage = () => {
+ const [toggleCreate, setToggleCreate] = useState(false);
+ const services = useServices();
+
+ const handleToggleCreate = () => {
+ setToggleCreate(!toggleCreate);
+ };
+
return (
- Services
+
+ Services
+
+ {toggleCreate && (
+
+
Create a new service
+
+
+ )}
+
+
+
+
+ Name |
+ Destination |
+ Referenced by |
+ {/* Delete */} |
+
+
+
+ {services.data &&
+ services.data.map((service) => (
+
+ {service.name} |
+
+
+ {service.destination[0]}
+
+ |
+
+
+ {service.references}
+
+ |
+ Delete |
+
+ ))}
+
+
+
+
);
};
diff --git a/frontend/src/components/pages/setup/index.tsx b/frontend/src/components/pages/setup/index.tsx
deleted file mode 100644
index cf78a4c..0000000
--- a/frontend/src/components/pages/setup/index.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { useNavigate } from "react-router-dom";
-
-import Button from "../../atoms/Button";
-import TextField from "../../molecules/TextField";
-import TraefikImgUrl from "../../../assets/traefik.png";
-
-const SetupPage = () => {
- const navigator = useNavigate();
-
- const handleContinue = () => navigator("/");
-
- return (
-
-
-
-

-
- Setup Traefik Confman
-
-
-
-
-
-
- );
-};
-
-export default SetupPage;
diff --git a/frontend/src/hooks/useServices.tsx b/frontend/src/hooks/useServices.tsx
new file mode 100644
index 0000000..abc54c6
--- /dev/null
+++ b/frontend/src/hooks/useServices.tsx
@@ -0,0 +1,19 @@
+import { faker } from "@faker-js/faker";
+import { useQuery } from "react-query";
+import { Service } from "../types";
+
+let services = Array(11)
+ .fill(0)
+ .map(() => ({
+ name: faker.name.lastName(),
+ references: [faker.name.firstName()],
+ destination: [faker.internet.ipv4()],
+ }));
+
+const fetchServices = async (): Promise
=> {
+ return services;
+};
+
+export default function useServices() {
+ return useQuery(["services"], fetchServices);
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 9b67590..51fbe49 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,10 +1,19 @@
import React from "react";
import ReactDOM from "react-dom/client";
+import { QueryClient, QueryClientProvider } from "react-query";
+import { ReactQueryDevtools } from "react-query/devtools";
import App from "./App";
import "./index.css";
+const queryClient = new QueryClient();
+
ReactDOM.createRoot(document.getElementById("root")!).render(
-
+
+
+
+
);
+
+export { queryClient };
diff --git a/frontend/src/queries/services.graphql b/frontend/src/queries/services.graphql
new file mode 100644
index 0000000..bd05a8b
--- /dev/null
+++ b/frontend/src/queries/services.graphql
@@ -0,0 +1,13 @@
+ mutation Login($credentials: LoginInput!) {
+ login(body: $credentials) {
+ ... on AuthSuccess {
+ user {
+ name
+ }
+ token
+ }
+ ... on CommonError {
+ message
+ }
+ }
+ }
\ No newline at end of file
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
new file mode 100644
index 0000000..8839efd
--- /dev/null
+++ b/frontend/src/types.ts
@@ -0,0 +1,7 @@
+export type InputSize = "sm" | "md" | "lg";
+
+export interface Service {
+ name: string;
+ references: string[];
+ destination: string[];
+}
diff --git a/main.py b/main.py
index 12f8f90..70a2ac5 100644
--- a/main.py
+++ b/main.py
@@ -1,4 +1,5 @@
from fastapi import FastAPI
+from fastapi.middleware.cors import CORSMiddleware
from strawberry.fastapi import GraphQLRouter
from api.schema import schema
@@ -13,5 +14,17 @@ graphql_app = GraphQLRouter(
app = FastAPI()
+origins = [
+ "http://localhost:3000",
+]
+
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=origins,
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
app.include_router(graphql_app, prefix="/graphql")
app.mount("/", SPAStaticFiles(directory="frontend/dist", html=True), name="frontend")