mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-10-24 17:29: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",
|
"clsx": "^2.1.1",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
"react-hook-form": "^7.64.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router": "^7.9.3"
|
"react-router": "^7.9.3"
|
||||||
},
|
},
|
||||||
@@ -3552,6 +3553,22 @@
|
|||||||
"react": "^19.2.0"
|
"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": {
|
"node_modules/react-redux": {
|
||||||
"version": "9.2.0",
|
"version": "9.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
"react-hook-form": "^7.64.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router": "^7.9.3"
|
"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 { serversSlice } from './slice';
|
||||||
import { getServers } from './api';
|
import { createServer, getServers } from './api';
|
||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
import { getServersState } from './selectors';
|
import { getServersState } from './selectors';
|
||||||
import type { RootState } from '../../../store';
|
import type { RootState } from '../../../store';
|
||||||
|
import type { CreateServerDTO } from './dto';
|
||||||
|
import { appNotificator } from '../../../utils/notification/app-notificator';
|
||||||
|
|
||||||
const PREFFIX = 'servers'
|
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 type { AxiosResponse } from 'axios';
|
||||||
import { api } from '../../../api/api';
|
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',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -5,3 +5,9 @@ export interface ServerDTO {
|
|||||||
grpc_port: number
|
grpc_port: number
|
||||||
status: string
|
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 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 = () => {
|
export const Servers = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { loading, servers } = useAppSelector(getServersState);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchServers());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="servers" className="tab-content active">
|
<div id="servers" className="tab-content active">
|
||||||
<div className="section">
|
<AddServer />
|
||||||
<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>
|
|
||||||
|
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<h2>Servers List</h2>
|
<h2>Servers List</h2>
|
||||||
<div id="serversList" className="loading">
|
<div id="serversList" className={clsx({ loading: loading })}>
|
||||||
Loading...
|
{loading && 'Loading...'}
|
||||||
|
{servers.length ? (
|
||||||
|
<ServersList servers={servers} />
|
||||||
|
) : (
|
||||||
|
<p>No servers found</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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