Skip to content

Commit

Permalink
Merge pull request #1305 from Chia-Network/configurable-api-host
Browse files Browse the repository at this point in the history
Configurable api host
  • Loading branch information
TheLastCicada authored Aug 15, 2024
2 parents 01d6c40 + 89c3a49 commit 213568c
Show file tree
Hide file tree
Showing 10 changed files with 12,691 additions and 12,635 deletions.
25,216 changes: 12,608 additions & 12,608 deletions package-lock.json

Large diffs are not rendered by default.

25 changes: 21 additions & 4 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import {IntlProvider} from 'react-intl';
import {loadLocaleData} from '@/translations';
import '@/App.css';
import {AppNavigator} from '@/routes';
import {setLocale} from '@/store/slices/app';
import {IndeterminateProgressOverlay} from '@/components';
import {resetApiHost, setConfigFileLoaded, setHost, setLocale} from '@/store/slices/app';
import {ComponentCenteredSpinner} from '@/components';
import {useGetUiConfigQuery} from '@/api';

/**
* @returns app react component to be rendered by electron as the UI
Expand All @@ -14,6 +15,8 @@ function App() {
const dispatch = useDispatch();
const appStore = useSelector((state: any) => state.app);
const [translationTokens, setTranslationTokens] = useState<object>();
const [appLoading, setAppLoading] = useState(true);
const { data: configData, isLoading: configFileLoading } = useGetUiConfigQuery();

useEffect(() => {
if (appStore.locale) {
Expand All @@ -27,8 +30,22 @@ function App() {
}
}, [appStore.locale, dispatch]);

if (!translationTokens) {
return <IndeterminateProgressOverlay />;
useEffect(() => {
if (configData) {
if (configData?.apiHost) {
dispatch(setHost({ apiHost: configData.apiHost }));
}
dispatch(setConfigFileLoaded({ configFileLoaded: true }));
} else if (!configFileLoading && !configData && appStore.configFileLoaded) {
dispatch(resetApiHost());
dispatch(setConfigFileLoaded({ configFileLoaded: false }));
}
}, [appStore.apiHost, appStore.configFileLoaded, configData, configFileLoading, dispatch]);

setTimeout(() => setAppLoading(false), 400);

if (!translationTokens || configFileLoading || appLoading) {
return <ComponentCenteredSpinner />;
}

return (
Expand Down
5 changes: 5 additions & 0 deletions src/renderer/api/cadt/v1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const baseQueryWithDynamicHost = async (args, api, extraOptions) => {
let modifiedArgs = args;
const state = api.getState();
const currentHost = state.app.apiHost;
const currentApiKey = state.app.apiKey;

// Check if currentHost is equal to the initialState's apiHost
const effectiveHost =
Expand All @@ -35,6 +36,10 @@ const baseQueryWithDynamicHost = async (args, api, extraOptions) => {
modifiedArgs = {
...args,
url: `${effectiveHost}${args.url}`,
headers: {
...args.headers,
...(currentApiKey ? { 'X-Api-Key': currentApiKey } : {}),
},
};
}

Expand Down
21 changes: 19 additions & 2 deletions src/renderer/api/cadt/v1/system/system.api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { cadtApi } from '../';
// @ts-ignore
import { BaseQueryResult } from '@reduxjs/toolkit/dist/query/baseQueryTypes';
import _ from 'lodash';

export interface Health {
message: string;
Expand All @@ -17,6 +18,10 @@ interface ServerHealth {
readOnly: boolean;
}

export interface Config {
apiHost?: string;
}

const systemApi = cadtApi.injectEndpoints({
endpoints: (builder) => ({
getHealth: builder.query<ServerHealth, GetHealthParams>({
Expand All @@ -29,7 +34,7 @@ const systemApi = cadtApi.injectEndpoints({
const isHealthy = response?.message === 'OK';
const readOnly = meta?.response?.headers.get('cw-readonly') === 'true';
return { isHealthy, readOnly };
}
},
}),
getHealthImmediate: builder.mutation<boolean, GetHealthParams>({
query: ({ apiHost = '', apiKey }) => ({
Expand All @@ -41,7 +46,19 @@ const systemApi = cadtApi.injectEndpoints({
return baseQueryReturnValue?.message === 'OK';
},
}),
getUiConfig: builder.query<Config | undefined, void>({
query: () => ({
url: `config.json`,
method: 'GET',
}),
transformResponse(baseQueryReturnValue: BaseQueryResult<Config>): Config | undefined {
if (_.isEmpty(baseQueryReturnValue) || _.isNil(baseQueryReturnValue)) {
return undefined;
}
return baseQueryReturnValue;
},
}),
}),
});

export const { useGetHealthQuery, useGetHealthImmediateMutation } = systemApi;
export const { useGetHealthQuery, useGetHealthImmediateMutation, useGetUiConfigQuery } = systemApi;
16 changes: 8 additions & 8 deletions src/renderer/components/blocks/buttons/ConnectButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@ import { Button, ConnectModal } from '@/components';
import { FormattedMessage } from 'react-intl';
import { useUrlHash } from '@/hooks';
import { useGetHealthQuery } from '@/api/cadt/v1/system';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '@/store';
import initialAppState from '@/store/slices/app/app.initialstate';
import { resetApiHost } from '@/store/slices/app';
import { useDispatch } from 'react-redux';

const ConnectButton: React.FC = () => {
const location = useLocation();
const dispatch = useDispatch();
const [isActive, setActive] = useUrlHash('connect');
const { apiHost } = useSelector((state: RootState) => state.app);
const { configFileLoaded } = useSelector((state: RootState) => state.app);

const { data: serverHealth, isLoading, refetch } = useGetHealthQuery({});

// Activte the connect modal when the service is not found
// Activate the connect modal when the service is not found
useEffect(() => {
if (!serverHealth?.isHealthy && !isLoading) {
setActive(true);
} else if (serverHealth?.isHealthy && isActive) {
setActive(false);
}
}, [serverHealth, setActive, isLoading]);
}, [serverHealth, setActive, isLoading, configFileLoaded, isActive]);

// Recheck the health when the location changes
useEffect(() => {
Expand All @@ -40,10 +40,10 @@ const ConnectButton: React.FC = () => {
<Button
color="none"
onClick={() => {
apiHost === initialAppState.apiHost ? setActive(true) : handleDisconnect();
!serverHealth?.isHealthy ? setActive(true) : handleDisconnect();
}}
>
{apiHost == initialAppState.apiHost ? <FormattedMessage id="connect" /> : <FormattedMessage id="disconnect" />}
{!serverHealth?.isHealthy ? <FormattedMessage id="connect" /> : <FormattedMessage id="disconnect" />}
</Button>
{isActive && <ConnectModal onClose={() => setActive(false)} />}
</>
Expand Down
15 changes: 12 additions & 3 deletions src/renderer/components/blocks/forms/ConnectForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import React, { useCallback } from 'react';
import { noop } from 'lodash';
import { ErrorMessage, Field, Form, Formik } from 'formik';
import * as yup from 'yup';
import { FloatingLabel, FormButton, HelperText, Spacer, Button } from '@/components';
import { Button, FloatingLabel, FormButton, HelperText, Spacer } from '@/components';
import { Alert } from 'flowbite-react';
import { FormattedMessage, IntlShape, useIntl } from 'react-intl';
import { useUrlHash } from '@/hooks';
import { useSelector } from 'react-redux';
import { RootState } from '@/store';

const validationSchema = yup.object({
apiHost: yup
Expand All @@ -28,6 +30,10 @@ interface FormProps {
const ConnectForm: React.FC<FormProps> = ({ onSubmit, hasServerError, onClearError = noop }) => {
const intl: IntlShape = useIntl();
const [, setIsActive] = useUrlHash('connect');
const appStore = useSelector((state: RootState) => state.app);
const configFileLoaded = appStore.configFileLoaded;
const apiHost = configFileLoaded ? appStore.apiHost : '';
const apiKey = appStore?.apiKey ? appStore.apiKey : '';

const handleSubmit = useCallback(
async (values: { apiHost: string; apiKey?: string }, { setSubmitting }) => {
Expand All @@ -51,7 +57,7 @@ const ConnectForm: React.FC<FormProps> = ({ onSubmit, hasServerError, onClearErr
}, [setIsActive]);

return (
<Formik initialValues={{ apiHost: '', apiKey: '' }} validationSchema={validationSchema} onSubmit={handleSubmit}>
<Formik initialValues={{ apiHost, apiKey }} validationSchema={validationSchema} onSubmit={handleSubmit}>
{({ errors, touched, isSubmitting }) => (
<Form>
{hasServerError && (
Expand All @@ -64,13 +70,16 @@ const ConnectForm: React.FC<FormProps> = ({ onSubmit, hasServerError, onClearErr
)}
<div className="mb-4">
<HelperText className="text-gray-400">
<FormattedMessage id="server-address-helper" />
<FormattedMessage
id={configFileLoaded ? 'api-host-loaded-from-configuration' : 'server-address-helper'}
/>
</HelperText>
<Spacer size={5} />
<Field name="apiHost">
{({ field }) => (
<FloatingLabel
id="apiHost"
disabled={configFileLoaded}
label={intl.formatMessage({ id: 'server-address' })}
color={errors.apiHost && touched.apiHost && 'error'}
variant="outlined"
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/components/blocks/modals/ConnectModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Modal, ConnectForm } from '@/components';
import { ConnectForm, Modal } from '@/components';
import { FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { setHost } from '@/store/slices/app';
Expand All @@ -20,9 +20,9 @@ const ConnectModal: React.FC<ConnectModalProps> = ({ onClose }: ConnectModalProp
const [, setActive] = useUrlHash('connect');

const handleSubmit = async (apiHost: string, apiKey?: string) => {
const response: BaseQueryResult | FetchBaseQueryError | SerializedError = await getHealth({ apiHost });
const response: BaseQueryResult | FetchBaseQueryError | SerializedError = await getHealth({ apiHost, apiKey });

if (!response.data) {
if (!response?.data) {
setServerNotFound(true);
return;
}
Expand Down
12 changes: 7 additions & 5 deletions src/renderer/store/slices/app/app.initialstate.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
export interface AppState {
locale?: string | null,
apiHost: string,
apiKey?: string | null,
isDarkTheme: boolean
locale?: string | null;
apiHost: string;
apiKey?: string | null;
configFileLoaded: boolean;
isDarkTheme: boolean;
}

const initialState: AppState = {
locale: null,
apiHost: 'http://localhost:31310',
apiKey: null,
isDarkTheme: false
configFileLoaded: false,
isDarkTheme: false,
};

export default initialState;
7 changes: 6 additions & 1 deletion src/renderer/store/slices/app/app.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,18 @@ export const appSlice = createSlice({
state.apiHost = initialState.apiHost;
state.apiKey = initialState.apiKey;
},

setConfigFileLoaded: (state, { payload }: { payload: { configFileLoaded: boolean } }) => {
state.configFileLoaded = payload.configFileLoaded;
},

toggleTheme: (state) => {
state.isDarkTheme = !state.isDarkTheme;
},
},
});

export const { setLocale, setHost, resetApiHost, toggleTheme } = appSlice.actions;
export const { setLocale, setHost, resetApiHost, toggleTheme, setConfigFileLoaded } = appSlice.actions;

export const selectCurrentHost = (state) => state.app.host;

Expand Down
3 changes: 2 additions & 1 deletion src/renderer/translations/tokens/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,5 +204,6 @@
"the-project-has-been-successfully-transferred-and-the-associated-changes-have-been-committed": "The project has been successfully transferred and the associated changes have been committed",
"project-transfer-error": "Project Transfer Error",
"failed-to-commit-project-transfer": "Failed to commit project transfer",
"no-changes-have-been-made": "No changes have been made"
"no-changes-have-been-made": "No changes have been made",
"api-host-loaded-from-configuration": "API host loaded from configuration"
}

0 comments on commit 213568c

Please sign in to comment.