Skip to content
Open
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
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
"react-router-dom": "^7.11.0",
"sass": "^1.97.0",
"swr": "^2.3.8",
"vite-plugin-compression": "^0.5.1"
"vite-plugin-compression": "^0.5.1",
"echarts": "^6.0.0",
"echarts-for-react": "^3.0.5"
},
"devDependencies": {
"typescript":">=4.0.0",
Expand Down
11 changes: 10 additions & 1 deletion public/locales/bg.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ network_devices:
new_network_device: Ново мрежово устройство
edit:
edit_network_device: Редакция на мрежово устройство
rooms:
big-room: Голяма зала
small-room: Малка зала
kitchen: Кухня
outside: Навън
views:
dashboard:
sensor_readings: Температура и влажност на въздуха
Expand All @@ -119,7 +124,7 @@ views:
colibri_message_contact: |
Ако имате проблем с ползването му, или е нужно да заявите достъп за нови служители, моля свържете се с Инит Лаб на
имейл [email protected].
colibri_message_emergency: При неотложни случаи, моля обадете се на тел. 0883 433 990 (Венцислав).
colibri_message_emergency: При неотложни случаи, моля обадете се на тел. 02 422 54 36.
devices:
lock: заключи
unlock: отключи
Expand Down Expand Up @@ -165,6 +170,8 @@ views:
lights: Осветление
hvac: Климатици
sensors: Графики
dark_mode: Тъмна тема
language: Език
oauth_application_management: OAuth интеграция
oauth_token_management: Упълномощени приложения
registrations:
Expand Down Expand Up @@ -227,6 +234,8 @@ views:
are_you_sure: 'Сигурни ли сте?'
sensors:
title: Показания на сензорите
temperature: Температура
humidity: Влажност
action_log:
title: Лог на действията
columns:
Expand Down
11 changes: 10 additions & 1 deletion public/locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ network_devices:
new_network_device: New network device
edit:
edit_network_device: Edit network device
rooms:
big-room: Big Room
small-room: Small Room
kitchen: Kitchen
outside: Outside
views:
dashboard:
sensor_readings: Air temperature and humidity
Expand All @@ -112,7 +117,7 @@ views:
colibri_message_contact: |
In case of technical difficulties, or you need to request access for new employees, please contact init Lab
at [email protected].
colibri_message_emergency: In case of major malfunction, please call +359 883 433 990 (Vencislav).
colibri_message_emergency: In case of major malfunction, please call +359 2 422 54 36.
devices:
lock: lock
unlock: unlock
Expand Down Expand Up @@ -158,6 +163,8 @@ views:
lights: Lights
hvac: HVAC
sensors: Sensors
dark_mode: Dark Mode
language: Language
oauth_application_management: OAuth integration
oauth_token_management: Authorized applications
registrations:
Expand Down Expand Up @@ -219,6 +226,8 @@ views:
are_you_sure: 'Are you shnur?'
sensors:
title: Sensor readings
temperature: Temperature
humidity: Humidity
action_log:
title: Action logs
columns:
Expand Down
5 changes: 4 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {createElement} from 'react';
import {createElement, useEffect} from 'react';
import {Container} from 'react-bootstrap';
import {Route, Routes} from 'react-router-dom';

Expand All @@ -14,10 +14,13 @@ import {useVariant} from './hooks/useVariant.ts';
import {getDoorActions, getHvacActions, getLightActions} from "./utils/device.ts";
import Devices from "./pages/Devices.tsx";
import {useDocumentTitle} from '@uidotdev/usehooks';
import {useTheme} from './hooks/useTheme.ts';

function App() {
const variant = useVariant();
useDocumentTitle(variant.title);
const [theme] = useTheme();
useEffect(() => document.documentElement.setAttribute('data-bs-theme', theme), [theme]);

return (<>
<NavBar/>
Expand Down
18 changes: 5 additions & 13 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export const oidc = {

export type MqttConfig = {[topic: string]: {label: string}}
export const sensors: MqttConfig = {
'sensors/big-room': {label: 'Big room',},
'sensors/small-room': {label: 'Small room',},
'sensors/kitchen': {label: 'Kitchen',},
'sensors/outside': {label: 'Outside',},
'sensors/big-room/test': {label: 'big-room'},
'sensors/small-room/test': {label: 'small-room',},
'sensors/kitchen/test': {label: 'kitchen',},
'sensors/outside/test': {label: 'outside',},
};

export const variantHosts: {[hostname: string]: string} = {
Expand Down Expand Up @@ -45,12 +45,4 @@ export const variants: {[variant: string]: VariantConfig} = {
logo: {url: colibriLogo, alt: 'colibri logo',},
title: 'Casa Libri',
},
}

export const grafana = {
urls: [
'https://stats.initlab.org/d-solo/SGAb0ZXMk/temperature-and-humidity?orgId=1&refresh=1m&panelId=4',
'https://stats.initlab.org/d-solo/SGAb0ZXMk/temperature-and-humidity?orgId=1&refresh=1m&panelId=5',
'https://stats.initlab.org/d-solo/SGAb0ZXMk/temperature-and-humidity?orgId=1&refresh=1m&panelId=10',
]
};
}
8 changes: 7 additions & 1 deletion src/hooks/useEndpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import useSWR, {type Fetcher, type SWRConfiguration} from 'swr';
import {authenticatedFetcher, fetcher} from '../utils/swr.js';
import {useAuthStorage} from './useAuthStorage.ts';
import type {FaunaPresentUser, FaunaUser} from "../fauna-types";
import type {PortierActionLogEntry, PortierDevice, RawMqttReading} from "../portier-types";
import type {MqttSensorHistory, PortierActionLogEntry, PortierDevice, RawMqttReading} from "../portier-types";

function useAuthSWR<TResult>(key: URL | string, config?: SWRConfiguration) {
const {accessToken} = useAuthStorage();
Expand Down Expand Up @@ -33,6 +33,12 @@ export function useMqttStatus(config?: SWRConfiguration) {
return useCheckSWR<{ [topic: string]: RawMqttReading }>(import.meta.env.MQTT_PROXY_URL.concat('status'), config);
}

export function useMqttHistory(config?: SWRConfiguration) {
return useCheckSWR<{
[sensor: string]: MqttSensorHistory
}>(import.meta.env.MQTT_PROXY_URL.concat('sensors/'), config);
}

export function useActionLog(config?: SWRConfiguration) {
return useAuthSWR<PortierActionLogEntry[]>(import.meta.env.PORTIER_URL.concat('api/actionLog/0/0'), config);
}
20 changes: 20 additions & 0 deletions src/hooks/useLocale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {type Dispatch, useEffect} from 'react';
import {useLocalStorage} from '@uidotdev/usehooks';
import {useCurrentUser} from './useEndpoints.ts';
import {useTranslation} from "react-i18next";

const LOCALE_KEY = 'locale';

export function useLocale(): [string | undefined, Dispatch<string>] {
const {data: user} = useCurrentUser();
const [locale, setStoredLocale] = useLocalStorage<string>(LOCALE_KEY);

const {i18n} = useTranslation();

useEffect(() => {
i18n.changeLanguage(locale ?? user?.locale ?? 'bg').then(() => {})
}, [i18n, locale, user?.locale]);


return [locale, setStoredLocale];
}
8 changes: 8 additions & 0 deletions src/hooks/useTheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {useLocalStorage, useMediaQuery} from '@uidotdev/usehooks';

const THEME_KEY = 'theme';

export function useTheme() {
const darkMode = useMediaQuery('(prefers-color-scheme: dark)') ? 'dark' : 'light';
return useLocalStorage<string>(THEME_KEY, darkMode);
}
5 changes: 2 additions & 3 deletions src/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import { initReactI18next } from 'react-i18next';
import { load } from 'js-yaml';
import {initReactI18next} from 'react-i18next';
import {load} from 'js-yaml';

i18n
.use(Backend)
.use(initReactI18next)
.init({
fallbackLng: 'bg',
backend: {
loadPath: import.meta.env.BASE_URL + 'locales/{{lng}}.yaml',
parse: (data: any) => load(data),
Expand Down
29 changes: 21 additions & 8 deletions src/layout/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
import {useEffect} from 'react';
import {NavLink, useLocation} from 'react-router-dom';
import {Container, Image, Nav, Navbar, NavDropdown} from 'react-bootstrap';
import {useTranslation} from 'react-i18next';

import './NavBar.css';
import i18n from '../i18n.ts';
import {useVariant} from '../hooks/useVariant.ts';
import {useCurrentUser} from '../hooks/useEndpoints.ts';
import {useTheme} from '../hooks/useTheme.ts';
import RequireRole from '../widgets/RequireRole.tsx';
import RequireVariant from "../widgets/RequireVariant.tsx";
import {useLocale} from '../hooks/useLocale.ts';

const NavBar = () => {
const {t} = useTranslation();
const [locale, setLocale] = useLocale();
const backendUrl = import.meta.env.OIDC_AUTHORITY_URL;
const {
data: user,
} = useCurrentUser();
const variant = useVariant();

useEffect(function () {
if (user?.locale) {
i18n.changeLanguage(user.locale).then(() => {
});
}
}, [user]);
const [theme, setTheme] = useTheme();
const changeLanguage = async () =>
setLocale((!locale || locale == 'bg') ? 'en' : 'bg');

const changeTheme = () => setTheme(theme == 'light' ? 'dark' : 'light');

// useEffect(function () {
// if (user?.locale) {
// i18n.changeLanguage(user.locale).then(() => {
// });
// }
// }, [user]);

const location = useLocation();

Expand Down Expand Up @@ -71,6 +78,12 @@ const NavBar = () => {
{t('views.navigation.labbers')}
</Nav.Link>
</RequireRole>
</Nav>
<Nav>
<Nav.Link onClick={changeLanguage}><i className="fa-solid fa-language"/>{' '}<span
className={'d-lg-none'}>{t('views.navigation.language')}</span></Nav.Link>
<Nav.Link onClick={changeTheme}><i className="fa-solid fa-circle-half-stroke"/>{' '}<span
className={'d-lg-none'}>{t('views.navigation.dark_mode')}</span></Nav.Link>
{user ? <NavDropdown title={<>
<i className="fa-solid fa-user"/>{' '}
{t('views.navigation.account')}
Expand Down
10 changes: 5 additions & 5 deletions src/pages/Devices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {PortierDevice} from "../portier-types";
const Devices = ({
deviceGroup,
deviceActionMapper,
}: {deviceGroup: string, deviceActionMapper: (d: PortierDevice) => string[]}) => {
}: { deviceGroup: string, deviceActionMapper: (d: PortierDevice) => string[] }) => {
const {t} = useTranslation();

const {
Expand All @@ -36,11 +36,11 @@ const Devices = ({
{devices && <>
{filteredDevices.length > 0 ? filteredDevices.map(device => {
const deviceActions = deviceActionMapper(device);
const isUnavailable = device?.statuses?.available === false;
const isOpen = device?.statuses?.open === true;
const isUnavailable = !device?.statuses?.available;
const isOpen = !!device?.statuses?.open;

return (<Col key={device.id} className={`col-xl-${2*deviceActions.length}`}>
<Card>
return (<Col key={device.id} className={`col-xl-${2 * deviceActions.length}`}>
<Card border="secondary">
<Card.Header
className={'text-start' + (variantName === "initlab" ? ' bg-primary text-light' : '')}>
{device.name}
Expand Down
18 changes: 11 additions & 7 deletions src/pages/Sensors.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import {Col, Row} from 'react-bootstrap';
import {useTranslation} from 'react-i18next';
import {grafana} from '../config.ts';
import {useMqttHistory} from "../hooks/useEndpoints.ts";
import SensorGraph from "../widgets/SensorReadings/SensorGraph.tsx";

const Sensors = () => {
const {t} = useTranslation();
const {data} = useMqttHistory();

return (<>
if (!data) return <></>;

return <>
<Row>
<Col>
<h2>{t('views.sensors.title')}</h2>
</Col>
</Row>
<Row className="row-cols row-cols-1 row-cols-xxl-3">
{grafana.urls.map(url => <Col key={url}>
<iframe src={url} className="w-100" height={300}/>
</Col>)}
<Row className="row-cols row-cols-1 row-cols-xxl-2">
{Object.entries(data).map(([sensor, metrics]) =>
<Col key={sensor}><SensorGraph sensor={sensor} metrics={metrics}/></Col>
)}
</Row>
</>);
</>;
};

export default Sensors;
2 changes: 2 additions & 0 deletions src/portier-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ export type PortierActionLogEntry = {
User?: { name: string, username: string, },
Application?: { name: string, },
}

export type MqttSensorHistory = { [metric: string]: [[number, number, number, number]] }
Loading
Loading