webui/src/app/ui/formCreation.tsx (306 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
"use client";
import React from "react";
import FormDialog from "./formDialog";
import {
createCluster,
createNamespace,
createNode,
createShard,
importCluster,
migrateSlot,
} from "../lib/api";
import { useRouter } from "next/navigation";
type NamespaceFormProps = {
position: string;
};
type ClusterFormProps = {
position: string;
namespace: string;
};
type ShardFormProps = {
position: string;
namespace: string;
cluster: string;
};
type NodeFormProps = {
position: string;
namespace: string;
cluster: string;
shard: string;
};
const containsWhitespace = (value: string): boolean => /\s/.test(value);
const validateFormData = (formData: FormData, fields: string[]): string | null => {
for (const field of fields) {
const value = formData.get(field);
if (typeof value === "string" && containsWhitespace(value)) {
return `${
field.charAt(0).toUpperCase() + field.slice(1)
} cannot contain any whitespace characters.`;
}
}
return null;
};
export const NamespaceCreation: React.FC<NamespaceFormProps> = ({ position }) => {
const router = useRouter();
const handleSubmit = async (formData: FormData) => {
const fieldsToValidate = ["name"];
const errorMessage = validateFormData(formData, fieldsToValidate);
if (errorMessage) {
return errorMessage;
}
const formObj = Object.fromEntries(formData.entries());
const response = await createNamespace(formObj["name"] as string);
if (response === "") {
router.push(`/namespaces/${formObj["name"]}`);
} else {
return "Invalid form data";
}
};
return (
<FormDialog
position={position}
title="Create Namespace"
submitButtonLabel="Create"
formFields={[{ name: "name", label: "Input Name", type: "text", required: true }]}
onSubmit={handleSubmit}
/>
);
};
export const ClusterCreation: React.FC<ClusterFormProps> = ({ position, namespace }) => {
const router = useRouter();
const handleSubmit = async (formData: FormData) => {
const fieldsToValidate = ["name", "replicas"];
const errorMessage = validateFormData(formData, fieldsToValidate);
if (errorMessage) {
return errorMessage;
}
const formObj = Object.fromEntries(formData.entries());
const nodes = JSON.parse(formObj["nodes"] as string) as string[];
if (nodes.length === 0) {
return "Nodes cannot be empty.";
}
for (const node of nodes) {
if (containsWhitespace(node)) {
return "Nodes cannot contain any whitespace characters.";
}
}
const response = await createCluster(
formObj["name"] as string,
nodes,
parseInt(formObj["replicas"] as string),
formObj["password"] as string,
namespace
);
if (response === "") {
router.push(`/namespaces/${namespace}/clusters/${formObj["name"]}`);
} else {
return "Invalid form data";
}
};
return (
<FormDialog
position={position}
title="Create Cluster"
submitButtonLabel="Create"
formFields={[
{ name: "name", label: "Input Name", type: "text", required: true },
{ name: "nodes", label: "Input Nodes", type: "array", required: true },
{
name: "replicas",
label: "Input Replicas",
type: "text",
required: true,
},
{
name: "password",
label: "Input Password",
type: "password",
},
]}
onSubmit={handleSubmit}
/>
);
};
export const ShardCreation: React.FC<ShardFormProps> = ({ position, namespace, cluster }) => {
const router = useRouter();
const handleSubmit = async (formData: FormData) => {
const fieldsToValidate = ["nodes"];
const errorMessage = validateFormData(formData, fieldsToValidate);
if (errorMessage) {
return errorMessage;
}
const formObj = Object.fromEntries(formData.entries());
const nodes = JSON.parse(formObj["nodes"] as string) as string[];
const password = formObj["password"] as string;
if (nodes.length === 0) {
return "Nodes cannot be empty.";
}
for (const node of nodes) {
if (containsWhitespace(node)) {
return "Nodes cannot contain any whitespace characters.";
}
}
const response = await createShard(namespace, cluster, nodes, password);
if (response === "") {
router.push(`/namespaces/${namespace}/clusters/${cluster}`);
} else {
return "Invalid form data";
}
};
return (
<FormDialog
position={position}
title="Create Shard"
submitButtonLabel="Create"
formFields={[
{ name: "nodes", label: "Input Nodes", type: "array", required: true },
{
name: "password",
label: "Input Password",
type: "password",
},
]}
onSubmit={handleSubmit}
/>
);
};
export const ImportCluster: React.FC<ClusterFormProps> = ({ position, namespace }) => {
const router = useRouter();
const handleSubmit = async (formData: FormData) => {
const fieldsToValidate = ["nodes"];
const errorMessage = validateFormData(formData, fieldsToValidate);
if (errorMessage) {
return errorMessage;
}
const formObj = Object.fromEntries(formData.entries());
const nodes = JSON.parse(formObj["nodes"] as string) as string[];
const cluster = formObj["cluster"] as string;
const password = formObj["password"] as string;
if (nodes.length === 0) {
return "Nodes cannot be empty.";
}
for (const node of nodes) {
if (containsWhitespace(node)) {
return "Nodes cannot contain any whitespace characters.";
}
}
const response = await importCluster(namespace, cluster, nodes, password);
if (response === "") {
router.push(`/namespaces/${namespace}/clusters/${cluster}`);
} else {
return "Invalid form data";
}
};
return (
<FormDialog
position={position}
title="Import Cluster"
submitButtonLabel="Import"
formFields={[
{
name: "cluster",
label: "Input Cluster",
type: "text",
required: true,
},
{ name: "nodes", label: "Input Nodes", type: "array", required: true },
{
name: "password",
label: "Input Password",
type: "password",
},
]}
onSubmit={handleSubmit}
/>
);
};
export const MigrateSlot: React.FC<ShardFormProps> = ({ position, namespace, cluster }) => {
const router = useRouter();
const handleSubmit = async (formData: FormData) => {
const fieldsToValidate = ["target", "slot", "slot_only"];
const errorMessage = validateFormData(formData, fieldsToValidate);
if (errorMessage) {
return errorMessage;
}
const formObj = Object.fromEntries(formData.entries());
const target = parseInt(formObj["target"] as string);
const slot = parseInt(formObj["slot"] as string);
const slotOnly = formObj["slot_only"] === "true";
const response = await migrateSlot(namespace, cluster, target, slot, slotOnly);
if (response === "") {
window.location.reload();
} else {
return "Invalid form data";
}
};
return (
<FormDialog
position={position}
title="Migrate Slot"
submitButtonLabel="Migrate"
formFields={[
{ name: "target", label: "Input Target", type: "text", required: true },
{ name: "slot", label: "Input Slot", type: "text", required: true },
{
name: "slot_only",
label: "Slot Only",
type: "enum",
values: ["true", "false"],
required: true,
},
]}
onSubmit={handleSubmit}
/>
);
};
export const NodeCreation: React.FC<NodeFormProps> = ({ position, namespace, cluster, shard }) => {
const router = useRouter();
const handleSubmit = async (formData: FormData) => {
const fieldsToValidate = ["Address", "Role"];
const errorMessage = validateFormData(formData, fieldsToValidate);
if (errorMessage) {
return errorMessage;
}
const formObj = Object.fromEntries(formData.entries());
const address = formObj["Address"] as string;
const role = formObj["Role"] as string;
const password = formObj["Password"] as string;
if (containsWhitespace(address)) {
return "Address cannot contain any whitespace characters.";
}
const response = await createNode(namespace, cluster, shard, address, role, password);
if (response === "") {
window.location.reload();
} else {
return "Invalid form data";
}
};
return (
<FormDialog
position={position}
title="Create Node"
submitButtonLabel="Create"
formFields={[
{
name: "Address",
label: "Input Address",
type: "text",
required: true,
},
{
name: "Role",
label: "Select Role",
type: "enum",
required: true,
values: ["master", "slave"],
},
{
name: "Password",
label: "Input Password",
type: "password",
},
]}
onSubmit={handleSubmit}
/>
);
};