Skip to content

Commit 8eb4384

Browse files
committed
Gets the default proxy with the backend
By default the app uses the default proxy provided by the web backend. However users can freely change it. The proxy is saved on the session store to avoid fetching the proxy each time.
1 parent 25beab3 commit 8eb4384

File tree

10 files changed

+210
-13
lines changed

10 files changed

+210
-13
lines changed

docs/frontend_doc.md

+18
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,24 @@ Return:
332332
`123.456.9%3A9000`. The native javascript functions `decodeURIComponent` and
333333
`encodeURIComponent` can be used.
334334

335+
## Get the default proxy address
336+
337+
| | |
338+
|--------|-----------------|
339+
| URL | `/config/proxy` |
340+
| Method | `GET` |
341+
| Input | |
342+
343+
344+
Return:
345+
346+
`200 OK` `text/plain`
347+
348+
```
349+
http://example.com/
350+
```
351+
352+
335353
# Production settings
336354

337355
The two followings things that will be shown here is how to have https on the different webpages and how to make the app run on a on server.

web/frontend/src/components/utils/Endpoints.tsx

+8-3
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,14 @@ export const getProxyAddress = (NodeAddr: string) => `/api/proxies/${encodeURICo
2626
export const getProxiesAddresses = '/api/proxies';
2727

2828
// public information can be directly fetched from dela nodes
29-
export const election = (ElectionID: string) =>
30-
`${process.env.REACT_APP_PROXY}/evoting/elections/${ElectionID}`;
31-
export const elections = `${process.env.REACT_APP_PROXY}/evoting/elections`;
29+
export const election = (proxy: string, ElectionID: string) =>
30+
new URL(`/evoting/elections/${ElectionID}`, proxy).href;
31+
export const elections = (proxy: string) => {
32+
return new URL('/evoting/elections', proxy).href;
33+
};
34+
35+
// get the default proxy address
36+
export const getProxyConfig = '/api/config/proxy';
3237

3338
// To remove
3439
export const ENDPOINT_EVOTING_RESULT = '/api/evoting/result';
+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { CheckIcon, PencilIcon, RefreshIcon } from '@heroicons/react/outline';
2+
import { FlashContext, FlashLevel, ProxyContext } from 'index';
3+
import { ChangeEvent, FC, createRef, useContext, useEffect, useState } from 'react';
4+
5+
const proxyKey = 'proxy';
6+
7+
const ProxyInput: FC = () => {
8+
const fctx = useContext(FlashContext);
9+
const pctx = useContext(ProxyContext);
10+
11+
const [proxy, setProxy] = useState<string>(pctx.getProxy());
12+
const [inputVal, setInputVal] = useState('');
13+
const [inputChanging, setInputChanging] = useState(false);
14+
const [inputWidth, setInputWidth] = useState(0);
15+
16+
const proxyTextRef = createRef<HTMLDivElement>();
17+
18+
const fetchFromBackend = async () => {
19+
try {
20+
const response = await fetch('/api/config/proxy');
21+
if (!response.ok) {
22+
const js = await response.json();
23+
throw new Error(JSON.stringify(js));
24+
} else {
25+
setProxy(await response.text());
26+
}
27+
} catch (e) {
28+
fctx.addMessage(`Failed to get proxy: ${proxy}`, FlashLevel.Error);
29+
}
30+
};
31+
32+
// update the proxy context and sessionStore each time the proxy changes
33+
useEffect(() => {
34+
sessionStorage.setItem(proxyKey, proxy);
35+
pctx.setProxy(proxy);
36+
setInputVal(proxy);
37+
}, [proxy]);
38+
39+
// function called by the "refresh" button
40+
const getDefault = () => {
41+
fetchFromBackend();
42+
fctx.addMessage('Proxy updated to default', FlashLevel.Info);
43+
};
44+
45+
const updateProxy = () => {
46+
try {
47+
new URL(inputVal);
48+
} catch {
49+
fctx.addMessage('invalid URL', FlashLevel.Error);
50+
return;
51+
}
52+
53+
setInputChanging(false);
54+
setProxy(inputVal);
55+
};
56+
57+
const editProxy = () => {
58+
setInputWidth(proxyTextRef.current.clientWidth);
59+
setInputChanging(true);
60+
};
61+
62+
return (
63+
<div className="flex flex-row items-center">
64+
{inputChanging ? (
65+
<>
66+
<input
67+
value={inputVal}
68+
onChange={(e: ChangeEvent<HTMLInputElement>) => setInputVal(e.target.value)}
69+
className="mt-1 ml-3 border rounded-md p-2"
70+
style={{ width: `${inputWidth + 3}px` }}
71+
/>
72+
<div className="ml-1">
73+
<button className={`border p-1 rounded-md }`} onClick={updateProxy}>
74+
<CheckIcon className="h-5 w-5" aria-hidden="true" />
75+
</button>
76+
</div>
77+
</>
78+
) : (
79+
<>
80+
<div
81+
ref={proxyTextRef}
82+
className="mt-1 ml-3 border border-transparent p-2"
83+
onClick={editProxy}>
84+
{inputVal}
85+
</div>
86+
<div className="">
87+
<button className="hover:text-indigo-500 p-1 rounded-md" onClick={editProxy}>
88+
<PencilIcon className="m-1 h-3 w-3" aria-hidden="true" />
89+
</button>
90+
</div>
91+
</>
92+
)}
93+
<button
94+
onClick={getDefault}
95+
className="flex flex-row items-center hover:text-indigo-500 p-1 rounded-md">
96+
get default
97+
<RefreshIcon className="m-1 h-3 w-3" aria-hidden="true" />
98+
</button>
99+
</div>
100+
);
101+
};
102+
103+
export default ProxyInput;

web/frontend/src/components/utils/useElection.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@ import useFetchCall from './useFetchCall';
22
import * as endpoints from './Endpoints';
33
import { useFillElectionInfo } from './FillElectionInfo';
44
import { ID } from 'types/configuration';
5+
import { useContext } from 'react';
6+
import { ProxyContext } from 'index';
57

68
// Custom hook that fetches an election given its id and returns its
79
// different parameters
810
const useElection = (electionID: ID) => {
11+
const pctx = useContext(ProxyContext);
12+
913
const request = {
1014
method: 'GET',
1115
};
12-
const [data, loading, error] = useFetchCall(endpoints.election(electionID), request);
16+
const [data, loading, error] = useFetchCall(
17+
endpoints.election(pctx.getProxy(), electionID),
18+
request
19+
);
1320
const {
1421
status,
1522
setStatus,

web/frontend/src/index.tsx

+44-1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,28 @@ class FlashMessage {
7171
// the flash context handles flash messages across the app
7272
export const FlashContext = createContext<FlashState>(undefined);
7373

74+
// the proxy state provides the proxy address across all the app
75+
export interface ProxyState {
76+
getProxy(): string;
77+
setProxy(p: string);
78+
}
79+
80+
export class ProxyHolder implements ProxyState {
81+
proxy: string;
82+
83+
getProxy(): string {
84+
return this.proxy;
85+
}
86+
87+
setProxy(p: string) {
88+
this.proxy = p;
89+
}
90+
}
91+
92+
const defaultProxyState = new ProxyHolder();
93+
94+
export const ProxyContext = createContext<ProxyState>(defaultProxyState);
95+
7496
// A small elements to display that the page is loading, should be something
7597
// more elegant in the future and be its own component.
7698
const Loading: FC = () => (
@@ -159,6 +181,22 @@ const AppContainer = () => {
159181
},
160182
};
161183

184+
const setDefaultProxy = async () => {
185+
let proxy = sessionStorage.getItem('proxy');
186+
187+
if (proxy === null) {
188+
const response = await fetch('/api/config/proxy');
189+
if (!response.ok) {
190+
const js = await response.json();
191+
throw new Error(`Failed to get the default proxy: ${JSON.stringify(js)}`);
192+
}
193+
194+
proxy = await response.text();
195+
}
196+
197+
defaultProxyState.setProxy(proxy);
198+
};
199+
162200
useEffect(() => {
163201
const req = {
164202
method: 'GET',
@@ -182,6 +220,9 @@ const AppContainer = () => {
182220
role: result.role,
183221
});
184222

223+
// wait for the default proxy to be set
224+
await setDefaultProxy();
225+
185226
setContent(<App />);
186227
} catch (e) {
187228
setContent(<Failed>{e.toString()}</Failed>);
@@ -194,7 +235,9 @@ const AppContainer = () => {
194235

195236
return (
196237
<FlashContext.Provider value={flashState}>
197-
<AuthContext.Provider value={auth}>{content}</AuthContext.Provider>
238+
<AuthContext.Provider value={auth}>
239+
<ProxyContext.Provider value={defaultProxyState}>{content}</ProxyContext.Provider>
240+
</AuthContext.Provider>
198241
</FlashContext.Provider>
199242
);
200243
};

web/frontend/src/layout/Footer.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1+
import ProxyInput from 'components/utils/proxy';
2+
13
const Footer = () => (
24
<div className="flex flex-row border-t justify-center bg-white items-center w-full p-4 text-gray-300 text-xs">
35
<footer>
4-
<div className="max-w-7xl mx-auto py-2 px-4 overflow-hidden sm:px-6 lg:px-8">
6+
<div className="flex flex-row items-center max-w-7xl mx-auto py-2 px-4 overflow-hidden sm:px-6 lg:px-8">
57
<span className="text-gray-400"> &copy; 2022 DEDIS LAB - </span>
68
<a className="text-gray-600" href="https://github.com/dedis/dela">
79
https://github.com/dedis/dela
810
</a>
11+
<div className="px-10">
12+
<ProxyInput />
13+
</div>
914
</div>
1015
</footer>
1116
</div>

web/frontend/src/mocks/handlers.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ const SETUP_TIMER = 4000;
4343
const SHUFFLE_TIMER = 2000;
4444
const DECRYPT_TIMER = 8000;
4545

46+
const defaultProxy = 'http://localhost/';
47+
4648
const isAuthorized = (roles: UserRole[]): boolean => {
4749
const id = sessionStorage.getItem('id');
4850
const userRole = mockUserDB.find(({ sciper }) => sciper === id).role;
@@ -91,7 +93,7 @@ export const handlers = [
9193
return res(ctx.status(200));
9294
}),
9395

94-
rest.get(endpoints.elections, async (req, res, ctx) => {
96+
rest.get(endpoints.elections(defaultProxy), async (req, res, ctx) => {
9597
await new Promise((r) => setTimeout(r, RESPONSE_TIME));
9698

9799
return res(
@@ -104,7 +106,7 @@ export const handlers = [
104106
);
105107
}),
106108

107-
rest.get(endpoints.election(':ElectionID'), async (req, res, ctx) => {
109+
rest.get(endpoints.election(defaultProxy, ':ElectionID'), async (req, res, ctx) => {
108110
const { ElectionID } = req.params;
109111
await new Promise((r) => setTimeout(r, RESPONSE_TIME));
110112

@@ -408,4 +410,12 @@ export const handlers = [
408410

409411
return res(ctx.status(200), ctx.text('Action successfully done'));
410412
}),
413+
414+
rest.get(endpoints.getProxyConfig, async (req, res, ctx) => {
415+
await new Promise((r) => setTimeout(r, RESPONSE_TIME));
416+
417+
const response = defaultProxy;
418+
419+
return res(ctx.status(200), ctx.text(response));
420+
}),
411421
];

web/frontend/src/pages/election/Index.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import * as endpoints from 'components/utils/Endpoints';
77
import Loading from 'pages/Loading';
88
import { LightElectionInfo, Status } from 'types/election';
99
import ElectionTableFilter from './components/ElectionTableFilter';
10-
import { FlashContext, FlashLevel } from 'index';
10+
import { FlashContext, FlashLevel, ProxyContext } from 'index';
1111

1212
const ElectionIndex: FC = () => {
1313
const { t } = useTranslation();
1414
const fctx = useContext(FlashContext);
15+
const pctx = useContext(ProxyContext);
1516

1617
const [statusToKeep, setStatusToKeep] = useState<Status>(null);
1718
const [elections, setElections] = useState<LightElectionInfo[]>(null);
@@ -24,7 +25,7 @@ const ElectionIndex: FC = () => {
2425
},
2526
};
2627

27-
const [data, dataLoading, error] = useFetchCall(endpoints.elections, request);
28+
const [data, dataLoading, error] = useFetchCall(endpoints.elections(pctx.getProxy()), request);
2829

2930
useEffect(() => {
3031
if (error !== null) {

web/frontend/src/pages/election/components/utils/useChangeAction.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { ID } from 'types/configuration';
66
import { Action, OngoingAction, Status } from 'types/election';
77
import { pollDKG, pollElection } from './PollStatus';
88
import { NodeStatus } from 'types/node';
9-
import { FlashContext, FlashLevel } from 'index';
9+
import { FlashContext, FlashLevel, ProxyContext } from 'index';
1010
import { useNavigate } from 'react-router';
1111
import { ROUTE_ELECTION_INDEX } from 'Routes';
1212

@@ -65,6 +65,7 @@ const useChangeAction = (
6565

6666
const fctx = useContext(FlashContext);
6767
const navigate = useNavigate();
68+
const pctx = useContext(ProxyContext);
6869

6970
const modalClose = (
7071
<ConfirmModal
@@ -163,7 +164,7 @@ const useChangeAction = (
163164
// We stop polling when the status has changed to nextStatus
164165
const match = (s: Status) => s === nextStatus;
165166

166-
pollElection(endpoints.election(electionID), request, match, interval)
167+
pollElection(endpoints.election(pctx.getProxy(), electionID), request, match, interval)
167168
.then(
168169
() => onFullFilled(nextStatus),
169170
(reason: any) => onRejected(reason, previousStatus)

web/frontend/src/pages/election/components/utils/useGetResults.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { ID } from 'types/configuration';
22
import { Results } from 'types/election';
33
import * as endpoints from 'components/utils/Endpoints';
4+
import { useContext } from 'react';
5+
import { ProxyContext } from 'index';
46

57
const useGetResults = () => {
8+
const pctx = useContext(ProxyContext);
9+
610
async function getResults(
711
electionID: ID,
812
setError: React.Dispatch<any>,
@@ -14,7 +18,7 @@ const useGetResults = () => {
1418
};
1519

1620
try {
17-
const response = await fetch(endpoints.election(electionID), request);
21+
const response = await fetch(endpoints.election(pctx.getProxy(), electionID), request);
1822

1923
if (!response.ok) {
2024
throw Error(response.statusText);

0 commit comments

Comments
 (0)