mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Added logic to toggle auto-connect on servers
This commit is contained in:
parent
ada5488a6c
commit
7637ce3107
6 changed files with 132 additions and 23 deletions
|
@ -57,11 +57,11 @@ export const ManageServers = (
|
|||
</div>
|
||||
</Row>
|
||||
|
||||
<SimpleCard title="Shlink servers">
|
||||
<SimpleCard>
|
||||
<table className="table table-hover mb-0">
|
||||
<thead className="responsive-table__header">
|
||||
<tr>
|
||||
{hasAutoConnect && <th />}
|
||||
{hasAutoConnect && <th style={{ width: '50px' }} />}
|
||||
<th>Name</th>
|
||||
<th>Base URL</th>
|
||||
<th />
|
||||
|
|
|
@ -7,9 +7,9 @@ import {
|
|||
faEdit as editIcon,
|
||||
faMinusCircle as deleteIcon,
|
||||
faPlug as connectIcon,
|
||||
faToggleOn as toggleOnIcon,
|
||||
faToggleOff as toggleOffIcon,
|
||||
faBan as toggleOffIcon,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { faCircle as toggleOnIcon } from '@fortawesome/free-regular-svg-icons';
|
||||
import { DropdownBtnMenu } from '../utils/DropdownBtnMenu';
|
||||
import { useToggle } from '../utils/helpers/hooks';
|
||||
import { ServerWithId } from './data';
|
||||
|
@ -20,23 +20,29 @@ export interface ManageServersRowProps {
|
|||
hasAutoConnect: boolean;
|
||||
}
|
||||
|
||||
interface ManageServersRowPropsConnectProps extends ManageServersRowProps {
|
||||
setAutoConnect: (server: ServerWithId, autoConnect: boolean) => void;
|
||||
}
|
||||
|
||||
export const ManageServersRow = (
|
||||
DeleteServerModal: FC<DeleteServerModalProps>,
|
||||
): FC<ManageServersRowProps> => ({ server, hasAutoConnect }) => {
|
||||
): FC<ManageServersRowPropsConnectProps> => ({ server, hasAutoConnect, setAutoConnect }) => {
|
||||
const [ isMenuOpen, toggleMenu ] = useToggle();
|
||||
const [ isModalOpen,, showModal, hideModal ] = useToggle();
|
||||
const serverUrl = `/server/${server.id}`;
|
||||
const { autoConnect: isAutoConnect } = server;
|
||||
const autoConnectIcon = isAutoConnect ? toggleOnIcon : toggleOffIcon;
|
||||
const autoConnectIcon = isAutoConnect ? toggleOffIcon : toggleOnIcon;
|
||||
|
||||
return (
|
||||
<tr className="responsive-table__row">
|
||||
{hasAutoConnect && (
|
||||
<td className="responsive-table__cell text-lg-right" data-th="Auto-connect">
|
||||
<td className="responsive-table__cell" data-th="Auto-connect">
|
||||
{isAutoConnect && (
|
||||
<>
|
||||
<FontAwesomeIcon icon={checkIcon} className="text-primary" id="autoConnectIcon" />
|
||||
<UncontrolledTooltip target="autoConnectIcon">Auto-connect to this server</UncontrolledTooltip>
|
||||
<UncontrolledTooltip target="autoConnectIcon" placement="right">
|
||||
Auto-connect to this server
|
||||
</UncontrolledTooltip>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
|
@ -53,7 +59,7 @@ export const ManageServersRow = (
|
|||
<DropdownItem tag={Link} to={`${serverUrl}/edit`}>
|
||||
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit server
|
||||
</DropdownItem>
|
||||
<DropdownItem>
|
||||
<DropdownItem onClick={() => setAutoConnect(server, !server.autoConnect)}>
|
||||
<FontAwesomeIcon icon={autoConnectIcon} fixedWidth /> {isAutoConnect ? 'Do not a' : 'A'}uto-connect
|
||||
</DropdownItem>
|
||||
<DropdownItem divider />
|
||||
|
|
|
@ -3,7 +3,7 @@ import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from
|
|||
import { Link } from 'react-router-dom';
|
||||
import { faPlus as plusIcon, faServer as serverIcon } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { isServerWithId, SelectedServer, ServersMap } from './data';
|
||||
import { getServerId, SelectedServer, ServersMap } from './data';
|
||||
|
||||
export interface ServersDropdownProps {
|
||||
servers: ServersMap;
|
||||
|
@ -25,12 +25,7 @@ const ServersDropdown = ({ servers, selectedServer }: ServersDropdownProps) => {
|
|||
return (
|
||||
<>
|
||||
{serversList.map(({ name, id }) => (
|
||||
<DropdownItem
|
||||
key={id}
|
||||
tag={Link}
|
||||
to={`/server/${id}`}
|
||||
active={isServerWithId(selectedServer) && selectedServer.id === id}
|
||||
>
|
||||
<DropdownItem key={id} tag={Link} to={`/server/${id}`} active={getServerId(selectedServer) === id}>
|
||||
{name}
|
||||
</DropdownItem>
|
||||
))}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { assoc, dissoc, map, pipe, reduce } from 'ramda';
|
||||
import { assoc, dissoc, fromPairs, map, pipe, reduce, toPairs } from 'ramda';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { Action } from 'redux';
|
||||
import { ServerData, ServersMap, ServerWithId } from '../data';
|
||||
|
@ -8,12 +8,22 @@ import { buildReducer } from '../../utils/helpers/redux';
|
|||
export const EDIT_SERVER = 'shlink/servers/EDIT_SERVER';
|
||||
export const DELETE_SERVER = 'shlink/servers/DELETE_SERVER';
|
||||
export const CREATE_SERVERS = 'shlink/servers/CREATE_SERVERS';
|
||||
export const SET_AUTO_CONNECT = 'shlink/servers/SET_AUTO_CONNECT';
|
||||
/* eslint-enable padding-line-between-statements */
|
||||
|
||||
export interface CreateServersAction extends Action<string> {
|
||||
newServers: ServersMap;
|
||||
}
|
||||
|
||||
interface DeleteServerAction extends Action<string> {
|
||||
serverId: string;
|
||||
}
|
||||
|
||||
interface SetAutoConnectAction extends Action<string> {
|
||||
serverId: string;
|
||||
autoConnect: boolean;
|
||||
}
|
||||
|
||||
const initialState: ServersMap = {};
|
||||
|
||||
const serverWithId = (server: ServerWithId | ServerData): ServerWithId => {
|
||||
|
@ -24,12 +34,28 @@ const serverWithId = (server: ServerWithId | ServerData): ServerWithId => {
|
|||
return assoc('id', uuid(), server);
|
||||
};
|
||||
|
||||
export default buildReducer<ServersMap, CreateServersAction>({
|
||||
export default buildReducer<ServersMap, CreateServersAction & DeleteServerAction & SetAutoConnectAction>({
|
||||
[CREATE_SERVERS]: (state, { newServers }) => ({ ...state, ...newServers }),
|
||||
[DELETE_SERVER]: (state, { serverId }: any) => dissoc(serverId, state),
|
||||
[DELETE_SERVER]: (state, { serverId }) => dissoc(serverId, state),
|
||||
[EDIT_SERVER]: (state, { serverId, serverData }: any) => !state[serverId]
|
||||
? state
|
||||
: assoc(serverId, { ...state[serverId], ...serverData }, state),
|
||||
[SET_AUTO_CONNECT]: (state, { serverId, autoConnect }) => {
|
||||
if (!state[serverId]) {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (!autoConnect) {
|
||||
return assoc(serverId, { ...state[serverId], autoConnect }, state);
|
||||
}
|
||||
|
||||
return fromPairs(
|
||||
toPairs(state).map(([ evaluatedServerId, server ]) => [
|
||||
evaluatedServerId,
|
||||
{ ...server, autoConnect: evaluatedServerId === serverId },
|
||||
]),
|
||||
);
|
||||
},
|
||||
}, initialState);
|
||||
|
||||
const serversListToMap = reduce<ServerWithId, ServersMap>((acc, server) => assoc(server.id, server, acc), {});
|
||||
|
@ -48,4 +74,10 @@ export const editServer = (serverId: string, serverData: Partial<ServerData>) =>
|
|||
serverData,
|
||||
});
|
||||
|
||||
export const deleteServer = ({ id }: ServerWithId) => ({ type: DELETE_SERVER, serverId: id });
|
||||
export const deleteServer = ({ id }: ServerWithId): DeleteServerAction => ({ type: DELETE_SERVER, serverId: id });
|
||||
|
||||
export const setAutoConnect = ({ id }: ServerWithId, autoConnect: boolean): SetAutoConnectAction => ({
|
||||
type: SET_AUTO_CONNECT,
|
||||
serverId: id,
|
||||
autoConnect,
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@ import DeleteServerButton from '../DeleteServerButton';
|
|||
import { EditServer } from '../EditServer';
|
||||
import ImportServersBtn from '../helpers/ImportServersBtn';
|
||||
import { resetSelectedServer, selectServer } from '../reducers/selectedServer';
|
||||
import { createServer, createServers, deleteServer, editServer } from '../reducers/servers';
|
||||
import { createServer, createServers, deleteServer, editServer, setAutoConnect } from '../reducers/servers';
|
||||
import { fetchServers } from '../reducers/remoteServers';
|
||||
import ForServerVersion from '../helpers/ForServerVersion';
|
||||
import { ServerError } from '../helpers/ServerError';
|
||||
|
@ -32,7 +32,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter:
|
|||
bottle.decorator('ManageServers', connect([ 'servers' ]));
|
||||
|
||||
bottle.serviceFactory('ManageServersRow', ManageServersRow, 'DeleteServerModal');
|
||||
bottle.decorator('ManageServers', connect([ 'servers' ]));
|
||||
bottle.decorator('ManageServersRow', connect(null, [ 'setAutoConnect' ]));
|
||||
|
||||
bottle.serviceFactory('CreateServer', CreateServer, 'ImportServersBtn', 'useStateFlagTimeout');
|
||||
bottle.decorator('CreateServer', withoutSelectedServer);
|
||||
|
@ -77,6 +77,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter:
|
|||
bottle.serviceFactory('createServers', () => createServers);
|
||||
bottle.serviceFactory('deleteServer', () => deleteServer);
|
||||
bottle.serviceFactory('editServer', () => editServer);
|
||||
bottle.serviceFactory('setAutoConnect', () => setAutoConnect);
|
||||
bottle.serviceFactory('fetchServers', fetchServers, 'axios');
|
||||
|
||||
bottle.serviceFactory('resetSelectedServer', () => resetSelectedServer);
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import { values } from 'ramda';
|
||||
import { dissoc, values } from 'ramda';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import reducer, {
|
||||
createServer,
|
||||
deleteServer,
|
||||
createServers,
|
||||
editServer,
|
||||
setAutoConnect,
|
||||
EDIT_SERVER,
|
||||
DELETE_SERVER,
|
||||
CREATE_SERVERS,
|
||||
SET_AUTO_CONNECT,
|
||||
} from '../../../src/servers/reducers/servers';
|
||||
import { RegularServer } from '../../../src/servers/data';
|
||||
|
||||
|
@ -29,6 +31,15 @@ describe('serverReducer', () => {
|
|||
def456: { id: 'def456' },
|
||||
}));
|
||||
|
||||
it('returns as it is when action is EDIT_SERVER and server does not exist', () =>
|
||||
expect(reducer(
|
||||
list,
|
||||
{ type: EDIT_SERVER, serverId: 'invalid', serverData: { foo: 'foo' } } as any,
|
||||
)).toEqual({
|
||||
abc123: { id: 'abc123' },
|
||||
def456: { id: 'def456' },
|
||||
}));
|
||||
|
||||
it('removes server when action is DELETE_SERVER', () =>
|
||||
expect(reducer(list, { type: DELETE_SERVER, serverId: 'abc123' } as any)).toEqual({
|
||||
def456: { id: 'def456' },
|
||||
|
@ -45,6 +56,51 @@ describe('serverReducer', () => {
|
|||
def456: { id: 'def456' },
|
||||
ghi789: { id: 'ghi789' },
|
||||
}));
|
||||
|
||||
it.each([
|
||||
[ true ],
|
||||
[ false ],
|
||||
])('returns state as it is when trying to set auto-connect on invalid server', (autoConnect) =>
|
||||
expect(reducer(list, {
|
||||
type: SET_AUTO_CONNECT,
|
||||
serverId: 'invalid',
|
||||
autoConnect,
|
||||
} as any)).toEqual({
|
||||
abc123: { id: 'abc123' },
|
||||
def456: { id: 'def456' },
|
||||
}));
|
||||
|
||||
it('disables auto-connect on a server which is already set to auto-connect', () => {
|
||||
const listWithDisabledAutoConnect = {
|
||||
...list,
|
||||
abc123: { ...list.abc123, autoConnect: true },
|
||||
};
|
||||
|
||||
expect(reducer(listWithDisabledAutoConnect, {
|
||||
type: SET_AUTO_CONNECT,
|
||||
serverId: 'abc123',
|
||||
autoConnect: false,
|
||||
} as any)).toEqual({
|
||||
abc123: { id: 'abc123', autoConnect: false },
|
||||
def456: { id: 'def456' },
|
||||
});
|
||||
});
|
||||
|
||||
it('disables auto-connect on all servers except selected one', () => {
|
||||
const listWithEnabledAutoConnect = {
|
||||
...list,
|
||||
abc123: { ...list.abc123, autoConnect: true },
|
||||
};
|
||||
|
||||
expect(reducer(listWithEnabledAutoConnect, {
|
||||
type: SET_AUTO_CONNECT,
|
||||
serverId: 'def456',
|
||||
autoConnect: true,
|
||||
} as any)).toEqual({
|
||||
abc123: { id: 'abc123', autoConnect: false },
|
||||
def456: { id: 'def456', autoConnect: true },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('action creators', () => {
|
||||
|
@ -82,6 +138,25 @@ describe('serverReducer', () => {
|
|||
|
||||
expect(result).toEqual(expect.objectContaining({ type: CREATE_SERVERS }));
|
||||
});
|
||||
|
||||
it('generates an id for every provided server if they do not have it', () => {
|
||||
const servers = values(list).map(dissoc('id'));
|
||||
const { newServers } = createServers(servers);
|
||||
|
||||
expect(values(newServers).every(({ id }) => !!id)).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setAutoConnect', () => {
|
||||
it.each([
|
||||
[ true ],
|
||||
[ false ],
|
||||
])('returns expected action', (autoConnect) => {
|
||||
const serverToEdit = Mock.of<RegularServer>({ id: 'abc123' });
|
||||
const result = setAutoConnect(serverToEdit, autoConnect);
|
||||
|
||||
expect(result).toEqual({ type: SET_AUTO_CONNECT, serverId: 'abc123', autoConnect });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue