mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-10-23 16:59:08 +00:00
feat: added create server
This commit is contained in:
17
client/package-lock.json
generated
17
client/package-lock.json
generated
@@ -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",
|
||||
|
@@ -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"
|
||||
},
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
@@ -0,0 +1,5 @@
|
||||
export interface CreateServerForm {
|
||||
name: string;
|
||||
hostname: string;
|
||||
grpc_port: string;
|
||||
}
|
@@ -0,0 +1 @@
|
||||
export * from './servers-list'
|
@@ -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>
|
||||
);
|
||||
};
|
@@ -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'
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
@@ -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',
|
||||
},
|
||||
});
|
||||
|
@@ -4,4 +4,10 @@ export interface ServerDTO {
|
||||
hostname: string
|
||||
grpc_port: number
|
||||
status: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface CreateServerDTO {
|
||||
name: string;
|
||||
hostname: string;
|
||||
grpc_port: number;
|
||||
}
|
||||
|
@@ -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>
|
||||
|
42
client/src/utils/notification/app-notificator.ts
Normal file
42
client/src/utils/notification/app-notificator.ts
Normal 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();
|
Reference in New Issue
Block a user