Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
Signed-off-by: Davide Briani <[email protected]>
  • Loading branch information
davidebriani committed Mar 1, 2024
1 parent b97d08a commit d21379a
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 44 deletions.
20 changes: 1 addition & 19 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,12 @@ import PageRouter from './Router';
import AstarteProvider, { useAstarte } from './AstarteManager';
import type { DashboardConfig } from './types';
import Snackbar from './ui/Snackbar';
import useFetch from './hooks/useFetch';
import useInterval from './hooks/useInterval';
import createReduxStore from './store';

const DashboardSidebar = () => {
const config = useConfig();
const astarte = useAstarte();

const healthFetcher = useFetch(() => {
const apiChecks = [
astarte.client.getAppengineHealth(),
astarte.client.getRealmManagementHealth(),
astarte.client.getPairingHealth(),
];
if (config.features.flow) {
apiChecks.push(astarte.client.getFlowHealth());
}
return Promise.all(apiChecks);
});

useInterval(healthFetcher.refresh, 30000);

const isApiHealthy = healthFetcher.status !== 'err';

if (!astarte.isAuthenticated) {
return null;
}
Expand Down Expand Up @@ -80,7 +62,7 @@ const DashboardSidebar = () => {
)}
<Sidebar.Item label="Realm settings" link="/settings" icon="settings" />
<Sidebar.Separator />
<Sidebar.ApiStatus healthy={isApiHealthy} realm={astarte.realm} />
<Sidebar.ApiStatus />
<Sidebar.Separator />
<Sidebar.Item label="Logout" link="/logout" icon="logout" />
<Sidebar.AppInfo appVersion={import.meta.env.VITE_APP_VERSION || ''} />
Expand Down
14 changes: 12 additions & 2 deletions src/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,14 @@ const ApiStatusCard = ({
interface DevicesCardProps {
connectedDevices: number;
totalDevices: number;
deviceRegistrationLimit: number | null;
connectedDevicesProvider: ChartProvider<'Object', ConnectedDevices>;
}

const DevicesCard = ({
connectedDevices,
totalDevices,
deviceRegistrationLimit,
connectedDevicesProvider,
}: DevicesCardProps): React.ReactElement => (
<Card id="devices-card" className="h-100">
Expand All @@ -123,9 +125,15 @@ const DevicesCard = ({
<Row noGutters>
<Col xs={12} lg={6}>
<Card.Title>Connected devices</Card.Title>
<Card.Text>{connectedDevices}</Card.Text>
<Card.Text>
{connectedDevices} / {totalDevices}
</Card.Text>
<Card.Title>Registered devices</Card.Title>
<Card.Text>{totalDevices}</Card.Text>
<Card.Text>
{deviceRegistrationLimit != null
? `${totalDevices} / ${deviceRegistrationLimit}`
: totalDevices}
</Card.Text>
</Col>
<Col xs={12} lg={6}>
{totalDevices > 0 && <ConnectedDevicesChart provider={connectedDevicesProvider} />}
Expand Down Expand Up @@ -319,6 +327,7 @@ const HomePage = (): React.ReactElement => {
const realmManagementHealth = useFetch(astarte.client.getRealmManagementHealth);
const pairingHealth = useFetch(astarte.client.getPairingHealth);
const flowHealth = useFetch(config.features.flow ? astarte.client.getFlowHealth : async () => {});
const deviceRegistrationLimitFetcher = useFetch(astarte.client.getDeviceRegistrationLimit);
const navigate = useNavigate();

const connectedDevicesProvider = useMemo(
Expand Down Expand Up @@ -379,6 +388,7 @@ const HomePage = (): React.ReactElement => {
<DevicesCard
connectedDevices={connectedDevices}
totalDevices={totalDevices}
deviceRegistrationLimit={deviceRegistrationLimitFetcher.value}
connectedDevicesProvider={connectedDevicesProvider}
/>
</Col>
Expand Down
5 changes: 5 additions & 0 deletions src/RealmSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export default (): React.ReactElement => {
const navigate = useNavigate();

const authConfigFetcher = useFetch(astarte.client.getConfigAuth);
const deviceRegistrationLimitFetcher = useFetch(astarte.client.getDeviceRegistrationLimit);

const showModal = useCallback(() => setIsModalVisible(true), [setIsModalVisible]);

Expand Down Expand Up @@ -122,6 +123,10 @@ export default (): React.ReactElement => {
return (
<SingleCardPage title="Realm Settings">
<AlertsBanner alerts={formAlerts} />
<Form.Group controlId="device-registration-limit">
<Form.Label>Device registration limit</Form.Label>
<Form.Control value={deviceRegistrationLimitFetcher.value ?? 'No limit'} readOnly />
</Form.Group>
<WaitForData
data={authConfigFetcher.value}
status={authConfigFetcher.status}
Expand Down
23 changes: 19 additions & 4 deletions src/RegisterDevicePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type { AstarteDevice, AstarteInterfaceDescriptor } from 'astarte-client';
import Icon from './components/Icon';
import ConfirmModal from './components/modals/Confirm';
import FormModal from './components/modals/Form';
import useFetch from './hooks/useFetch';
import SingleCardPage from './ui/SingleCardPage';
import { byteArrayToUrlSafeBase64, urlSafeBase64ToByteArray } from './Base64';
import { AlertsBanner, useAlerts } from './AlertManager';
Expand Down Expand Up @@ -253,7 +254,7 @@ const NamespaceModal = ({ onCancel, onConfirm }: NamespaceModalProps) => {
);
};

export default (): React.ReactElement => {
function RegisterDevicePage(): React.ReactElement {
const searchQuery = new URLSearchParams(useLocation().search);
const initialDeviceId = searchQuery.get('deviceId') || '';
const [deviceId, setDeviceId] = useState<AstarteDevice['id']>(initialDeviceId);
Expand All @@ -268,6 +269,15 @@ export default (): React.ReactElement => {
const [registrationAlerts, registrationAlertsController] = useAlerts();
const astarte = useAstarte();
const navigate = useNavigate();
const deviceRegistrationLimitFetcher = useFetch(astarte.client.getDeviceRegistrationLimit);
const devicesStatsFetcher = useFetch(astarte.client.getDevicesStats);

const isFetchingRegistrationStats =
devicesStatsFetcher.status === 'loading' || deviceRegistrationLimitFetcher.status === 'loading';
const deviceRegistrationLimitReached =
devicesStatsFetcher.value != null &&
deviceRegistrationLimitFetcher.value != null &&
devicesStatsFetcher.value.totalDevices >= deviceRegistrationLimitFetcher.value;

const byteArray = urlSafeBase64ToByteArray(deviceId);
const isValidDeviceId = byteArray.length === 17 && byteArray[16] === 0;
Expand Down Expand Up @@ -295,8 +305,11 @@ export default (): React.ReactElement => {
setShowCredentialSecretModal(true);
})
.catch((err) => {
const errorMessage = deviceRegistrationLimitReached
? `The device registration limit is set to ${deviceRegistrationLimitFetcher.value} and there are too many registered devices already.`
: err.message;
setRegisteringDevice(false);
registrationAlertsController.showError(`Couldn't register device: ${err.message}`);
registrationAlertsController.showError(`Could not register the device. ${errorMessage}`);
});
};

Expand Down Expand Up @@ -371,7 +384,7 @@ export default (): React.ReactElement => {
<Button
variant="primary"
type="submit"
disabled={!isValidDeviceId || isRegisteringDevice}
disabled={!isValidDeviceId || isRegisteringDevice || isFetchingRegistrationStats}
>
{isRegisteringDevice && (
<Spinner as="span" size="sm" animation="border" role="status" className="mr-2" />
Expand Down Expand Up @@ -413,4 +426,6 @@ export default (): React.ReactElement => {
)}
</SingleCardPage>
);
};
}

export default RegisterDevicePage;
84 changes: 65 additions & 19 deletions src/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,74 @@ import React, { useMemo } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Nav, NavItem, NavLink } from 'react-bootstrap';

import { useAstarte } from './AstarteManager';
import { useConfig } from './ConfigManager';
import Icon from './components/Icon';
import useFetch from './hooks/useFetch';
import useInterval from './hooks/useInterval';

const SidebarApiStatus = () => {
const config = useConfig();
const astarte = useAstarte();
const deviceRegistrationLimitFetcher = useFetch(astarte.client.getDeviceRegistrationLimit);
const devicesStatsFetcher = useFetch(astarte.client.getDevicesStats);

const healthFetcher = useFetch(() => {
const apiChecks = [
astarte.client.getAppengineHealth(),
astarte.client.getRealmManagementHealth(),
astarte.client.getPairingHealth(),
];
if (config.features.flow) {
apiChecks.push(astarte.client.getFlowHealth());
}
return Promise.all(apiChecks);
});

interface SidebarApiStatusProps {
healthy: boolean;
realm: React.ReactNode;
}
useInterval(deviceRegistrationLimitFetcher.refresh, 30000);
useInterval(devicesStatsFetcher.refresh, 30000);
useInterval(healthFetcher.refresh, 30000);

const SidebarApiStatus = ({ healthy, realm }: SidebarApiStatusProps) => (
<NavItem className="nav-status pl-4">
<div>
<b>Realm</b>
</div>
<p>{realm}</p>
<div>
<b>API Status</b>
</div>
<p className="my-1">
<Icon icon={healthy ? 'statusConnected' : 'statusDisconnected'} className="mr-2" />
{healthy ? 'Up and running' : 'Degraded'}
</p>
</NavItem>
);
const isApiHealthy = healthFetcher.status !== 'err';

if (!astarte.isAuthenticated) {
return null;
}

return (
<NavItem className="nav-status pl-4">
<div>
<b>Realm</b>
</div>
<p>{astarte.realm}</p>
<div>
<b>Connected devices</b>
</div>
<p>
{devicesStatsFetcher.value != null
? `${devicesStatsFetcher.value.connectedDevices} / ${devicesStatsFetcher.value.totalDevices}`
: '-'}
</p>
<div>
<b>Registered devices</b>
</div>
<p>
{devicesStatsFetcher.value != null
? deviceRegistrationLimitFetcher.value != null
? `${devicesStatsFetcher.value.totalDevices} / ${deviceRegistrationLimitFetcher.value}`
: devicesStatsFetcher.value.totalDevices
: '-'}
</p>
<div>
<b>API Status</b>
</div>
<p className="my-1">
<Icon icon={isApiHealthy ? 'statusConnected' : 'statusDisconnected'} className="mr-2" />
{isApiHealthy ? 'Up and running' : 'Degraded'}
</p>
</NavItem>
);
};

interface SidebarAppInfoProps {
appVersion: string;
Expand Down
7 changes: 7 additions & 0 deletions src/astarte-client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class AstarteClient {
this.listeners = {};

this.getConfigAuth = this.getConfigAuth.bind(this);
this.getDeviceRegistrationLimit = this.getDeviceRegistrationLimit.bind(this);
this.getBlocks = this.getBlocks.bind(this);
this.getDeviceData = this.getDeviceData.bind(this);
this.getDevicesStats = this.getDevicesStats.bind(this);
Expand All @@ -198,6 +199,7 @@ class AstarteClient {
this.apiConfig = {
realmManagementHealth: astarteAPIurl`${config.realmManagementApiUrl}health`,
auth: astarteAPIurl`${config.realmManagementApiUrl}v1/${'realm'}/config/auth`,
deviceRegistrationLimit: astarteAPIurl`${config.realmManagementApiUrl}v1/${'realm'}/config/device_registration_limit`,
interfaces: astarteAPIurl`${config.realmManagementApiUrl}v1/${'realm'}/interfaces`,
interfaceMajors: astarteAPIurl`${config.realmManagementApiUrl}v1/${'realm'}/interfaces/${'interfaceName'}`,
interface: astarteAPIurl`${config.realmManagementApiUrl}v1/${'realm'}/interfaces/${'interfaceName'}/${'interfaceMajor'}`,
Expand Down Expand Up @@ -268,6 +270,11 @@ astarteAPIurl`${config.realmManagementApiUrl}v1/${'realm'}/interfaces/${'interfa
});
}

async getDeviceRegistrationLimit(): Promise<number | null> {
const response = await this.$get(this.apiConfig.deviceRegistrationLimit(this.config));
return response.data;
}

async getPolicyNames(): Promise<string[]> {
const response = await this.$get(this.apiConfig.policies(this.config));
return response.data;
Expand Down

0 comments on commit d21379a

Please sign in to comment.