Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable api host #1305

Merged
merged 3 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
}
Loading