Added frontend description

This commit is contained in:
kunkliricsi 2020-12-06 15:41:59 +01:00
parent dbad96be95
commit 8970d4fec3
28 changed files with 23563 additions and 2739 deletions

View File

@ -22,7 +22,17 @@
--> -->
<title>Birdmap</title> <title>Birdmap</title>
</head> </head>
<body style="height: 100vh;"> <body>
<style>
body {
height: 100vh;
-ms-overflow-style: none;
}
body::-webkit-scrollbar {
display:none;
}
</style>
<noscript> <noscript>
You need to enable JavaScript to run this app. You need to enable JavaScript to run this app.
</noscript> </noscript>

View File

@ -60,7 +60,7 @@ namespace Birdmap.Controllers
var token = tokenHandler.CreateToken(tokenDescriptor); var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token); var tokenString = tokenHandler.WriteToken(token);
var response = _mapper.Map<AuthenticateResponse>(user); AuthenticateResponse response = _mapper.Map<AuthenticateResponse>(user);
response.AccessToken = tokenString; response.AccessToken = tokenString;
response.TokenType = "Bearer"; response.TokenType = "Bearer";
response.ExpiresIn = expiresInSeconds; response.ExpiresIn = expiresInSeconds;

View File

@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2020-12-04T17:21:22.434Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36" etag="puyzN88MKD48mWwX1Ew6" version="13.10.9" type="device"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">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</diagram></mxfile>

View File

@ -3,7 +3,7 @@
url = {https://kubernetes.io}, url = {https://kubernetes.io},
} }
@dashboard{kubernetes-dashboard, @misc{kubernetes-dashboard,
title = {A Kubernetes Dashboard hivatalos oldala}, title = {A Kubernetes Dashboard hivatalos oldala},
url = {https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/}, url = {https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/},
} }
@ -53,11 +53,21 @@
url = {https://jwt.io/introduction/}, url = {https://jwt.io/introduction/},
} }
@misc{automapper,
title = {Az AutoMapper hivatalos oldala},
url = {https://automapper.org/},
}
@misc{react, @misc{react,
title = {A React.js hivatalos oldala}, title = {A React.js hivatalos oldala},
url = {https://reactjs.org/}, url = {https://reactjs.org/},
} }
@misc{react-context,
title = {A React Context dokumentációja},
url = {https://reactjs.org/docs/context.html},
}
@misc{material, @misc{material,
title = {A Material hivatalos oldala}, title = {A Material hivatalos oldala},
url = {https://material.io/}, url = {https://material.io/},
@ -83,6 +93,11 @@
url = {https://github.com/RicoSuter/NSwag}, url = {https://github.com/RicoSuter/NSwag},
} }
@misc{nswag-studio,
title = {Az NSwag Studio github oldala},
url = {https://github.com/RicoSuter/NSwag/wiki/NSwagStudio},
}
@misc{swagger-ui, @misc{swagger-ui,
title = {A Swagger UI hivatalos oldala}, title = {A Swagger UI hivatalos oldala},
url = {https://swagger.io/tools/swagger-ui/}, url = {https://swagger.io/tools/swagger-ui/},

View File

@ -33,8 +33,7 @@ Az egyik a \verb+User+, mely az alkalmazás felhasználóinak adatait tárolja.
A másik a \verb+Service+, mely a külső szolgáltatások adatainak tárolását szolgálja, amelyeket azért tárolok az adatbázisban és nem mondjuk a konfigurációs fájlban, A másik a \verb+Service+, mely a külső szolgáltatások adatainak tárolását szolgálja, amelyeket azért tárolok az adatbázisban és nem mondjuk a konfigurációs fájlban,
mert szerettem volna, hogyha a kezelőfelületen lehetne őket szerkeszteni, törölni. mert szerettem volna, hogyha a kezelőfelületen lehetne őket szerkeszteni, törölni.
\lstset{style=sharpc, morekeywords={record, get, set}} \begin{lstlisting}[style=csharp, caption=A User és a Service modell]
\begin{lstlisting}[caption=A User és a Service modell]
public record User public record User
{ {
public int Id { get; set; } public int Id { get; set; }
@ -51,7 +50,7 @@ mert szerettem volna, hogyha a kezelőfelületen lehetne őket szerkeszteni, tö
{ {
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public Uri Uri { get; set; } public Uri Url { get; set; }
public bool IsFromConfig { get; set; } public bool IsFromConfig { get; set; }
} }
@ -74,19 +73,23 @@ Majd hozzáadja az újonnan beolvasott értékeket.
\section{Üzleti logikai réteg} \section{Üzleti logikai réteg}
%---------------------------------------------------------------------------- %----------------------------------------------------------------------------
Ebben a rétegben található meg a szerver legtöbb szolgáltatása. It vannak implementálva a Birdnetes Command and Control és Input komponensekkel kommunikáló szolgáltatások is, Ebben a rétegben található meg a szerver legtöbb szolgáltatása. It vannak implementálva a Birdnetes Command and Control és Input komponensekkel kommunikáló szolgáltatások is,
melyeket azok OpenAPI leírói alapján az NSwag\cite{nswag} alkalmazással generáltam. Az OpenAPI a klienseken kívül definiálja még az azok által használt modelleket is. melyeket azok OpenAPI leírói alapján az NSwag Studio\cite{nswag-studio} alkalmazással generáltam. Az OpenAPI a klienseken kívül definiálja még az azok által használt modelleket is.
A Command and Control által használt \verb+Device+ modell tartalmazza annak egyedi azonosítóját, státuszát, koordinátáit és a használt szenzorok listáját, A Command and Control által használt \verb+Device+ modell tartalmazza annak egyedi azonosítóját, státuszát, koordinátáit és a használt szenzorok listáját,
melyeknek szintén van egy modellje \verb+Sensor+ néven. Ennek szintén van azonosítója és státusza. Az Input szolgáltatásnak is van saját modellje, melyeknek szintén van egy modellje \verb+Sensor+ néven. Ennek szintén van azonosítója és státusza. Az Input szolgáltatásnak is van saját modellje,
amely a hangüzenetek metaadatait reprezentálja. Többek között tartalmazza a kihelyezett eszköz egyedi azonosítóját és a hangüzenet keltének dátumát. amely a hangüzenetek metaadatait reprezentálja. Többek között tartalmazza a kihelyezett eszköz egyedi azonosítóját és a hangüzenet keltének dátumát.
Ugyan itt található meg a \verb+User+ és \verb+Service+ entitások létrehozásáért, olvasásáért, szerkesztéséért és törléséért felelős szolgáltatások is. Ugyan itt található meg a \verb+User+ és \verb+Service+ entitások létrehozásáért, olvasásáért, szerkesztéséért és törléséért felelős szolgáltatások is.
Valamint itt található még az autentikációért felelős szolgáltatás is. A felhasználók jelszavainak tárolására a HMAC (Hash-based Message Authentication Code) algorithmust, Valamint itt található még az autentikációért felelős szolgáltatás is. A felhasználók jelszavainak tárolására a HMAC (Hash-based Message Authentication Code) algorithmust,
pontosabban annak a \verb+HMACSHA512+\cite{hmacsha512} C\# implementációját használtam. pontosabban annak a \verb+HMACSHA512+\cite{hmacsha512} C\# implementációját használtam.
Minden jelszóhoz generálok egy egyedi kulcsot és azzal egy hash-t, majd ezeket tárolom a \verb+User+ modell \verb+PasswordSalt+ és \verb+PasswordHash+ mezőiben. Minden jelszóhoz generálok egy egyedi kulcsot és azzal egy hash-t, majd ezeket tárolom a \verb+User+ modell \verb+PasswordSalt+ és \verb+PasswordHash+ mezőiben.
Amikor egy felhasználó be akar jelentkezni először megvizsgálom, hogy egyáltalán létezik-e az adatbázisban az adott nevű felhasználó, Amikor egy felhasználó be akar jelentkezni először megvizsgálom, hogy egyáltalán létezik-e az adatbázisban az adott nevű felhasználó,
ha igen, akkor a megadott jelszóból az imént említett folyamattal generált kulcsot és hash-t összehasonlítom az adatbázisban tárolttal. ha igen, akkor a megadott jelszóból az imént említett folyamattal generált kulcsot és hash-t összehasonlítom az adatbázisban tárolttal.
Azért hasznos íly módon, és nem mondjuk egyszerű szöveges formában tárolni a felhasználók jelszavát, mert így a felhasználón kívül senki sem tudja, hogy mi volt az eredeti jelszava,
az algorithmus egyirányú volta miatt\footnotemark. Ha véletlenül rossz kezekbe kerülne az adatbázis tartalma, akkor sem fognak tudni bejeletkezni a felhasználók adataival.
\footnotetext{Generálni egyszerű és gyors. Visszafejteni közel lehetetlen.}
%---------------------------------------------------------------------------- %----------------------------------------------------------------------------
\subsection{Kommunikációs Szolgáltatások} \subsection{Kommunikációs Szolgáltatások}
%---------------------------------------------------------------------------- %----------------------------------------------------------------------------
@ -125,18 +128,17 @@ Meg lehet adni különböző szűrőket és kimeneteket, amellyel szelektálni l
Például az MQTT szolgáltalás napló bejegyzéseit a \ref{lst:nlog-config} lista alapján szűrtem. Például az MQTT szolgáltalás napló bejegyzéseit a \ref{lst:nlog-config} lista alapján szűrtem.
Minden \verb+Debug+ szintől nagyobb és \verb+Error+ szinttől kisebb bejegyzés, mely tartalmazza az \verb+Mqtt+ kulcsszót az \verb+mqttFile+ azonosítójú fájlba kerül. Minden \verb+Debug+ szintől nagyobb és \verb+Error+ szinttől kisebb bejegyzés, mely tartalmazza az \verb+Mqtt+ kulcsszót az \verb+mqttFile+ azonosítójú fájlba kerül.
\lstset{style=xml, morekeywords={targets, target, xsi:type, name, fileName, layout, rules, logger, name, minlevel, maxlevel, writeTo, final}} \begin{lstlisting}[style=xml, caption=Az NLog.config fájl egy részlete, label=lst:nlog-config]
\begin{lstlisting}[caption=Az NLog.config fájl egy részlete, label=lst:nlog-config]
<targets> <targets>
... ...
<target xsi:type="File" name="mqttFile" fileName="${basedir}Logs/birdmap-mqtt-${shortdate}.log" <target xsi:type="File" name="mqttFile" fileName="..." layout="..." />
layout="..." />
... ...
</targets> </targets>
<rules> <rules>
... ...
<logger name="*.*Mqtt*.*" minlevel="Trace" maxlevel="Warning" writeTo="mqttFile" final="true"/> <logger name="*.*Mqtt*.*" minlevel="Trace" maxlevel="Warning" writeTo="mqttFile"
final="true"/>
... ...
</rules> </rules>
\end{lstlisting} \end{lstlisting}
@ -146,7 +148,11 @@ Azaz, hogy egy kérés-t milyen sorrendben dolgozzák fel a regisztrált szolgá
A szerveroldali kivételkezelésre szánt szolgáltatás, az \verb+ExceptionHandlerMiddleware+ is itt van használva, A szerveroldali kivételkezelésre szánt szolgáltatás, az \verb+ExceptionHandlerMiddleware+ is itt van használva,
amely elkap minden kivételt, amit a csővezeték további részei dobtak és JSON formátumban visszaadja azokat a kliensnek. amely elkap minden kivételt, amit a csővezeték további részei dobtak és JSON formátumban visszaadja azokat a kliensnek.
Továbbá az NSwag\cite{nswag} szoftvercsomag segítségével regisztrálok egy szolgáltatást, %----------------------------------------------------------------------------
\subsection{Swagger}
\label{subsect:backend-swagger}
%----------------------------------------------------------------------------
Az NSwag\cite{nswag} szoftvercsomag segítségével regisztrálok egy szolgáltatást,
mely a szerveroldalon található kontrollereket felhasználva generál egy OpenAPI specifikációt és annak egy Swagger UI\cite{swagger-ui} felületet, mely a szerveroldalon található kontrollereket felhasználva generál egy OpenAPI specifikációt és annak egy Swagger UI\cite{swagger-ui} felületet,
ahol a végpontok kipróbálhatóak, tesztelhetőek kliensoldal nélkül is. ahol a végpontok kipróbálhatóak, tesztelhetőek kliensoldal nélkül is.
@ -161,14 +167,8 @@ ahol a végpontok kipróbálhatóak, tesztelhetőek kliensoldal nélkül is.
\subsection{Kontrollerek} \subsection{Kontrollerek}
%---------------------------------------------------------------------------- %----------------------------------------------------------------------------
A kontrollerek határozzák meg, hogy a szerveroldalon milyen végpontokat, milyen paraméterekkel lehet meghívni, ahhoz milyen jogosultságok kellenek. A kontrollerek határozzák meg, hogy a szerveroldalon milyen végpontokat, milyen paraméterekkel lehet meghívni, ahhoz milyen jogosultságok kellenek.
A jogosultságok kezelését a JSON Web Token-ekkel oldottam meg. A fejlasználó bejelentkezéskor kap egy ilyen token-t,
amelyben tárolom a hozzá tartozó szerepet. A \ref{lst:devices-controller}-as listában látszik, hogy hogyan használom ezeket a szerepeket.
A \verb+DevicesController+ végpontjait alapértelmezetten \verb+User+ és \verb+Admin+ jogosultságú felhasználó hívhatja, az "api/devices/online" végpontot azonban csak \verb+Admin+ jogosultságú.
Hasonló képpen oldottam meg ezt a többi kontrollernél is. A \verb+User+ felhasználók csak olyan végpontokat hívhat, mely kizárolag az állapotok olvasásával jár.
Az \verb+Admin+ felhasználók hívhatnak bármilyen végpontot.
\lstset{style=sharpc, morekeywords={record, async}} \begin{lstlisting}[style=csharp, caption=Az eszköz kontroller és annak "online" végpontja, label=lst:devices-controller]
\begin{lstlisting}[caption=Az eszköz kontroller és annak "online" végpontja, label=lst:devices-controller]
[Authorize(Roles = "User, Admin")] [Authorize(Roles = "User, Admin")]
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
@ -184,6 +184,42 @@ Az \verb+Admin+ felhasználók hívhatnak bármilyen végpontot.
} }
\end{lstlisting} \end{lstlisting}
Controllersw A jogosultságok kezelését a JSON Web Token-ekkel oldottam meg. A fejlasználó bejelentkezéskor kap egy ilyen token-t,
Dtos amelyben tárolom a hozzá tartozó szerepet. A \ref{lst:devices-controller}-as listában látszik, hogy hogyan használom ezeket a szerepeket.
mapper A \verb+DevicesController+ végpontjait alapértelmezetten \verb+User+ és \verb+Admin+ jogosultságú felhasználó hívhatja, az "api/devices/online" végpontot azonban csak \verb+Admin+ jogosultságú.
Hasonló képpen oldottam meg ezt a többi kontrollernél is. A \verb+User+ felhasználók csak olyan végpontokat hívhat, mely kizárolag az állapotok olvasásával jár.
Az \verb+Admin+ felhasználók hívhatnak bármilyen végpontot.
A szerveroldalon négy különböző kontroller található, melyek mindegyikének alapvető feladata az üzleti logikát megvalósító szolgáltatások használata, a működés naplózás,
illetve az imént említett végpontok authorizálása és kiszolgálása. Ezeken kívül a kontrollerek speciális feladata a következő:
\begin{itemize}
\item Az \textbf{AuthController} felel a felhasználók bejelentkezésének lebonyolításáért, a JSON Web Token elkészítéséért. Az \verb+[Authorize]+ helyett itt az \verb+[AllowAnonymous]+ attribútum van használva, mellyel azt lehet jelezni, hogy a végpont bejelentkezés nélkül is hívható.
\item A \textbf{ServiceController} felel az alkalmazás által használt külső szolgáltatások állapotának lekérdezhetőségéért. Ilyenek például a Birdnetes rendszer vagy az MQTT szolgáltatás állapota.
\item A \textbf{DevicesController} felel a Command and Control mikroszolgáltatással való kommunikáció megvalósításáért, illetve a SignalR használatáért. Ha egy felhasználó valamelyik végpontot használva változtat valamelyik eszköz állapotán, akkor a kontroller jelez erről a klienseknek.
\item A \textbf{LogController} felel azért, hogy az \verb+Admin+ jogosultságú felhasználók letölthessék a szerveroldalon készült naplófájlokat.
\end{itemize}
Az adatbázisból érkező adatok gyakran túl sok vagy túl kevés információt tartalmaznak ahhoz, hogy kiolvasás után rögtön elküldjem a kliensoldalnak.
Például amikor a felhasználó bejelentkezik a kiolvasott \verb+User+ objektum tartalmazza annak jelszavát (hash-elt formában), viszont nem tartalmazza az authorizációhoz használt token adatait.
Ennek a megoldására adatátviteli objektumokat hoztam létre, melyek csak azokat a mezőket tartalmazzák amelyekre a felhasználónak szüksége van.
Az adatbázisból kiolvasott objektum hasznos részeit és egyéb használni kívánt információt átmásolom az átviteli objektumba. Majd ezt küldöm el a kliensoldal felé.
Hogy az adatok másolását ne kézzel kelljen csinálnom, az AutoMapper\cite{automapper} szoftvercsomagot alkalmaztam, melynek használata rendkívül egyszerű.
Meg lehet adni profilokat, ahol két objektum közötti leképzéseket lehet felvenni. A szoftvercsomag automatikusan átmásolja az azonos nevű mezőket az egyik objektumból a másikba,
de meg lehet adni egyedi leképzéseket is.
\pagebreak
\begin{lstlisting}[style=csharp, caption=Egy példa az AutoMapper használatára.]
// Creating maps.
CreateMap<User, AuthenticateResponse>()
.ForMember(m => m.Username, opt => opt.MapFrom(m => m.Name))
.ForMember(m => m.UserRole, opt => opt.MapFrom(m => m.Role))
.ReverseMap();
CreateMap<Service, ServiceRequest>()
.ReverseMap();
// Using maps.
IMapper mapper = GetMapper();
User user = GetUserFromDb();
AuthenticateResponse response = mapper.Map<AuthenticateResponse>(user);
\end{lstlisting}

View File

@ -0,0 +1,289 @@
%----------------------------------------------------------------------------
\chapter{Kliens oldal}
\label{chapt:birdmap-frontend}
%----------------------------------------------------------------------------
Ebben a fejezetben bemutatom a kliensoldal architektúráját. Ismertetem a különböző komponensek felépítését.
%----------------------------------------------------------------------------
\section{Architektúra}
%----------------------------------------------------------------------------
Az alkalmazásnak minden oldala egy külön React komponens, mely mindegyikének saját mappája van a főkönyvtár alatt,
ahol az egyes oldalak által használt szolgáltatások és egyéb komponensek találhatóak.
A közöses használt szolgáltatások és komponensek a common mappába kerültek.
A kliensoldal belépési pontja az \verb+App.js+ fájlban található \verb+App+ komponens.
Itt egy React \verb+Switch+-ben fel van sorolva az összes oldal komponense azok elérési útvonalai szerint.
Ezt szemlélteti a \ref{lst:react-switch}-es lista.
Az a komponens jelenik meg a felületen, amelyiknek \verb+path+ mező értéke megegyezik az URL-ben találhatóval.
\begin{lstlisting}[style=jsx, caption=Az App.js Switch tartalma., label=lst:react-switch]
<Switch>
<PublicRoute exact path="/login" component={AuthComponent} />
<AdminRoute exact path="/logs" component={LogsComponent} />
<DevicesContextProvider>
<PrivateRoute exact path="/" component={DashboardComponent} />
<PrivateRoute exact path="/devices/:id?" component={DevicesComponent} />
<PrivateRoute exact path="/heatmap" component={HeatmapComponent} />
</DevicesContextProvider>
</Switch>
\end{lstlisting}
Hozzáférés szempontjából három fajta oldalt különböztetünk meg:
\begin{itemize}
\item \textbf{Publikus oldal}. Az oldal bejelentkezés nélkül is látogatható.
\item \textbf{Privát oldal}. Az oldal csak bejelentkezés után látogatható.
\item \textbf{Admin oldal}. Az oldalt csak bejelentkezett admin felhasználók látogathatják.
\end{itemize}
Ezek alapján készítettem két generikus komponenst. Az egyik a \verb+DefaultLayout+ komponens, mely az oldal alapértelmezett elrendezéséért felel.
Paraméterében át lehet adni egy másik megjeleníteni kívánt komponenst, melyet a fejléc alatt jelenít meg.
Mivel minden komponens ebbe az bázis komponensbe van csomagolva, így akárhova navigálunk az oldalon a felület mindig egységes marad.
A másik komponens a \verb+PredicateRoute+, melynek paraméterében meg lehet adni egy feltételt, illetve egy másik komponenst.
Ha a feltétel hamis akkor átírányítja a felhasználót a bejelentkező oldalra, ha igaz akkor megjeleníti a \verb+DefaultLayout+-ba csomagolt komponenst.
Publikus oldalnál a feltétel mindig igaz.
Privátnál a feltétel a bejelentkezéshez van kötve.
Az admin oldal feltétele egyrészt szintén a bejelentkezés, másrészt a felhasználó \verb+Admin+ jogolsultsága.
Ezt a folyamatot próbálja szemléltetni a \ref{fig:birdmap-frontend-architecture}-es ábra.
Legfelül sárgával vannak feltüntetve a hívható végpontok, alattuk a hozzájuk kapcsolt megjelenítendő komponensek, azok alatt pedig a hozzáférést szabályozó komponensek.
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/birdmap-frontend-routes.png}
\caption{A Birdmap kliensoldalának architektúrája}
\label{fig:birdmap-frontend-architecture}
\end{figure}
%----------------------------------------------------------------------------
\section{Kommunikáció a szerveroldallal}
%----------------------------------------------------------------------------
A szerveroldallal való kommunikációt rendkívül egyszerűen tudtam implementálni köszönhetően a \ref{subsect:backend-swagger}-as fejezetben bemutatott Swagger oldalnak
és annak, hogy az NSwag Studio-val\cite{nswag-studio} a C\#-on kívül lehet TypeScript\footnotemark klienseket is generálni a leíró fájlból.
Így készültek el a kommponensek kommunikációért felelős szolgáltatásai.
\footnotetext{JavaScript-re épített statikus típusdefiníciókat tartalmazó nyelv. JavaScript és TypeScript együtt is használható.}
%----------------------------------------------------------------------------
\section{Komponensek}
%----------------------------------------------------------------------------
Ebben a szakaszban ismertete az egyes oldalak komponenseit és azok alkomponenseit,
illetve a navigációért felelős fejlécet.
%----------------------------------------------------------------------------
\subsection{Navigáció}
%----------------------------------------------------------------------------
A fejléc két komponensből áll. Az egyik az oldal címe a másik az oldalak linkjeit tartalmazó komponens.
Utóbbit a React \verb+NavLink+ komponenseivel készítettem, melyeknek meg lehet adni, hogy kattintásra hova irányítsa a felhasználót.
Ha a jelenlegi webcím tartalmazza a linknek megadott címet, akkor az aktív státuszba kerül, melyre külön stílus osztályok vonatkoznak.
Ezt használva, az aktív linkeket egy aláhúzással jelölöm.
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/appbar-user-admin.png}
\caption{A Birdmap fejléce. Felül a User, alul az Admin felhasználóké}
\label{fig:birdmap-appbar}
\end{figure}
A fejléc alapértelmezetten része a \verb+DefaultLayout+ komponensnek, így minden oldalon megjelenítésre kerül.
%----------------------------------------------------------------------------
\subsection{Login}
%----------------------------------------------------------------------------
A bejelentkező oldal viszonylag egyszerű. Két szövegdobozt és egy bejelentkező gombot tartalmaz, ahogy az a \ref{fig:birdmap-login}-as ábrán is látszik.
\begin{figure}[!ht]
\centering
\includegraphics[width=60mm, keepaspectratio]{figures/birdmap-login.png}
\caption{A Birdmap bejelentkező felülete}
\label{fig:birdmap-login}
\end{figure}
A generált szerverrel kommunikáló szolgáltatás be van csomagolva egy közösen használt másik szolgáltatásba.
Ennek célja, hogy a bejelentkezés eredményét több komponens is olvashassa, hiszen az alkalmazás felületét alapvetően megkülönbözteti,
egyrészt a bejelentkezés sikeressége, másrészt a bejelentkezett felhasználó jogosultsági köre.
Sikeres bejelentkezés után a szerver elküldi a felhasználó szerepét, illetve a hozzáférési token-t, amelyre a kliens többi szolgáltatásának is szüksége lesz a kommunkációhoz.
Ezeket az oldal \verb+sessionStorage+-ában\footnotemark tárolom és a becsomagolt szolgáltatáson keresztül elérhetőek.
Kijelentkezni a navigációs fejlécben található profil ikonra való kattintással lehet.
\footnotetext{Webtárhely objektum. Lehetővé teszi a kulcs-érték párok tárolását a böngészőben.}
%----------------------------------------------------------------------------
\subsection{Logs}
%----------------------------------------------------------------------------
Ez az oldal az \verb+Admin+ felhasználó számára lehetővé teszi a szerveren található naplófájlok letöltését \verb+zip+ fájlformátumú archív fájlokban.
Komponense a \ref{fig:birdmap-logs}-es ábrán látható.
\begin{figure}[!ht]
\centering
\includegraphics[width=75mm, keepaspectratio]{figures/birdmap-logs.png}
\caption{A Birdmap naplófájlok letöltésének felülete}
\label{fig:birdmap-logs}
\end{figure}
%----------------------------------------------------------------------------
\subsection{Eszköz állapot és hangüzenet kezelő szolgáltatás}
%----------------------------------------------------------------------------
A szakasz további komponenseinek van egy közös ismertetője. Mégpedig, hogy mindegyiknek szüksége van a kihelyezett eszközök adataira
és az azok által publikált hangüzenetekből képzett valószínüségre.
A Reactnek van egy beépített komponense \verb+Context+\cite{react-context} néven, mellyel különböző komponensek között lehet adatokat megosztani.
Ezt használva készítettem egy \verb+DevicesContextProvider+ osztályt, melynek feladata a szerver eszköz kontrollerével való kommunikáció a megfelelő szolgáltatáson keresztül,
illetve a SignalR csatornákra való feliratkozás. Ezekből az adatokból egy \verb+DevicesContext+ készül, mely a \verb+Provider+ által átadásra kerül annak minden gyerekének.
A \ref{lst:react-switch}-es listában látható, hogy a \verb+DevicesContextProvider+ szülője a \verb+Dashboard+, \verb+Devices+ és \verb+Heatmap+ komponenseknek.
%----------------------------------------------------------------------------
\subsection{Dashboard}
%----------------------------------------------------------------------------
A Dashboard az alkalmazás kezdő oldala. Itt található meg a külső szolgáltatások állapotát vizsgáló komponens,
illetve a kihelyezett eszközök működési folyamatában áttekintést nyújtó diagrammok mindegyike.
Az oldal megjelenítésekor elindul egy másodpercenként ismétlődő folyamat,
mely a \verb+DevicesContext+-ből kiolvasott értékekből legenerálja a diagrammokon megjelenítendő összes adatot.
Ez azonban az adat mennyiségétől függően akár egy-két másodpercig is eltarthat, ami rendkívül lassúvá és használhatatlanná tenné a felületet.
Ennek elkerülése érdekében az adatfeldolgozó folyamat egyszerre csak egy pár elemet dolgoz fel, mely alfolyamatok között 20 milliszekundum szüneteket iktattam be.
Továbbá hogy a különböző diagrammok animációi is zökkenőmentesek legyenek, azok adatai cserélése között is van 300 milliszekundum szünet.
Így valamivel lasabb az adatfeldolgozás, de a felület használható marad.
%----------------------------------------------------------------------------
\subsubsection{Külső szolgáltatások}
%----------------------------------------------------------------------------
Az alkalmazás használatának szempontjából van néhány olyan külső szolgáltatás, melyek elérhetősége hiányában a rendszer működésképtelen.
Ilyen például a Birdnetes klasztere vagy a szerver MQTT szolgáltatása.
Ezért készítettem el az \ref{fig:dashboard-services-loaded}-ös ábrán látható információs panelt, ahol a szolgáltatások állapotát lehet látni, hogy a felhasználó tudja miért nem működik esetleg az alkalmazás.
A felület megvalósításhoz a Material UI \verb+Accordion+ elemét használtam, ami lényegében egy lenyíló lista.
Ennek fejlécében a szolgáltatás neve, elérési útvonala és státusza látható. A lenyíló elemben a szolgáltatástól érkezett válasz van megjelenítve.
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/dashboard-services-loaded.png}
\caption{Az alkalmazás által használt külső komponensek állapotának megjelenítéséért felelős komponens}
\label{fig:dashboard-services-loaded}
\end{figure}
Az oldal betöltése vagy a frissítés gomb megnyomása esetén az adatok lekérésre kerülnek a szervertől.
Ez a folyamat akár öt-hat másodpercig is eltarthat, mely közben a felhasználó egy üres listát látna.
Ennek elkerülésére használom a Material UI \verb+Skeleton+ komponensét,
mely egy megadható méretű töltő csíkkal helyettesíti az \verb+Accordion+-ban található elemeket a \ref{fig:dashboard-services-loading}-os ábrán látható módon.
Azért célszerű ennek a használata, mert így a felhasználónak több információja van arról, hogy a felületen milyen adatok és hol fognak megjelenni.
A felhasználói élmény maximalizálása érdekében a frissítés előtt lekérdezem a szervertől, hogy hány darab szolgáltatás található az adatbázisban
és annyi darab töltőcsíkos \verb+Accordion+-t jelenítek meg.
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/dashboard-services-loading.png}
\caption{A Skeletonok alkalmazása a külső szolgáltatások állapotának betöltése közben.}
\label{fig:dashboard-services-loading}
\end{figure}
%----------------------------------------------------------------------------
\subsubsection{Eszközök és szenzorok állapota}
%----------------------------------------------------------------------------
Ennek a komponensnek a szerepe, hogy áttekintést nyújtson az eszközök és szenzorok állapotáról.
Úgy gondoltam, hogy erre a legcélravezetőbb eszköz a \ref{fig:dashboard-donut}-es ábrán is látható Apexcharts fánk diagrammja.
Látható, hogy hány darab eszköz és szenzor van bekapcsolt, kikapcsolt, illetve hibás állapotban.
Az állapotok változása esetén a \verb+DevicesContextProvider+-nek köszönhetően az adatok automatikusan frissülnek.
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/dashboard-donut-devices.png}
\caption{A Dashboard eszköz- és szenzor állapotok diagrammja}
\label{fig:dashboard-donut}
\end{figure}
%----------------------------------------------------------------------------
\subsubsection{Hőtérkép diagrammok}
%----------------------------------------------------------------------------
Ezekkel a diagrammokkal az a célom, hogy az eszközök által küldött észleléseket időrendben vizualizáljam.
Megvalósításukhoz az Apexcharts Heatmap típusú diagrammját használtam.
A \ref{fig:dashboard-heatmap-second}-as ábrán látható diagram az elmúlt egy percben küldött, másodpercenként a legnagyobb, hangüzenetekből képzett valószínűségeket ábrozolja.
A \ref{fig:dashboard-heatmap-minute}-es ábrán látható diagram pedig az elmúlt egy órában percenként a legnagyobbakat.
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/second-heatmap.png}
\caption{Másodperc alapú hőtérképes diagramm}
\label{fig:dashboard-heatmap-second}
\end{figure}
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/minute-heatmap.png}
\caption{Perc alapú hőtérképes diagramm}
\label{fig:dashboard-heatmap-minute}
\end{figure}
A függőleges tengelyen a rendszer eszközei vannak dinamikusan megjelenítve.
A vízszintes tengelyen pedig az említett időtartományok.
A diagrammokon látható négyzetek a valószínüség nagyságától függően sötétebbek vagy világosabbak.
\newpage
%----------------------------------------------------------------------------
\subsubsection{Riasztás számláló}
%----------------------------------------------------------------------------
Ez egy egyszerű oszlopdiagram, mely aggregálja az egyes eszközök által küldött hangüzeneteket 0.5 valószínűség felett a \ref{fig:dashboard-devices-column}-es ábrán látható módon.
Segítségével megvizsgálható, hogy mely eszközök riasztanak a legtöbbet a legnagyobb valószínűséggel.
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/dashboard-column-devices.png}
\caption{Eszközönkénti riasztásokat számláló diagramm}
\label{fig:dashboard-devices-column}
\end{figure}
Az egyes oszlopok három részre vannak bontva az üzenetek öt tized, hét tized és kilenc tized fölötti valószínűsége szerint.
\newpage
%----------------------------------------------------------------------------
\subsubsection{Üzenetek gyakorisága}
%----------------------------------------------------------------------------
Az oldalon található utolsó diagramm egy vonal diagammn, melynek célja, hogy ábrázolja a rendszer által küldött üzenetek számát másodpercenként.
A \ref{fig:dashboard-messages-line}-es ábrán látható a komponens.
A vízszintes tengelyen a legelső érték az alkalmazás által először észlelt üzenet időpontja.
Az utolsó érték a legutoljára észlelt időpontja.
A függőleges tengelyen az adott másodpercben érkező üzenetek száma van ábrázolva.
Az előzőekkel ellentétben itt az adatok nincsennek szűrve a hangüzenet valószínűsége alapján,
tehát a rendszer által küldött összes üzenet látható.
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/dashboard-line-messages.png}
\caption{A másodpercenként érkező üzenetek számát ábrázoló diagram.}
\label{fig:dashboard-messages-line}
\end{figure}
\newpage
%----------------------------------------------------------------------------
\subsection{Devices}
%----------------------------------------------------------------------------
Ez az oldal lehetővé teszi a felhasználók számára az eszközök állapotának áttekintését, \verb+Admin+ felhasználók számára azok menedszelését is.
Az eszközök dinamikusan jelennek meg a \verb+DevicesContextProvider+ adatai alapján, melyek megjelenítésére a Material UI \verb+Accrordion+ komponensét használom.
Ennek fejlécében az eszköz neve, egyedi azonosítója és státusza található. A lenyíló részben pedig az eszköz által használt szenzorok neve, azonosítója és státusza.
\verb+Admin+ felhasználók számára a felület két fajta gombbal bővül, mellyekkel be és ki lehet kapcsolni az egyes eszközöket, szenzorokat.
Az \verb+Accordion+-ok felett található egy külön panel, mellyel egyszerre lehet kezelni az összes eszközt és azok szenzorait.
A Devices oldal felülete a \ref{fig:frontend-devices}-es ábrán,
az \verb+Admin+ felhasználók számára nyújtott plusz funkciók a \ref{fig:frontend-devices-admin}-as ábrán láthatók.
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/devices.png}
\caption{A Devices oldal felülete.}
\label{fig:frontend-devices}
\end{figure}
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/devices-admin.png}
\caption{Az Admin felhasználók számára elérhető plusz funkciók.}
\label{fig:frontend-devices-admin}
\end{figure}
%----------------------------------------------------------------------------
\subsection{Heatmap}
%----------------------------------------------------------------------------
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/heatmap.png}
\caption{A Heatmap oldal felülete.}
\label{fig:frontend-heatmap}
\end{figure}
- felépítés architektúra
- App and navigation
- Components and services
- kép minden felületről
- kép user és admin felhasználók különbségéről
- Device service
- dashboard systeminfo service
- kép a skeletonról -> igazi adat

View File

@ -46,9 +46,7 @@ A \ref{fig:grafana}-es ábra egy jó példa arra, hogy hogyan néz ki egy által
\subsection{Kibana} \subsection{Kibana}
%---------------------------------------------------------------------------- %----------------------------------------------------------------------------
A Kibana\cite{kibana} jelentősen hasonlít a Grafanához, azonban amíg a utóbbit inkább az időben változó metrikák vizualizálására használják például processzor leterheltség vagy memória használat, A Kibana\cite{kibana} jelentősen hasonlít a Grafanához, azonban amíg a utóbbit inkább az időben változó metrikák vizualizálására használják például processzor leterheltség vagy memória használat,
addig az előbbit elsődlegesen az Elasticsearch\footenotemark adatok, főként napló bejegyzések, analizálására használják. addig az előbbit elsődlegesen az Elasticsearch\footnote{Ingyenes és nyílt forráskódú index alapú keresőmotor} adatok, főként napló bejegyzések, analizálására használják.
\footnotetext{Ingyenes és nyílt forráskódú index alapú keresőmotor}
\begin{figure}[!ht] \begin{figure}[!ht]
\centering \centering

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -32,7 +32,6 @@
\usepackage[unicode]{hyperref} % For hyperlinks in the generated document. \usepackage[unicode]{hyperref} % For hyperlinks in the generated document.
\usepackage{xcolor} \usepackage{xcolor}
\usepackage{listings} % For source code snippets. \usepackage{listings} % For source code snippets.
\lstdefinestyle{sharpc}{language=[Sharp]C, frame=lr, rulecolor=\color{blue!80!black}}
\usepackage[amsmath,thmmarks]{ntheorem} % Theorem-like environments. \usepackage[amsmath,thmmarks]{ntheorem} % Theorem-like environments.

View File

@ -42,32 +42,127 @@
%-------------------------------------------------------------------------------------- %--------------------------------------------------------------------------------------
% Set up listings % Set up listings
%-------------------------------------------------------------------------------------- %--------------------------------------------------------------------------------------
\definecolor{red}{rgb}{0.6,0,0}
\definecolor{blue}{rgb}{0,0,0.6}
\definecolor{lightblue}{rgb}{0,0.5,0.7}
\definecolor{green}{rgb}{0,0.8,0}
\definecolor{cyan}{rgb}{0.0,0.6,0.6}
\definecolor{orange}{rgb}{0.8,0.6,0}
\definecolor{white}{rgb}{0.98, 0.98, 0.98}
\definecolor{lightgray}{rgb}{0.95,0.95,0.95} \definecolor{lightgray}{rgb}{0.95,0.95,0.95}
\lstset{ \definecolor{darkviolet}{rgb}{0.58, 0.0, 0.83}
\definecolor{royalblue}{rgb}{0.25, 0.41, 0.88}
\lstdefinestyle{csharp}{
language=csh,
basicstyle=\scriptsize\ttfamily,
numbers=left,
numberstyle=\tiny,
numbersep=5pt,
tabsize=2,
extendedchars=true,
breaklines=true,
frame=b,
stringstyle=\color{red}\ttfamily,
showspaces=false,
captionpos=b,
showtabs=false,
xleftmargin=17pt,
framexleftmargin=17pt,
framexrightmargin=5pt,
framexbottommargin=4pt,
commentstyle=\color{green},
morecomment=[l]{//}, %use comment-line-style!
morecomment=[s]{/*}{*/}, %for multiline comments
showstringspaces=false,
morekeywords={ abstract, event, new, struct,
as, explicit, null, switch,
base, extern, object, this,
get, set,
bool, false, operator, throw,
break, finally, out, true,
byte, fixed, override, try,
case, float, params, typeof,
catch, for, private, uint,
char, foreach, protected, ulong,
checked, goto, public, unchecked,
class, record, async, if, readonly, unsafe,
const, implicit, ref, ushort,
continue, in, return, using,
decimal, int, sbyte, virtual,
default, interface, sealed, volatile,
delegate, internal, short, void,
do, is, sizeof, while,
double, lock, stackalloc,
else, long, static,
enum, namespace, string},
morekeywords=[2]{CreateMap, ForMember, MapFrom, ReverseMap, Onlineall, GetMapper, GetUserFromDb, Map},
morekeywords=[3]{Id, Name, PasswordHash, PasswordSalt, Role, IsFromConfig, Url, Username, UserRole, mapper, user, response},
keywordstyle=\color{blue},
keywordstyle={[2]\color{orange}},
keywordstyle={[3]\color{lightblue}},
identifierstyle=\color{cyan},
backgroundcolor=\color{white}
}
\lstdefinestyle{jsx}{
basicstyle=\scriptsize\ttfamily,
numbers=left,
numberstyle=\tiny,
numbersep=5pt,
tabsize=2,
extendedchars=true,
breaklines=true,
frame=b,
stringstyle=\color{red}\ttfamily,
showspaces=false,
captionpos=b,
showtabs=false,
xleftmargin=17pt,
framexleftmargin=17pt,
framexrightmargin=5pt,
framexbottommargin=4pt,
commentstyle=\color{green},
morestring=*[d]{"},
morecomment=[l]{//}, %use comment-line-style!
morecomment=[s]{/*}{*/}, %for multiline comments
showstringspaces=false,
morekeywords={ },
morekeywords=[2]{AuthComponent, LogsComponent, DashboardComponent, DevicesComponent, HeatmapComponent},
morekeywords=[3]{exact, path, component},
keywordstyle=\color{blue},
keywordstyle={[2]\color{orange}},
keywordstyle={[3]\color{lightblue}},
identifierstyle=\color{cyan},
backgroundcolor=\color{white}
}
\lstdefinestyle{xml}{
basicstyle=\scriptsize\ttfamily, % print whole listing small basicstyle=\scriptsize\ttfamily, % print whole listing small
keywordstyle=\color{black}\bfseries, % bold black keywords
identifierstyle=, % nothing happens
% default behavior: comments in italic, to change use % default behavior: comments in italic, to change use
% commentstyle=\color{green}, % for e.g. green comments % commentstyle=\color{green}, % for e.g. green comments
stringstyle=\scriptsize,
showstringspaces=false, % no special string spaces showstringspaces=false, % no special string spaces
aboveskip=3pt, aboveskip=3pt,
belowskip=3pt, belowskip=3pt,
backgroundcolor=\color{lightgray}, stringstyle=\color{red}\ttfamily,
backgroundcolor=\color{white},
keywordstyle=\color{blue},
identifierstyle=\color{cyan},
columns=flexible, columns=flexible,
keepspaces=true, keepspaces=true,
escapeinside={(*@}{@*)}, escapeinside={(*@}{@*)},
captionpos=b, captionpos=b,
breaklines=true, breaklines=true,
frame=single, frame=b,
float=!ht, float=!ht,
tabsize=2, tabsize=2,
morestring=*[d]{"},
morekeywords={targets, target, rules, logger},
literate=* literate=*
{á}{{\'a}}1 {é}{{\'e}}1 {í}{{\'i}}1 {ó}{{\'o}}1 {ö}{{\"o}}1 {ő}{{\H{o}}}1 {ú}{{\'u}}1 {ü}{{\"u}}1 {ű}{{\H{u}}}1 {á}{{\'a}}1 {é}{{\'e}}1 {í}{{\'i}}1 {ó}{{\'o}}1 {ö}{{\"o}}1 {ő}{{\H{o}}}1 {ú}{{\'u}}1 {ü}{{\"u}}1 {ű}{{\H{u}}}1
{Á}{{\'A}}1 {É}{{\'E}}1 {Í}{{\'I}}1 {Ó}{{\'O}}1 {Ö}{{\"O}}1 {Ő}{{\H{O}}}1 {Ú}{{\'U}}1 {Ü}{{\"U}}1 {Ű}{{\H{U}}}1 {Á}{{\'A}}1 {É}{{\'E}}1 {Í}{{\'I}}1 {Ó}{{\'O}}1 {Ö}{{\"O}}1 {Ő}{{\H{O}}}1 {Ú}{{\'U}}1 {Ü}{{\"U}}1 {Ű}{{\H{U}}}1
} }
%-------------------------------------------------------------------------------------- %--------------------------------------------------------------------------------------
% Set up theorem-like environments % Set up theorem-like environments
%-------------------------------------------------------------------------------------- %--------------------------------------------------------------------------------------

File diff suppressed because one or more lines are too long