feat: added create server

This commit is contained in:
Home
2025-10-11 01:32:59 +03:00
parent 45c21cca82
commit 894dd4da95
12 changed files with 216 additions and 26 deletions

View File

@@ -13,6 +13,7 @@
"clsx": "^2.1.1",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-hook-form": "^7.64.0",
"react-redux": "^9.2.0",
"react-router": "^7.9.3"
},
@@ -3552,6 +3553,22 @@
"react": "^19.2.0"
}
},
"node_modules/react-hook-form": {
"version": "7.64.0",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.64.0.tgz",
"integrity": "sha512-fnN+vvTiMLnRqKNTVhDysdrUay0kUUAymQnFIznmgDvapjveUWOOPqMNzPg+A+0yf9DuE2h6xzBjN1s+Qx8wcg==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/react-redux": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",

View File

@@ -18,6 +18,7 @@
"clsx": "^2.1.1",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-hook-form": "^7.64.0",
"react-redux": "^9.2.0",
"react-router": "^7.9.3"
},

View File

@@ -0,0 +1,44 @@
import { useForm, type SubmitHandler } from 'react-hook-form';
import { createServerAction } from '../../duck';
import { useAppDispatch } from '../../../../common/hooks';
import type { CreateServerForm } from './types';
export const AddServer = () => {
const dispatch = useAppDispatch();
const { register, handleSubmit, reset } = useForm<CreateServerForm>();
const onSubmit: SubmitHandler<CreateServerForm> = (values) => {
const data = {
...values,
grpc_port: parseInt(values.grpc_port)
}
dispatch(createServerAction(data)).then(() => {
reset();
});
};
return (
<div className="section">
<h2>Add Server</h2>
<form onSubmit={handleSubmit(onSubmit)} id="serverForm">
<div className="form-group">
<label>Name:</label>
<input {...register('name', { required: true })} />
</div>
<div className="form-group">
<label>Hostname:</label>
<input {...register('hostname', { required: true })} />
</div>
<div className="form-group">
<label>gRPC Port:</label>
<input {...register('grpc_port', { required: true })} />
</div>
<button type="submit" className="btn btn-primary">
Add Server
</button>
</form>
</div>
);
};

View File

@@ -0,0 +1,5 @@
export interface CreateServerForm {
name: string;
hostname: string;
grpc_port: string;
}

View File

@@ -0,0 +1 @@
export * from './servers-list'

View File

@@ -0,0 +1,50 @@
import type { FC } from 'react';
import type { ServerDTO } from '../../duck';
export interface ServersListProps {
servers: ServerDTO[];
}
export const ServersList: FC<ServersListProps> = (props) => {
const { servers } = props;
return (
<table>
<tr>
<th>Name</th>
<th>Hostname</th>
<th>Port</th>
<th>Status</th>
<th>Actions</th>
</tr>
{servers.map((s) => (
<tr>
<td>{s.name}</td>
<td>{s.hostname}</td>
<td>{s.grpc_port}</td>
<td>{s.status}</td>
<td>
<button
className="btn btn-success"
// onclick="testServer('${s.id}')"
>
Test
</button>
<button
className="btn btn-primary"
// onclick="editServer('${s.id}')"
>
Edit
</button>
<button
className="btn btn-danger"
// onclick="deleteServer('${s.id}')"
>
Delete
</button>
</td>
</tr>
))}
</table>
);
};

View File

@@ -1,8 +1,10 @@
import { serversSlice } from './slice';
import { getServers } from './api';
import { createServer, getServers } from './api';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { getServersState } from './selectors';
import type { RootState } from '../../../store';
import type { CreateServerDTO } from './dto';
import { appNotificator } from '../../../utils/notification/app-notificator';
const PREFFIX = 'servers'
@@ -28,3 +30,18 @@ export const fetchServers = createAsyncThunk(
}
},
);
export const createServerAction = createAsyncThunk(
`${PREFFIX}/createServer`,
async (params: CreateServerDTO, { dispatch }) => {
try{
await createServer(params)
dispatch(fetchServers())
} catch(e){
appNotificator.add({
message: e instanceof Error ? e.message : `Unknown error in ${PREFFIX}/createServer`,
type: 'error'
})
}
}
)

View File

@@ -1,5 +1,13 @@
import type { AxiosResponse } from 'axios';
import { api } from '../../../api/api';
import type { ServerDTO } from './dto';
import type { ServerDTO, CreateServerDTO } from './dto';
export const getServers = () => api.get<never, AxiosResponse<ServerDTO[]>>('/servers');
export const getServers = () =>
api.get<never, AxiosResponse<ServerDTO[]>>('/servers');
export const createServer = (params: CreateServerDTO ) =>
api.post<AxiosResponse>('servers', params, {
headers: {
'Content-Type': 'application/json',
},
});

View File

@@ -4,4 +4,10 @@ export interface ServerDTO {
hostname: string
grpc_port: number
status: string
}
}
export interface CreateServerDTO {
name: string;
hostname: string;
grpc_port: number;
}

View File

@@ -1,33 +1,32 @@
import { useEffect } from 'react';
import type { RouteObject } from 'react-router';
import { AddServer } from '../../features/servers/components/add-server/add-server';
import { fetchServers, getServersState } from '../../features';
import { useAppDispatch, useAppSelector } from '../../common/hooks';
import clsx from 'clsx';
import { ServersList } from '../../features/servers/components/servers-list';
export const Servers = () => {
const dispatch = useAppDispatch();
const { loading, servers } = useAppSelector(getServersState);
useEffect(() => {
dispatch(fetchServers());
}, [dispatch]);
return (
<div id="servers" className="tab-content active">
<div className="section">
<h2>Add Server</h2>
<form id="serverForm">
<div className="form-group">
<label>Name:</label>
<input type="text" id="serverName" required />
</div>
<div className="form-group">
<label>Hostname:</label>
<input type="text" id="serverHostname" required />
</div>
<div className="form-group">
<label>gRPC Port:</label>
<input type="number" id="serverPort" value="2053" />
</div>
<button type="submit" className="btn btn-primary">
Add Server
</button>
</form>
</div>
<AddServer />
<div className="section">
<h2>Servers List</h2>
<div id="serversList" className="loading">
Loading...
<div id="serversList" className={clsx({ loading: loading })}>
{loading && 'Loading...'}
{servers.length ? (
<ServersList servers={servers} />
) : (
<p>No servers found</p>
)}
</div>
</div>
</div>

View File

@@ -0,0 +1,42 @@
export type NoticeType = 'success' | 'error' | 'warn';
export interface Notice {
message: string;
type: NoticeType;
}
export interface Notificator {
list: Map<number, Notice>;
add: (notice: Notice) => number;
remove: (id: number) => void;
getAll: () => Notice[];
}
class AppNotificator implements Notificator {
public list: Map<number, Notice> = new Map();
add = (notice: Notice) => {
const id = Date.now();
this.list.set(id, notice);
this.show(notice, id);
// TODO show on UI
return id;
};
remove = (id: number) => {
this.list.delete(id);
};
getAll = () => {
return Array.from(this.list.values());
};
show = (notice: Notice, id?: number) => {
// TODO
alert(JSON.stringify(notice));
if (id) {
setTimeout(() => {
this.remove(id)
}, 300);
}
};
}
export const appNotificator = new AppNotificator();