27 Commits

Author SHA1 Message Date
a458ad1712 Added birdnetes introduction 2020-11-27 15:37:25 +01:00
030b259d32 Added introduction 0.1 2020-11-26 19:44:53 +01:00
022852b163 Added abtract 0.4 2020-11-26 17:33:49 +01:00
2d5eca233e Added abtract 0.3 2020-11-26 16:28:33 +01:00
9d6e0cd453 Added abtract 0.2 2020-11-26 16:24:02 +01:00
a2c3112f81 Added abtract 0.1 2020-11-26 15:35:03 +01:00
c7e3fcabcf Added thesis base, and ideas.txt 2020-11-26 12:15:43 +01:00
70c4c91035 Fixed LogService baseUrl 2020-11-26 10:11:49 +01:00
aa39597541 Modified log folder path 2020-11-26 10:01:47 +01:00
bcbab1383a Added Logs navigation 2020-11-26 09:35:50 +01:00
bab0984c30 Added logdownloader 2020-11-25 14:10:47 +01:00
0667c6ec39 Added environment specific appsettings 2020-11-25 12:22:51 +01:00
0d71899ce1 Fixed docker-compose issues 2020-11-25 12:16:14 +01:00
966d8bd79e Some changes 2020-11-23 13:31:09 +01:00
5b42ce9f43 Multiple configuration modifications
Added Enviroment variable support
Addes baseUrl variable for live services
Included default env variables in docker-compose.yml
Updated sql and nodejs versions
2020-11-23 10:50:10 +01:00
85320d3cf3 Added dockerfile, added compose 2020-11-23 09:23:05 +01:00
9d55c39e33 Added Mqtt messages buffer to help unload frontend 2020-11-22 10:39:51 +01:00
d75e9d378d Added further optimazation 2020-11-22 09:56:32 +01:00
3cdaa2dc35 Increased timeout for ui update 2020-11-21 20:14:30 +01:00
9af0ba1bb8 Best optimization so far 2020-11-21 19:42:14 +01:00
04c27560ea Added timeout processing 2020-11-21 19:18:29 +01:00
73157520ab Optimized chart update (did not achieve much) 2020-11-21 18:32:12 +01:00
f862e4b8da Added all charts (pre optimization) 2020-11-21 18:10:36 +01:00
f85346aea9 Added charts 2020-11-21 17:40:05 +01:00
1d438bc349 Modified dashboard 2020-11-21 10:38:52 +01:00
86999cd646 Moved service components to services folder 2020-11-21 10:22:11 +01:00
8979ad6db3 Added dashboard services
Added GetCount endpoint
Added ServiceInfo Skeletons
2020-11-19 20:43:01 +01:00
94 changed files with 7403 additions and 266 deletions

25
.dockerignore Normal file
View File

@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/docs
**/bin
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

View File

@ -8,6 +8,9 @@
<SpaRoot>ClientApp\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<AssemblyName>Birdmap.API</AssemblyName>
<UserSecretsId>a919c854-b332-49ee-8e38-96549f828836</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@ -31,6 +34,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.9" />
<PackageReference Include="MQTTnet" Version="3.0.13" />
<PackageReference Include="MQTTnet.AspNetCore" Version="3.0.13" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
@ -52,6 +56,7 @@
<None Remove="ClientApp\src\components\auth\Auth.tsx" />
<None Remove="ClientApp\src\components\auth\AuthClient.ts" />
<None Remove="ClientApp\src\components\auth\AuthService.ts" />
<None Remove="ClientApp\src\components\dashboard\ServiceInfoService.ts" />
<None Remove="ClientApp\src\components\devices\DeviceService.ts" />
</ItemGroup>
@ -61,7 +66,6 @@
<ItemGroup>
<Folder Include="ClientApp\src\common\components\" />
<Folder Include="ClientApp\src\components\dashboard\" />
</ItemGroup>
<ItemGroup>

View File

@ -1610,6 +1610,25 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
},
"@rollup/plugin-babel": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.2.1.tgz",
"integrity": "sha512-Jd7oqFR2dzZJ3NWANDyBjwTtX/lYbZpVcmkHrfQcpvawHs9E4c0nYk5U2mfZ6I/DZcIvy506KZJi54XK/jxH7A==",
"requires": {
"@babel/helper-module-imports": "^7.10.4",
"@rollup/pluginutils": "^3.1.0"
}
},
"@rollup/pluginutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
"requires": {
"@types/estree": "0.0.39",
"estree-walker": "^1.0.1",
"picomatch": "^2.2.2"
}
},
"@svgr/babel-plugin-add-jsx-attribute": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz",
@ -1761,6 +1780,11 @@
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
"integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag=="
},
"@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="
},
"@types/glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
@ -2356,6 +2380,20 @@
"normalize-path": "^2.1.1"
}
},
"apexcharts": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.22.2.tgz",
"integrity": "sha512-pR+cmApk7dhfYILBpe8RVb+FdLfVCt/RDWvAJO1F5feeSQ8lKDgFkRuVu9KOeEarHVXjUpnhLqHNMx7YaprK8A==",
"requires": {
"@rollup/plugin-babel": "^5.2.1",
"svg.draggable.js": "^2.2.2",
"svg.easing.js": "^2.0.0",
"svg.filter.js": "^2.0.2",
"svg.pathmorphing.js": "^0.1.3",
"svg.resize.js": "^1.4.3",
"svg.select.js": "^3.0.1"
}
},
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
@ -5566,6 +5604,11 @@
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="
},
"estree-walker": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="
},
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@ -10799,6 +10842,14 @@
"prop-types": "^15.6.2"
}
},
"react-apexcharts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.3.7.tgz",
"integrity": "sha512-2OFhEHd70/WHN0kmrJtVx37UfaL71ZogVkwezmDqwQWgwhK6upuhlnEEX7tEq4xvjA+RFDn6hiUTNIuC/Q7Zqw==",
"requires": {
"prop-types": "^15.5.7"
}
},
"react-app-polyfill": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz",
@ -13066,6 +13117,70 @@
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ=="
},
"svg.draggable.js": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
"requires": {
"svg.js": "^2.0.1"
}
},
"svg.easing.js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
"integrity": "sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=",
"requires": {
"svg.js": ">=2.3.x"
}
},
"svg.filter.js": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
"integrity": "sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=",
"requires": {
"svg.js": "^2.2.5"
}
},
"svg.js": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA=="
},
"svg.pathmorphing.js": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
"requires": {
"svg.js": "^2.4.0"
}
},
"svg.resize.js": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
"requires": {
"svg.js": "^2.6.5",
"svg.select.js": "^2.1.2"
},
"dependencies": {
"svg.select.js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
"requires": {
"svg.js": "^2.2.5"
}
}
}
},
"svg.select.js": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
"requires": {
"svg.js": "^2.6.5"
}
},
"svgo": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.0.tgz",

View File

@ -7,6 +7,7 @@
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.56",
"@microsoft/signalr": "^5.0.0",
"apexcharts": "^3.22.2",
"bootstrap": "^4.3.1",
"connected-react-router": "6.5.2",
"google-map-react": "^2.1.9",
@ -17,6 +18,7 @@
"merge": "1.2.1",
"popper.js": "^1.16.0",
"react": "^16.11.0",
"react-apexcharts": "^1.3.7",
"react-dom": "16.11.0",
"react-google-maps": "^9.4.5",
"react-redux": "7.1.1",

View File

@ -1,22 +1,17 @@
import { Box, Container, IconButton, Menu, MenuItem, MenuList, Paper, Grow, Popper } from '@material-ui/core';
import AccountCircle from '@material-ui/icons/AccountCircle';
import AppBar from '@material-ui/core/AppBar';
import { positions } from '@material-ui/system';
import { Box, Paper } from '@material-ui/core';
import { blueGrey, grey, orange } from '@material-ui/core/colors';
import { createMuiTheme, createStyles, makeStyles, Theme } from '@material-ui/core/styles';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import { ThemeProvider } from '@material-ui/styles';
import React, { useState, } from 'react';
import { BrowserRouter, NavLink, Redirect, Route, Switch, Link } from 'react-router-dom';
import BirdmapTitle from './components/appBar/BirdmapTitle';
import React, { useState } from 'react';
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom';
import BirdmapBar from './components/appBar/BirdmapBar';
import Auth from './components/auth/Auth';
import AuthService from './components/auth/AuthService';
import { ClickAwayListener } from '@material-ui/core';
import MapContainer from './components/heatmap/Heatmap';
import Dashboard from './components/dashboard/Dashboard';
import Devices from './components/devices/Devices';
import { blueGrey, blue, orange, grey } from '@material-ui/core/colors';
import DevicesContextProvider from './contexts/DevicesContextProvider'
import MapContainer from './components/heatmap/Heatmap';
import Logs from './components/logs/Logs';
import DevicesContextProvider from './contexts/DevicesContextProvider';
const theme = createMuiTheme({
palette: {
@ -25,14 +20,13 @@ const theme = createMuiTheme({
dark: grey[400],
},
secondary: {
main: orange[200],
main: blueGrey[700],
dark: blueGrey[50],
}
},
});
function App() {
const [authenticated, setAuthenticated] = useState(AuthService.isAuthenticated());
const [isAdmin, setIsAdmin] = useState(AuthService.isAdmin());
@ -47,14 +41,19 @@ function App() {
);
}
const LogsComponent = () => {
return <Logs/>
}
const DashboardComponent = () => {
return <Link to="/devices/5">This is a link</Link>;
return <Dashboard isAdmin={isAdmin}/>;
};
const DevicesComponent = () => {
return <Devices isAdmin={isAdmin}/>;
};
const HeatmapComponent = () => {
return (
<Paper elevation={0}>
@ -63,15 +62,46 @@ function App() {
);
};
const HeaderComponent = () => {
return (
<BirdmapBar onLogout={AuthService.logout} isAdmin={isAdmin} isAuthenticated={authenticated}/>
);
}
const PredicateRoute = ({ component: Component, predicate: Predicate, ...rest }: { [x: string]: any, component: any, predicate: any }) => {
return (
<PredicateRouteInternal {...rest} header={HeaderComponent} body={Component} predicate={Predicate}/>
);
}
const PublicRoute = ({ component: Component, ...rest }: { [x: string]: any, component: any }) => {
return (
<PredicateRoute {...rest} component={Component} predicate={true}/>
);
}
const PrivateRoute = ({ component: Component, ...rest }: { [x: string]: any, component: any }) => {
return (
<PredicateRoute {...rest} component={Component} predicate={authenticated}/>
);
}
const AdminRoute = ({ component: Component, ...rest }: { [x: string]: any, component: any }) => {
return (
<PredicateRoute {...rest} component={Component} predicate={authenticated && isAdmin}/>
);
}
return (
<ThemeProvider theme={theme}>
<BrowserRouter>
<Switch>
<PublicRoute path="/login" component={AuthComponent} />
<PublicRoute exact path="/login" component={AuthComponent} />
<AdminRoute exact path="/logs" component={LogsComponent} />
<DevicesContextProvider>
<PrivateRoute path="/" exact authenticated={authenticated} component={DashboardComponent} />
<PrivateRoute path="/devices/:id?" exact authenticated={authenticated} component={DevicesComponent} />
<PrivateRoute path="/heatmap" exact authenticated={authenticated} component={HeatmapComponent} />
<PrivateRoute exact path="/" component={DashboardComponent} />
<PrivateRoute exact path="/devices/:id?" component={DevicesComponent} />
<PrivateRoute exact path="/heatmap" component={HeatmapComponent} />
</DevicesContextProvider>
</Switch>
</BrowserRouter>
@ -81,112 +111,26 @@ function App() {
export default App;
const PublicRoute = ({ component: Component, ...rest }: { [x: string]: any, component: any}) => {
const PredicateRouteInternal = ({ header: HeaderComponent, body: BodyComponent, predicate: Predicate, ...rest }: { [x: string]: any, header: any, body: any, predicate: any }) => {
return (
<Route {...rest} render={matchProps => (
<DefaultLayout component={Component} authenticated={false} isAdmin={false} {...matchProps} />
)} />
);
}
const PrivateRoute = ({ component: Component, authenticated: Authenticated, ...rest }: { [x: string]: any, component: any, authenticated: any }) => {
return (
<Route {...rest} render={matchProps => (
Authenticated
? <DefaultLayout component={Component} authenticated={Authenticated} {...matchProps} />
Predicate
? <DefaultLayoutInternal header={HeaderComponent} body={BodyComponent} {...matchProps} />
: <Redirect to='/login' />
)} />
);
};
const DefaultLayout = ({ component: Component, authenticated: Authenticated, ...rest }: { [x: string]: any, component: any, authenticated: any }) => {
const DefaultLayoutInternal = ({ header: HeaderComponent, body: BodyComponent, ...rest }: { [x: string]: any, header: any, body: any }) => {
const classes = useDefaultLayoutStyles();
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef<HTMLButtonElement>(null);
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event: React.MouseEvent<EventTarget>) => {
if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) {
return;
}
setOpen(false);
};
const handleLogout = (event: React.MouseEvent<EventTarget>) => {
if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) {
return;
}
AuthService.logout();
setOpen(false);
};
function handleListKeyDown(event: React.KeyboardEvent) {
if (event.key === 'Tab') {
event.preventDefault();
setOpen(false);
}
}
const prevOpen = React.useRef(open);
React.useEffect(() => {
if (prevOpen.current === true && open === false) {
anchorRef.current!.focus();
}
prevOpen.current = open;
}, [open]);
const renderNavLinks = () => {
return Authenticated
? <Container className={classes.nav_menu}>
<NavLink exact to="/" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Dashboard</NavLink>
<NavLink to="/devices" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Devices</NavLink>
<NavLink exact to="/heatmap" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Heatmap</NavLink>
<IconButton className={classes.nav_menu_icon}
ref={anchorRef}
aria-haspopup="true"
aria-controls={open ? 'menu-list-grow' : undefined}
aria-label="account of current user"
onClick={handleToggle}>
<AccountCircle/>
</IconButton>
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
<MenuItem onClick={handleLogout} component={Link} {...{ to: '/login' }}>Logout</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</Container>
: null;
};
return (
<React.Fragment>
<AppBar position="static" className={classes.bar_root}>
<Toolbar>
<BirdmapTitle />
<Typography component={'span'} className={classes.typo}>
{renderNavLinks()}
</Typography>
</Toolbar>
</AppBar>
<Box zIndex="modal" className={classes.box_root}>
<Component {...rest} />
<Box className={classes.header}>
<HeaderComponent />
</Box>
<Box className={classes.body}>
<BodyComponent {...rest} />
</Box>
</React.Fragment>
);
@ -194,46 +138,12 @@ const DefaultLayout = ({ component: Component, authenticated: Authenticated, ...
const useDefaultLayoutStyles = makeStyles((theme: Theme) =>
createStyles({
bar_root: {
header: {
height: '7%',
},
box_root: {
body: {
backgroundColor: theme.palette.primary.dark,
height: '93%',
},
typo: {
marginLeft: 'auto',
color: 'white',
},
nav_menu: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
nav_menu_icon: {
color: 'inherit',
marginLeft: '24px',
'&:hover': {
color: 'inherit',
}
},
nav_menu_item: {
textDecoration: 'none',
fontWeight: 'normal',
color: 'inherit',
marginLeft: '24px',
'&:hover': {
color: 'inherit',
}
},
nav_menu_item_active: {
textDecoration: 'underline',
fontWeight: 'bold',
color: 'inherit',
marginLeft: '24px',
'&:hover': {
color: 'inherit',
}
},
}
}),
);

View File

@ -1,5 +1,5 @@
export default {
probability_method_name: 'NotifyDeviceAsync',
probability_method_name: 'NotifyMessagesAsync',
update_method_name: 'NotifyDeviceUpdatedAsync',
update_all_method_name: 'NotifyAllUpdatedAsync',
};

View File

@ -0,0 +1,136 @@
import { ClickAwayListener, Container, createStyles, Grow, IconButton, makeStyles, MenuItem, MenuList, Paper, Popper, Theme } from '@material-ui/core';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import AccountCircle from '@material-ui/icons/AccountCircle';
import React from 'react';
import { Link, NavLink } from 'react-router-dom';
import BirdmapTitle from './BirdmapTitle';
export default function BirdmapBar(props: { onLogout: () => void; isAuthenticated: any; isAdmin: any; }) {
const classes = useAppbarStyles();
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef<HTMLButtonElement>(null);
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event: React.MouseEvent<EventTarget>) => {
if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) {
return;
}
setOpen(false);
};
const handleLogout = (event: React.MouseEvent<EventTarget>) => {
if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) {
return;
}
props.onLogout();
setOpen(false);
};
function handleListKeyDown(event: React.KeyboardEvent) {
if (event.key === 'Tab') {
event.preventDefault();
setOpen(false);
}
}
const prevOpen = React.useRef(open);
React.useEffect(() => {
if (prevOpen.current === true && open === false) {
anchorRef.current!.focus();
}
prevOpen.current = open;
}, [open]);
const renderNavLinks = () => {
return props.isAuthenticated
? <Container className={classes.nav_menu}>
<NavLink exact to="/" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Dashboard</NavLink>
{props.isAdmin ? <NavLink exact to="/logs" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Logs</NavLink> : null}
<NavLink to="/devices" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Devices</NavLink>
<NavLink exact to="/heatmap" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Heatmap</NavLink>
<IconButton className={classes.nav_menu_icon}
ref={anchorRef}
aria-haspopup="true"
aria-controls={open ? 'menu-list-grow' : undefined}
aria-label="account of current user"
onClick={handleToggle}>
<AccountCircle />
</IconButton>
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
<MenuItem onClick={handleLogout} component={Link} {...{ to: '/login' }}>Logout</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</Container>
: null;
};
return (
<AppBar position="static">
<Toolbar>
<BirdmapTitle />
<Typography component={'span'} className={classes.typo}>
{renderNavLinks()}
</Typography>
</Toolbar>
</AppBar>
)
};
const useAppbarStyles = makeStyles((theme: Theme) =>
createStyles({
typo: {
marginLeft: 'auto',
color: 'white',
},
nav_menu: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
nav_menu_icon: {
color: 'inherit',
marginLeft: '24px',
'&:hover': {
color: 'inherit',
}
},
nav_menu_item: {
textDecoration: 'none',
fontWeight: 'normal',
color: 'inherit',
marginLeft: '24px',
'&:hover': {
color: 'inherit',
}
},
nav_menu_item_active: {
textDecoration: 'underline',
fontWeight: 'bold',
color: 'inherit',
marginLeft: '24px',
'&:hover': {
color: 'inherit',
}
},
}),
);

View File

@ -0,0 +1,328 @@
import React, { Component } from 'react';
import { withStyles } from '@material-ui/styles';
import Services from './services/Services';
import { blueGrey } from '@material-ui/core/colors';
import { Box, Grid, IconButton, Paper, Typography } from '@material-ui/core';
import DonutChart from './charts/DonutChart';
import HeatmapChart from './charts/HeatmapChart';
import BarChart from './charts/BarChart';
import LineChart from './charts/LineChart';
import DevicesContext from '../../contexts/DevicesContext';
import C from '../../common/Constants';
const styles = theme => ({
root: {
flexGrow: 1,
padding: '64px',
backgroundColor: theme.palette.primary.dark,
},
typo: {
fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular,
},
paper: {
backgroundColor: blueGrey[50],
padding: '16px',
}
});
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
deviceSeries: [],
sensorSeries: [],
heatmapSecondsSeries: [],
heatmapMinutesSeries: [],
barSeries: [],
barCategories: [],
lineSeries: [],
};
this.updateSeries = this.updateSeries.bind(this);
this.updateDynamic = this.updateDynamic.bind(this);
this.performTask = this.performTask.bind(this);
}
static contextType = DevicesContext;
componentDidMount() {
this.context.addHandler(C.update_all_method_name, this.updateSeries);
this.context.addHandler(C.update_method_name, this.updateSeries);
this.updateSeries();
this.updateDynamic();
}
componentWillUnmount() {
this.context.removeHandler(C.update_all_method_name, this.updateSeries);
this.context.removeHandler(C.update_method_name, this.updateSeries);
if (this.updateTimer) {
clearTimeout(this.updateTimer);
}
}
getItemsWithStatus(iterate, status) {
const items = [];
for (var d of iterate) {
if (d.status == status) {
items.push(d);
}
}
return items;
}
getDevicesWithStatus(status) {
return this.getItemsWithStatus(this.context.devices, status);
}
getSensorsWithStatus(status) {
const sensors = [];
for (var d of this.context.devices) {
sensors.push(...d.sensors)
}
return this.getItemsWithStatus(sensors, status);
}
getDeviceSeries() {
var online = this.getDevicesWithStatus("Online").length;
var offline = this.getDevicesWithStatus("Offline").length;
var error = this.getDevicesWithStatus("Error").length;
return [online, offline, error]
}
getSensorSeries() {
var online = this.getSensorsWithStatus("Online").length;
var offline = this.getSensorsWithStatus("Offline").length;
var unknown = this.getSensorsWithStatus("Unknown").length;
return [online, offline, unknown]
}
updateSeries() {
this.setState({
deviceSeries: this.getDeviceSeries(),
sensorSeries: this.getSensorSeries()
});
}
updateDynamic = () => {
const secondAgo = new Date();
secondAgo.setMilliseconds(0);
const minuteAgo = new Date(Date.now() - 1000 * 60);
const hourAgo = new Date(Date.now() - 1000 * 60 * 60);
const minuteDevicePoints = {};
const hourDevicePoints = {};
const barDevicePoints = {};
const linePoints = {};
for (var d of this.context.devices) {
minuteDevicePoints[d.id] = Array(60).fill(0);
hourDevicePoints[d.id] = Array(60).fill(0);
barDevicePoints[d.id] = Array(3).fill(0);
}
const processHeatmapItem = (items, index) => {
const p = items[index];
if (p.date > minuteAgo) {
var seconds = Math.floor((p.date.getTime() - minuteAgo.getTime()) / 1000);
var oldProb = minuteDevicePoints[p.deviceId][seconds];
if (oldProb < p.prob) {
minuteDevicePoints[p.deviceId][seconds] = p.prob;
}
}
if (p.date > hourAgo) {
var minutes = Math.floor((p.date.getTime() - hourAgo.getTime()) / (1000 * 60));
var oldProb = hourDevicePoints[p.deviceId][minutes];
if (oldProb < p.prob) {
hourDevicePoints[p.deviceId][minutes] = p.prob;
}
}
if (p.prob > 0.5 && p.prob <= 0.7) {
barDevicePoints[p.deviceId][0] += 1;
}
if (p.prob > 0.7 && p.prob <= 0.9) {
barDevicePoints[p.deviceId][1] += 1;
}
if (p.prob > 0.9) {
barDevicePoints[p.deviceId][2] += 1;
}
if (p.date < secondAgo) {
var shortDate = p.date.toUTCString();
var point = linePoints[shortDate];
if (point === undefined) {
linePoints[shortDate] = 1;
} else {
linePoints[shortDate] += 1;
}
}
}
const onFinished = () => {
const minuteHeatmapSeries = [];
var i = 0;
for (var p in minuteDevicePoints) {
minuteHeatmapSeries.push({
name: "Device " + i,
data: minuteDevicePoints[p].map((value, index) => ({
x: new Date(Date.now() - (60 - index) * 1000).toLocaleTimeString('hu-HU'),
y: value
})),
});
i++;
};
const hourHeatmapSeries = [];
var i = 0;
for (var p in hourDevicePoints) {
hourHeatmapSeries.push({
name: "Device " + i,
data: hourDevicePoints[p].map((value, index) => ({
x: new Date(Date.now() - (60 - index) * 1000 * 60).toLocaleTimeString('hu-HU').substring(0, 5),
y: value
})),
});
i++;
};
const barSeries = [];
const getCount = column => {
var counts = [];
for (var p in barDevicePoints) {
counts.unshift(barDevicePoints[p][column]);
}
return counts;
};
barSeries.push({
name: "Prob > 0.5",
data: getCount(0),
});
barSeries.push({
name: "Prob > 0.7",
data: getCount(1),
});
barSeries.push({
name: "Prob > 0.9",
data: getCount(2),
});
const lineSeries = [{ name: "message/sec", data: [] }];
for (var m in linePoints) {
lineSeries[0].data.push({
x: new Date(m).getTime(),
y: linePoints[m],
})
}
const getBarCategories = () => {
const categories = [];
for (var i = this.context.devices.length - 1; i >= 0; i--) {
categories.push("Device " + i)
}
return categories;
}
const toUpdate = [
{ heatmapSecondsSeries: minuteHeatmapSeries },
{ heatmapMinutesSeries: hourHeatmapSeries },
{ barSeries: barSeries },
{ barCategories: getBarCategories() },
{ lineSeries: lineSeries }
];
//Set states must be done separately otherwise ApexChart's UI update freezes the page.
this.performTask(toUpdate, 2, 300, (list, index) => {
this.setState(list[index]);
},
() => {
this.updateTimer = setTimeout(this.updateDynamic, 1000);
});
}
this.performTask(this.context.heatmapPoints, Math.ceil(this.context.heatmapPoints.length / 50), 20,
processHeatmapItem, onFinished);
}
performTask(items, numToProcess, wait, processItem, onFinished) {
var pos = 0;
// This is run once for every numToProcess items.
function iteration() {
// Calculate last position.
var j = Math.min(pos + numToProcess, items.length);
// Start at current position and loop to last position.
for (var i = pos; i < j; i++) {
processItem(items, i);
}
// Increment current position.
pos += numToProcess;
// Only continue if there are more items to process.
if (pos < items.length)
setTimeout(iteration, wait); // Wait 10 ms to let the UI update.
else
onFinished();
}
iteration();
}
render() {
const { classes } = this.props;
return (
<Box className={classes.root}>
<Grid container spacing={3}>
<Grid item xs={12}>
<Services isAdmin={this.props.isAdmin} />
</Grid>
<Grid item xs={6}>
<Paper className={classes.paper}>
<DonutChart totalLabel="Devices" series={this.state.deviceSeries} />
</Paper>
</Grid>
<Grid item xs={6}>
<Paper className={classes.paper}>
<DonutChart totalLabel="Sensors" series={this.state.sensorSeries} />
</Paper>
</Grid>
<Grid item xs={12}>
<Paper className={classes.paper}>
<HeatmapChart label="Highest probability per second by devices" series={this.state.heatmapSecondsSeries} />
</Paper>
</Grid>
<Grid item xs={12}>
<Paper className={classes.paper}>
<HeatmapChart label="Highest probability per minute by devices" series={this.state.heatmapMinutesSeries} />
</Paper>
</Grid>
<Grid item xs={6}>
<Paper className={classes.paper}>
<BarChart label="# of messages by devices" series={this.state.barSeries} categories={this.state.barCategories} />
</Paper>
</Grid>
<Grid item xs={6}>
<Paper className={classes.paper}>
<LineChart label="# of messages per second" series={this.state.lineSeries} />
</Paper>
</Grid>
</Grid>
</Box>
);
}
}
export default withStyles(styles)(Dashboard);

View File

@ -0,0 +1,92 @@
import React, { Component } from 'react';
import Chart from 'react-apexcharts';
import { blueGrey, green, red, orange, amber } from '@material-ui/core/colors';
export class BarChart extends Component {
constructor(props) {
super(props)
this.state = {
options: {},
};
}
componentDidUpdate(prevProps) {
if (prevProps.categories !== this.props.categories) {
this.setState({options: {
chart: {
stacked: true,
animations: {
enabled: true,
easing: 'linear',
speed: 250,
animateGradually: {
enabled: false,
},
dynamicAnimation: {
enabled: true,
speed: 250
}
},
},
plotOptions: {
bar: {
horizontal: true,
},
},
colors: [blueGrey[500], blueGrey[700], blueGrey[900]],
stroke: {
width: 1,
colors: ['#fff']
},
title: {
text: this.props.label,
style: {
fontSize: '22px',
fontWeight: 600,
fontFamily: 'Helvetica, Arial, sans-serif',
},
},
xaxis: {
categories: this.props.categories,
labels: {
formatter: function (val) {
return val;
}
}
},
yaxis: {
title: {
text: undefined
},
},
tooltip: {
y: {
formatter: function (val) {
return val;
}
}
},
fill: {
opacity: 1
},
legend: {
position: 'top',
}
}});
}
}
render() {
return (
<Chart
options={this.state.options}
series={this.props.series}
type="bar"
height={600}
/>
)
}
}
export default BarChart;

View File

@ -0,0 +1,67 @@
import React, { Component } from 'react';
import Chart from 'react-apexcharts';
import { blueGrey, green, red } from '@material-ui/core/colors';
export class DonutChart extends Component {
constructor(props) {
super(props);
this.state = {
options: {
legend: {
fontSize: '18px',
},
plotOptions: {
pie: {
startAngle: 0,
expandOnClick: false,
offsetX: 0,
offsetY: 0,
customScale: 1,
dataLabels: {
offset: 0,
minAngleToShowLabel: 10
},
donut: {
size: '65%',
background: 'transparent',
labels: {
show: true,
total: {
show: true,
showAlways: true,
label: props.totalLabel,
fontSize: '22px',
fontFamily: 'Helvetica, Arial, sans-serif',
fontWeight: 600,
color: '#373d3f',
formatter: function (w) {
return w.globals.seriesTotals.reduce((a, b) => {
return a + b
}, 0)
}
}
}
},
}
},
dataLabels: {
enabled: false
},
colors: [green[500], blueGrey[500], red[500]],
labels: ['Online', 'Offline', 'Error / Unknown']},
}
}
render() {
return (
<Chart
options={this.state.options}
series={this.props.series}
type="donut"/>
)
}
}
export default DonutChart;

View File

@ -0,0 +1,55 @@
import React, { Component } from 'react';
import Chart from 'react-apexcharts';
import { blueGrey, green, red } from '@material-ui/core/colors';
export class HeatmapChart extends Component {
constructor(props) {
super(props)
this.state = {
options: {
chart: {
animations: {
enabled: true,
easing: 'linear',
speed: 250,
animateGradually: {
enabled: false,
speed: 250,
},
dynamicAnimation: {
enabled: true,
speed: 250
}
}
},
dataLabels: {
enabled: false
},
colors: [blueGrey[900]],
title: {
text: props.label,
style: {
fontSize: '22px',
fontWeight: 600,
fontFamily: 'Helvetica, Arial, sans-serif',
},
},
},
}
}
render() {
return (
<Chart
options={this.state.options}
series={this.props.series}
type="heatmap"
height={600}
/>
)
}
}
export default HeatmapChart

View File

@ -0,0 +1,74 @@
import React, { Component } from 'react';
import Chart from 'react-apexcharts';
import { blueGrey, green, red } from '@material-ui/core/colors';
export class LineChart extends Component {
constructor(props) {
super(props)
this.state = {
options: {
chart: {
animations: {
enabled: true,
easing: 'linear',
speed: 250,
animateGradually: {
enabled: false,
},
dynamicAnimation: {
enabled: true,
speed: 250
}
},
zoom: {
enabled: false
}
},
colors: [blueGrey[900]],
dataLabels: {
enabled: false
},
stroke: {
curve: 'straight'
},
title: {
text: this.props.label,
align: 'left',
style: {
fontSize: '22px',
fontWeight: 600,
fontFamily: 'Helvetica, Arial, sans-serif',
},
},
grid: {
row: {
colors: ['#f3f3f3', 'transparent'], // takes an array which will be repeated on columns
opacity: 0.5
},
},
xaxis: {
type: 'datetime',
labels: {
formatter: function (val) {
return new Date(val).toLocaleTimeString('hu-HU');
}
}
}
},
}
}
render() {
return (
<Chart
options={this.state.options}
series={this.props.series}
type="line"
height={600}
/>
)
}
}
export default LineChart

View File

@ -0,0 +1,61 @@
import { TextField } from '@material-ui/core';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import React, { useState } from 'react';
export default function AddNewDialog(props) {
const [name, setName] = useState("");
const [url, setUrl] = useState("");
const onNameChange = (event) => {
setName(event.target.value);
}
const onUrlChange = (event) => {
setUrl(event.target.value);
}
return (
<div>
<Dialog
open={props.open}
onClose={props.handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{"Add new service."}</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
id="name"
label="Name"
type="text"
onChange={onNameChange}
fullWidth
/>
<TextField
autoFocus
margin="dense"
id="url"
label="Url"
type="text"
onChange={onUrlChange}
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={props.handleClose} color="primary">
Cancel
</Button>
<Button onClick={() => props.handleAdd(name, url)} color="primary" autoFocus>
Add
</Button>
</DialogActions>
</Dialog>
</div>
);
}

View File

@ -0,0 +1,35 @@
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import React from 'react';
export default function DeleteDialog(props) {
return (
<div>
<Dialog
open={props.open}
onClose={() => props.handleClose(false)}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{"Are you sure?"}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Deleting is permament.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => props.handleClose(false)} color="primary">
Cancel
</Button>
<Button onClick={() => props.handleClose(true)} color="primary" autoFocus>
OK
</Button>
</DialogActions>
</Dialog>
</div>
);
}

View File

@ -0,0 +1,234 @@
import { Box, FormControlLabel, Grid, IconButton, Paper, TextField, Typography } from '@material-ui/core';
import Accordion from '@material-ui/core/Accordion';
import AccordionDetails from '@material-ui/core/AccordionDetails';
import AccordionSummary from '@material-ui/core/AccordionSummary';
import { blueGrey, green, red } from '@material-ui/core/colors';
import { CancelRounded, CheckCircleRounded, Delete, Edit } from '@material-ui/icons/';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { withStyles } from '@material-ui/styles';
import React, { Component } from 'react';
import DeleteDialog from './DeleteDialog';
const styles = theme => ({
root: {
flexGrow: 1,
padding: '64px',
backgroundColor: theme.palette.primary.dark,
},
acc_summary: {
backgroundColor: blueGrey[50],
padding: theme.spacing(2),
textAlign: 'center',
height: '75px',
},
acc_details: {
backgroundColor: blueGrey[100],
},
grid_typo: {
fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular,
},
grid_typo_2: {
marginLeft: '5px',
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
color: theme.palette.text.secondary,
},
typo: {
fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular,
},
icon_box: {
marginLeft: '30px',
},
paper: {
backgroundColor: blueGrey[50],
padding: theme.spacing(2),
textAlign: 'center',
height: '75px',
}
});
class ServiceInfoComponent extends Component {
constructor(props) {
super(props);
this.state = {
isDialogOpen: false,
isEditing: false,
name: "",
url: "",
}
this.handleDialogClose = this.handleDialogClose.bind(this);
this.onNameChange = this.onNameChange.bind(this);
this.onUrlChange = this.onUrlChange.bind(this);
this.handleSaveCancel = this.handleSaveCancel.bind(this);
}
componentDidMount() {
this.setState({ name: this.props.info.service.name, url: this.props.info.service.uri });
if (this.props.isEditing !== undefined) {
this.setState({ isEditing: this.props.isEditing });
}
}
onNameChange(event) {
this.setState({ name: event.target.value });
}
onUrlChange(event) {
this.setState({ url: event.target.value });
}
getColor(status) {
if (status === "OK")
return { color: green[600] };
else
return { color: red[600] };
}
handleDialogClose(result) {
this.setState({ isDialogOpen: false });
if (result === true) {
this.props.service.delete(this.props.info.service.id);
}
}
handleEditCancel(value) {
this.setState({ isEditing: value });
}
handleSaveCancel() {
let request = {
...this.props.info.service
};
request.name = this.state.name;
request.uri = this.state.url;
if (request.id > 0) {
this.props.service.put(request).catch(ex => {
console.log(ex);
});
}
else {
this.props.service.post(request).catch(ex => {
console.log(ex);
});
}
this.setState({ isEditing: false });
}
renderSaveCancel() {
return (
<Box styles={{ marginLeft: 'auto' }}>
<IconButton color="primary" onClick={this.handleSaveCancel}>
<CheckCircleRounded fontSize="large" />
</IconButton>
<IconButton color="primary" onClick={() => this.setState({ isEditing: false })}>
<CancelRounded fontSize="large" />
</IconButton>
</Box>
);
}
renderButtons() {
const renderEditDelete = () => {
return (
<React.Fragment>
<DeleteDialog open={this.state.isDialogOpen} handleClose={this.handleDialogClose}/>
<IconButton color="primary" onClick={() => this.handleEditCancel(true)}>
<Edit fontSize="large"/>
</IconButton>
<IconButton style={{color: red[600]}} onClick={() => this.setState({ isDialogOpen: true })}>
<Delete fontSize="large"/>
</IconButton>
</React.Fragment>
);
}
const { classes } = this.props;
return (
<Box className={classes.icon_box}>
{this.props.isAdmin && this.props.info.service.name !== "Mqtt Client Service" ? renderEditDelete() : null}
</Box>
);
}
render() {
const { classes } = this.props;
const renderAccordion = () => {
return (
<Accordion>
<AccordionSummary className={classes.acc_summary}
expandIcon={<ExpandMoreIcon />}
aria-controls={"device-panel-/" + this.props.info.service.name}
id={"device-panel-/" + this.props.info.service.name}>
<Grid container
spacing={1}
direction="row"
justify="flex-start"
alignItems="center">
<Grid item>
<Typography className={classes.grid_typo}>{this.props.info.service.name}</Typography>
</Grid>
<Grid item>
<Typography className={classes.grid_typo_2}>{this.props.info.service.uri}</Typography>
</Grid>
<Grid item style={{ marginLeft: 'auto' }}>
<Grid container
spacing={1}
direction="row"
justify="flex-start"
alignItems="center">
<Grid item>
<FormControlLabel
onClick={(event) => event.stopPropagation()}
onFocus={(event) => event.stopPropagation()}
control={this.renderButtons()} />
</Grid>
<Grid item>
<Typography style={this.getColor(this.props.info.statusCode)}>Status: <b>{this.props.info.statusCode}</b></Typography>
</Grid>
</Grid>
</Grid>
</Grid>
</AccordionSummary>
<AccordionDetails className={classes.acc_details}>
{this.props.info.response}
</AccordionDetails>
</Accordion>
);
};
const renderTextFields = () => {
return (
<Paper className={classes.acc_summary}>
<Grid container
spacing={1}
direction="row"
justify="flex-start"
alignItems="center">
<Grid item xs>
<TextField label="Name" type="text" defaultValue={this.props.info.service.name} onChange={this.onNameChange} />
</Grid>
<Grid item xs={6}>
<TextField label="Url" type="text" fullWidth defaultValue={this.props.info.service.uri} onChange={this.onUrlChange}/>
</Grid>
<Grid item xs>
{this.renderSaveCancel()}
</Grid>
</Grid>
</Paper>
);
};
return this.state.isEditing ? renderTextFields() : renderAccordion();
}
}
export default withStyles(styles)(ServiceInfoComponent);

View File

@ -0,0 +1,386 @@
"use strict";
/* tslint:disable */
/* eslint-disable */
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v13.8.2.0 (NJsonSchema v10.2.1.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
// ReSharper disable InconsistentNaming
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiException = exports.HttpStatusCode = exports.ServiceRequest = exports.ServiceInfo = void 0;
var ServiceInfoService = /** @class */ (function () {
function ServiceInfoService(baseUrl, http) {
this.jsonParseReviver = undefined;
this.http = http ? http : window;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "";
}
ServiceInfoService.prototype.getCount = function () {
var _this = this;
var url_ = this.baseUrl + "/api/Services/count";
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "GET",
headers: {
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processGetCount(_response);
});
};
ServiceInfoService.prototype.processGetCount = function (response) {
var _this = this;
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 200) {
return response.text().then(function (_responseText) {
var result200 = null;
var resultData200 = _responseText === "" ? null : JSON.parse(_responseText, _this.jsonParseReviver);
result200 = resultData200 !== undefined ? resultData200 : null;
return result200;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
ServiceInfoService.prototype.get = function () {
var _this = this;
var url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "GET",
headers: {
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processGet(_response);
});
};
ServiceInfoService.prototype.processGet = function (response) {
var _this = this;
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 200) {
return response.text().then(function (_responseText) {
var result200 = null;
var resultData200 = _responseText === "" ? null : JSON.parse(_responseText, _this.jsonParseReviver);
if (Array.isArray(resultData200)) {
result200 = [];
for (var _i = 0, resultData200_1 = resultData200; _i < resultData200_1.length; _i++) {
var item = resultData200_1[_i];
result200.push(ServiceInfo.fromJS(item));
}
}
return result200;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
ServiceInfoService.prototype.post = function (request) {
var _this = this;
var url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, "");
var content_ = JSON.stringify(request);
var options_ = {
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processPost(_response);
});
};
ServiceInfoService.prototype.processPost = function (response) {
var _this = this;
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 201) {
return response.text().then(function (_responseText) {
var result201 = null;
var resultData201 = _responseText === "" ? null : JSON.parse(_responseText, _this.jsonParseReviver);
result201 = ServiceRequest.fromJS(resultData201);
return result201;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
ServiceInfoService.prototype.put = function (request) {
var _this = this;
var url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, "");
var content_ = JSON.stringify(request);
var options_ = {
body: content_,
method: "PUT",
headers: {
"Content-Type": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processPut(_response);
});
};
ServiceInfoService.prototype.processPut = function (response) {
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 204) {
return response.text().then(function (_responseText) {
return;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
ServiceInfoService.prototype.delete = function (id) {
var _this = this;
var url_ = this.baseUrl + "/api/Services/{id}";
if (id === undefined || id === null)
throw new Error("The parameter 'id' must be defined.");
url_ = url_.replace("{id}", encodeURIComponent("" + id));
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "DELETE",
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processDelete(_response);
});
};
ServiceInfoService.prototype.processDelete = function (response) {
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 204) {
return response.text().then(function (_responseText) {
return;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
return ServiceInfoService;
}());
exports.default = ServiceInfoService;
var ServiceInfo = /** @class */ (function () {
function ServiceInfo(data) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
this[property] = data[property];
}
}
}
ServiceInfo.prototype.init = function (_data) {
if (_data) {
this.service = _data["service"] ? ServiceRequest.fromJS(_data["service"]) : undefined;
this.statusCode = _data["statusCode"];
this.response = _data["response"];
}
};
ServiceInfo.fromJS = function (data) {
data = typeof data === 'object' ? data : {};
var result = new ServiceInfo();
result.init(data);
return result;
};
ServiceInfo.prototype.toJSON = function (data) {
data = typeof data === 'object' ? data : {};
data["service"] = this.service ? this.service.toJSON() : undefined;
data["statusCode"] = this.statusCode;
data["response"] = this.response;
return data;
};
return ServiceInfo;
}());
exports.ServiceInfo = ServiceInfo;
var ServiceRequest = /** @class */ (function () {
function ServiceRequest(data) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
this[property] = data[property];
}
}
}
ServiceRequest.prototype.init = function (_data) {
if (_data) {
this.id = _data["id"];
this.name = _data["name"];
this.uri = _data["uri"];
}
};
ServiceRequest.fromJS = function (data) {
data = typeof data === 'object' ? data : {};
var result = new ServiceRequest();
result.init(data);
return result;
};
ServiceRequest.prototype.toJSON = function (data) {
data = typeof data === 'object' ? data : {};
data["id"] = this.id;
data["name"] = this.name;
data["uri"] = this.uri;
return data;
};
return ServiceRequest;
}());
exports.ServiceRequest = ServiceRequest;
var HttpStatusCode;
(function (HttpStatusCode) {
HttpStatusCode["Continue"] = "Continue";
HttpStatusCode["SwitchingProtocols"] = "SwitchingProtocols";
HttpStatusCode["Processing"] = "Processing";
HttpStatusCode["EarlyHints"] = "EarlyHints";
HttpStatusCode["OK"] = "OK";
HttpStatusCode["Created"] = "Created";
HttpStatusCode["Accepted"] = "Accepted";
HttpStatusCode["NonAuthoritativeInformation"] = "NonAuthoritativeInformation";
HttpStatusCode["NoContent"] = "NoContent";
HttpStatusCode["ResetContent"] = "ResetContent";
HttpStatusCode["PartialContent"] = "PartialContent";
HttpStatusCode["MultiStatus"] = "MultiStatus";
HttpStatusCode["AlreadyReported"] = "AlreadyReported";
HttpStatusCode["IMUsed"] = "IMUsed";
HttpStatusCode["MultipleChoices"] = "Ambiguous";
HttpStatusCode["Ambiguous"] = "Ambiguous";
HttpStatusCode["MovedPermanently"] = "Moved";
HttpStatusCode["Moved"] = "Moved";
HttpStatusCode["Found"] = "Redirect";
HttpStatusCode["Redirect"] = "Redirect";
HttpStatusCode["SeeOther"] = "RedirectMethod";
HttpStatusCode["RedirectMethod"] = "RedirectMethod";
HttpStatusCode["NotModified"] = "NotModified";
HttpStatusCode["UseProxy"] = "UseProxy";
HttpStatusCode["Unused"] = "Unused";
HttpStatusCode["TemporaryRedirect"] = "TemporaryRedirect";
HttpStatusCode["RedirectKeepVerb"] = "TemporaryRedirect";
HttpStatusCode["PermanentRedirect"] = "PermanentRedirect";
HttpStatusCode["BadRequest"] = "BadRequest";
HttpStatusCode["Unauthorized"] = "Unauthorized";
HttpStatusCode["PaymentRequired"] = "PaymentRequired";
HttpStatusCode["Forbidden"] = "Forbidden";
HttpStatusCode["NotFound"] = "NotFound";
HttpStatusCode["MethodNotAllowed"] = "MethodNotAllowed";
HttpStatusCode["NotAcceptable"] = "NotAcceptable";
HttpStatusCode["ProxyAuthenticationRequired"] = "ProxyAuthenticationRequired";
HttpStatusCode["RequestTimeout"] = "RequestTimeout";
HttpStatusCode["Conflict"] = "Conflict";
HttpStatusCode["Gone"] = "Gone";
HttpStatusCode["LengthRequired"] = "LengthRequired";
HttpStatusCode["PreconditionFailed"] = "PreconditionFailed";
HttpStatusCode["RequestEntityTooLarge"] = "RequestEntityTooLarge";
HttpStatusCode["RequestUriTooLong"] = "RequestUriTooLong";
HttpStatusCode["UnsupportedMediaType"] = "UnsupportedMediaType";
HttpStatusCode["RequestedRangeNotSatisfiable"] = "RequestedRangeNotSatisfiable";
HttpStatusCode["ExpectationFailed"] = "ExpectationFailed";
HttpStatusCode["MisdirectedRequest"] = "MisdirectedRequest";
HttpStatusCode["UnprocessableEntity"] = "UnprocessableEntity";
HttpStatusCode["Locked"] = "Locked";
HttpStatusCode["FailedDependency"] = "FailedDependency";
HttpStatusCode["UpgradeRequired"] = "UpgradeRequired";
HttpStatusCode["PreconditionRequired"] = "PreconditionRequired";
HttpStatusCode["TooManyRequests"] = "TooManyRequests";
HttpStatusCode["RequestHeaderFieldsTooLarge"] = "RequestHeaderFieldsTooLarge";
HttpStatusCode["UnavailableForLegalReasons"] = "UnavailableForLegalReasons";
HttpStatusCode["InternalServerError"] = "InternalServerError";
HttpStatusCode["NotImplemented"] = "NotImplemented";
HttpStatusCode["BadGateway"] = "BadGateway";
HttpStatusCode["ServiceUnavailable"] = "ServiceUnavailable";
HttpStatusCode["GatewayTimeout"] = "GatewayTimeout";
HttpStatusCode["HttpVersionNotSupported"] = "HttpVersionNotSupported";
HttpStatusCode["VariantAlsoNegotiates"] = "VariantAlsoNegotiates";
HttpStatusCode["InsufficientStorage"] = "InsufficientStorage";
HttpStatusCode["LoopDetected"] = "LoopDetected";
HttpStatusCode["NotExtended"] = "NotExtended";
HttpStatusCode["NetworkAuthenticationRequired"] = "NetworkAuthenticationRequired";
})(HttpStatusCode = exports.HttpStatusCode || (exports.HttpStatusCode = {}));
var ApiException = /** @class */ (function (_super) {
__extends(ApiException, _super);
function ApiException(message, status, response, headers, result) {
var _this = _super.call(this) || this;
_this.isApiException = true;
_this.message = message;
_this.status = status;
_this.response = response;
_this.headers = headers;
_this.result = result;
return _this;
}
ApiException.isApiException = function (obj) {
return obj.isApiException === true;
};
return ApiException;
}(Error));
exports.ApiException = ApiException;
function throwException(message, status, response, headers, result) {
if (result !== null && result !== undefined)
throw result;
else
throw new ApiException(message, status, response, headers, null);
}
//# sourceMappingURL=SystemInfoService.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,389 @@
/* tslint:disable */
/* eslint-disable */
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v13.8.2.0 (NJsonSchema v10.2.1.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
// ReSharper disable InconsistentNaming
export default class ServiceInfoService {
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
this.http = http ? http : <any>window;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "";
}
getCount(): Promise<number> {
let url_ = this.baseUrl + "/api/Services/count";
url_ = url_.replace(/[?&]$/, "");
let options_ = <RequestInit>{
method: "GET",
headers: {
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processGetCount(_response);
});
}
protected processGetCount(response: Response): Promise<number> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result200 = resultData200 !== undefined ? resultData200 : <any>null;
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<number>(<any>null);
}
get(): Promise<ServiceInfo[]> {
let url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, "");
let options_ = <RequestInit>{
method: "GET",
headers: {
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processGet(_response);
});
}
protected processGet(response: Response): Promise<ServiceInfo[]> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
if (Array.isArray(resultData200)) {
result200 = [] as any;
for (let item of resultData200)
result200!.push(ServiceInfo.fromJS(item));
}
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<ServiceInfo[]>(<any>null);
}
post(request: ServiceRequest): Promise<ServiceRequest> {
let url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(request);
let options_ = <RequestInit>{
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processPost(_response);
});
}
protected processPost(response: Response): Promise<ServiceRequest> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 201) {
return response.text().then((_responseText) => {
let result201: any = null;
let resultData201 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result201 = ServiceRequest.fromJS(resultData201);
return result201;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<ServiceRequest>(<any>null);
}
put(request: ServiceRequest): Promise<void> {
let url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(request);
let options_ = <RequestInit>{
body: content_,
method: "PUT",
headers: {
"Content-Type": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processPut(_response);
});
}
protected processPut(response: Response): Promise<void> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 204) {
return response.text().then((_responseText) => {
return;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<void>(<any>null);
}
delete(id: number): Promise<void> {
let url_ = this.baseUrl + "/api/Services/{id}";
if (id === undefined || id === null)
throw new Error("The parameter 'id' must be defined.");
url_ = url_.replace("{id}", encodeURIComponent("" + id));
url_ = url_.replace(/[?&]$/, "");
let options_ = <RequestInit>{
method: "DELETE",
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processDelete(_response);
});
}
protected processDelete(response: Response): Promise<void> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 204) {
return response.text().then((_responseText) => {
return;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<void>(<any>null);
}
}
export class ServiceInfo implements IServiceInfo {
service?: ServiceRequest | undefined;
statusCode!: HttpStatusCode;
response?: string | undefined;
constructor(data?: IServiceInfo) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}
init(_data?: any) {
if (_data) {
this.service = _data["service"] ? ServiceRequest.fromJS(_data["service"]) : <any>undefined;
this.statusCode = _data["statusCode"];
this.response = _data["response"];
}
}
static fromJS(data: any): ServiceInfo {
data = typeof data === 'object' ? data : {};
let result = new ServiceInfo();
result.init(data);
return result;
}
toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["service"] = this.service ? this.service.toJSON() : <any>undefined;
data["statusCode"] = this.statusCode;
data["response"] = this.response;
return data;
}
}
export interface IServiceInfo {
service?: ServiceRequest | undefined;
statusCode: HttpStatusCode;
response?: string | undefined;
}
export class ServiceRequest implements IServiceRequest {
id!: number;
name?: string | undefined;
uri?: string | undefined;
constructor(data?: IServiceRequest) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}
init(_data?: any) {
if (_data) {
this.id = _data["id"];
this.name = _data["name"];
this.uri = _data["uri"];
}
}
static fromJS(data: any): ServiceRequest {
data = typeof data === 'object' ? data : {};
let result = new ServiceRequest();
result.init(data);
return result;
}
toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["id"] = this.id;
data["name"] = this.name;
data["uri"] = this.uri;
return data;
}
}
export interface IServiceRequest {
id: number;
name?: string | undefined;
uri?: string | undefined;
}
export enum HttpStatusCode {
Continue = "Continue",
SwitchingProtocols = "SwitchingProtocols",
Processing = "Processing",
EarlyHints = "EarlyHints",
OK = "OK",
Created = "Created",
Accepted = "Accepted",
NonAuthoritativeInformation = "NonAuthoritativeInformation",
NoContent = "NoContent",
ResetContent = "ResetContent",
PartialContent = "PartialContent",
MultiStatus = "MultiStatus",
AlreadyReported = "AlreadyReported",
IMUsed = "IMUsed",
MultipleChoices = "Ambiguous",
Ambiguous = "Ambiguous",
MovedPermanently = "Moved",
Moved = "Moved",
Found = "Redirect",
Redirect = "Redirect",
SeeOther = "RedirectMethod",
RedirectMethod = "RedirectMethod",
NotModified = "NotModified",
UseProxy = "UseProxy",
Unused = "Unused",
TemporaryRedirect = "TemporaryRedirect",
RedirectKeepVerb = "TemporaryRedirect",
PermanentRedirect = "PermanentRedirect",
BadRequest = "BadRequest",
Unauthorized = "Unauthorized",
PaymentRequired = "PaymentRequired",
Forbidden = "Forbidden",
NotFound = "NotFound",
MethodNotAllowed = "MethodNotAllowed",
NotAcceptable = "NotAcceptable",
ProxyAuthenticationRequired = "ProxyAuthenticationRequired",
RequestTimeout = "RequestTimeout",
Conflict = "Conflict",
Gone = "Gone",
LengthRequired = "LengthRequired",
PreconditionFailed = "PreconditionFailed",
RequestEntityTooLarge = "RequestEntityTooLarge",
RequestUriTooLong = "RequestUriTooLong",
UnsupportedMediaType = "UnsupportedMediaType",
RequestedRangeNotSatisfiable = "RequestedRangeNotSatisfiable",
ExpectationFailed = "ExpectationFailed",
MisdirectedRequest = "MisdirectedRequest",
UnprocessableEntity = "UnprocessableEntity",
Locked = "Locked",
FailedDependency = "FailedDependency",
UpgradeRequired = "UpgradeRequired",
PreconditionRequired = "PreconditionRequired",
TooManyRequests = "TooManyRequests",
RequestHeaderFieldsTooLarge = "RequestHeaderFieldsTooLarge",
UnavailableForLegalReasons = "UnavailableForLegalReasons",
InternalServerError = "InternalServerError",
NotImplemented = "NotImplemented",
BadGateway = "BadGateway",
ServiceUnavailable = "ServiceUnavailable",
GatewayTimeout = "GatewayTimeout",
HttpVersionNotSupported = "HttpVersionNotSupported",
VariantAlsoNegotiates = "VariantAlsoNegotiates",
InsufficientStorage = "InsufficientStorage",
LoopDetected = "LoopDetected",
NotExtended = "NotExtended",
NetworkAuthenticationRequired = "NetworkAuthenticationRequired",
}
export class ApiException extends Error {
message: string;
status: number;
response: string;
headers: { [key: string]: any; };
result: any;
constructor(message: string, status: number, response: string, headers: { [key: string]: any; }, result: any) {
super();
this.message = message;
this.status = status;
this.response = response;
this.headers = headers;
this.result = result;
}
protected isApiException = true;
static isApiException(obj: any): obj is ApiException {
return obj.isApiException === true;
}
}
function throwException(message: string, status: number, response: string, headers: { [key: string]: any; }, result?: any): any {
if (result !== null && result !== undefined)
throw result;
else
throw new ApiException(message, status, response, headers, null);
}

View File

@ -0,0 +1,88 @@
import { Grid, Typography } from '@material-ui/core';
import Accordion from '@material-ui/core/Accordion';
import AccordionDetails from '@material-ui/core/AccordionDetails';
import AccordionSummary from '@material-ui/core/AccordionSummary';
import { blueGrey } from '@material-ui/core/colors';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { Skeleton } from '@material-ui/lab';
import { withStyles } from '@material-ui/styles';
import React, { Component } from 'react';
const styles = theme => ({
acc_summary: {
backgroundColor: blueGrey[50],
padding: theme.spacing(2),
textAlign: 'center',
height: '75px',
},
acc_details: {
backgroundColor: blueGrey[100],
},
grid_typo: {
fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular,
},
grid_typo_2: {
marginLeft: '5px',
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
color: theme.palette.text.secondary,
},
});
class ServiceInfoSkeleton extends Component {
render() {
const { classes } = this.props;
return (
<Accordion>
<AccordionSummary className={classes.acc_summary}
expandIcon={<ExpandMoreIcon />}>
<Grid container
spacing={1}
direction="row"
justify="flex-start"
alignItems="center">
<Grid item>
<Typography className={classes.grid_typo}><Skeleton width={200} /></Typography>
</Grid>
<Grid item>
<Typography className={classes.grid_typo_2}><Skeleton width={300} /></Typography>
</Grid>
<Grid item style={{ marginLeft: 'auto' }}>
<Grid container
spacing={1}
direction="row"
justify="flex-start"
alignItems="center">
<Grid item>
<Typography><Skeleton width={150} /></Typography>
</Grid>
</Grid>
</Grid>
</Grid>
</AccordionSummary>
<AccordionDetails className={classes.acc_details}>
<Grid container
spacing={1}
direction="column"
justify="flex-start"
alignItems="flex-start">
<Grid item>
<Typography><Skeleton width={800} /></Typography>
</Grid>
<Grid item>
<Typography><Skeleton width={300} /></Typography>
</Grid>
<Grid item>
<Typography><Skeleton width={500} /></Typography>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
);
}
}
export default withStyles(styles)(ServiceInfoSkeleton);

View File

@ -0,0 +1,146 @@
import { Grid, IconButton, Paper, Typography } from '@material-ui/core';
import { blueGrey } from '@material-ui/core/colors';
import { AddBox, Refresh } from '@material-ui/icons/';
import { withStyles } from '@material-ui/styles';
import { HubConnectionBuilder } from '@microsoft/signalr';
import React, { Component } from 'react';
import AddNewDialog from './AddNewDialog';
import ServiceInfoService, { ServiceRequest } from './ServiceInfoService';
import ServiceInfoComponent from './ServiceInfoComponent';
import ServiceInfoSkeleton from './ServiceInfoSkeleton';
const styles = theme => ({
typo: {
fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular,
},
paper: {
backgroundColor: blueGrey[50],
height: '60px',
}
});
const hub_url = "/hubs/services";
const notify_method_name = "NotifyUpdatedAsync";
class Services extends Component {
constructor(props) {
super(props);
this.state = {
hubConnection: null,
isDialogOpen: false,
isLoading: false,
service: new ServiceInfoService(),
services: [],
serviceCount: [1, 2, 3],
}
this.handleDevicesUpdated = this.handleDevicesUpdated.bind(this);
this.addDevice = this.addDevice.bind(this);
}
handleDevicesUpdated() {
this.setState({ isLoading: true });
this.state.service.getCount().then(result => {
const updatedCount = [];
for (var i = 0; i < result; i++) {
updatedCount.push(i);
}
this.setState({ serviceCount: updatedCount });
}).catch(ex => {
console.log(ex);
});
this.state.service.get().then(result => {
const updatedServices = [];
for (var s of result) {
updatedServices.push(s);
}
this.setState({ services: updatedServices });
}).catch(ex => {
console.log(ex);
}).finally(() => this.setState({ isLoading: false }));
}
componentDidMount() {
this.handleDevicesUpdated();
const newConnection = new HubConnectionBuilder()
.withUrl(hub_url)
.withAutomaticReconnect()
.build();
this.setState({ hubConnection: newConnection });
newConnection.start()
.then(_ => {
console.log('Services hub Connected!');
newConnection.on(notify_method_name, () => this.handleDevicesUpdated());
}).catch(e => console.log('Services hub Connection failed: ', e));
}
componentWillUnmount() {
if (this.state.hubConnection != null) {
this.state.hubConnection.off(notify_method_name);
console.log('Services hub Disconnected!');
}
}
addDevice(name, url) {
this.setState({ isDialogOpen: false });
let request = new ServiceRequest();
request.id = 0;
request.name = name;
request.uri = url;
this.state.service.post(request).catch(ex => {
console.log(ex);
});
}
render() {
const { classes } = this.props;
const ServiceComponents = this.state.services.map((info, index) => (
<ServiceInfoComponent key={index} isAdmin={this.props.isAdmin} info={info} service={this.state.service} />
));
const Skeletons = this.state.serviceCount.map((i, index) => (
<ServiceInfoSkeleton key={index} />
));
return (
<React.Fragment>
<Paper className={classes.paper} square>
<Grid container
spacing={0}
direction="row"
justify="center"
alignItems="center">
<Grid item>
<Typography className={classes.typo}>Services</Typography>
</Grid>
<Grid item>
{this.props.isAdmin ?
<IconButton color="primary" onClick={() => this.setState({ isDialogOpen: true })}>
<AddBox fontSize="large" />
</IconButton>
: null
}
</Grid>
<Grid item>
<IconButton color="primary" onClick={this.handleDevicesUpdated}>
<Refresh fontSize="large" />
</IconButton>
</Grid>
<AddNewDialog open={this.state.isDialogOpen} handleClose={() => this.setState({ isDialogOpen: false })} handleAdd={this.addDevice} />
</Grid>
</Paper>
{this.state.isLoading ? Skeletons : ServiceComponents}
</React.Fragment>
);
}
}
export default withStyles(styles)(Services);

View File

@ -1,14 +1,14 @@
import React, { Component } from 'react';
import { Box, FormControlLabel, Grid, IconButton, Typography } from '@material-ui/core';
import Accordion from '@material-ui/core/Accordion';
import { blue, blueGrey, green, orange, red, yellow } from '@material-ui/core/colors';
import AccordionSummary from '@material-ui/core/AccordionSummary';
import AccordionDetails from '@material-ui/core/AccordionDetails';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { Grid, Typography, Paper, IconButton, Box, FormControlLabel } from '@material-ui/core';
import { withStyles } from '@material-ui/styles';
import { withRouter } from "react-router";
import AccordionSummary from '@material-ui/core/AccordionSummary';
import { blueGrey, green, orange, red } from '@material-ui/core/colors';
import { Power, PowerOff, Refresh } from '@material-ui/icons/';
import DeviceService from '../../common/DeviceService'
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { withStyles } from '@material-ui/styles';
import React, { Component } from 'react';
import { withRouter } from "react-router";
import DeviceService from '../../common/DeviceService';
import DevicesContext from '../../contexts/DevicesContext';
const styles = theme => ({
@ -31,6 +31,7 @@ const styles = theme => ({
grid_item: {
width: '100%',
marginLeft: '5px',
marginRight: '30px',
},
grid_item_typo: {
fontSize: theme.typography.pxToRem(15),
@ -62,8 +63,8 @@ class DeviceComponent extends Component {
if (status == "Online") {
return { color: green[600] };
} else if (status == "Offline") {
return { color: orange[900] };
} else /* if (device.status == "unknown") */ {
return { color: blueGrey[500] };
} else /* if (device.status == "Unknown" || device.status == "Error") */ {
return { color: red[800] };
}
}

View File

@ -1,11 +1,11 @@
import { Box, Paper, Typography, IconButton, Grid } from '@material-ui/core';
import { blue, blueGrey, green, orange, red, yellow } from '@material-ui/core/colors';
import { Box, Grid, IconButton, Paper, Typography } from '@material-ui/core';
import { blueGrey } from '@material-ui/core/colors';
import { Power, PowerOff, Refresh } from '@material-ui/icons/';
import { withStyles } from '@material-ui/styles';
import React from 'react';
import DeviceService from '../../common/DeviceService';
import DevicesContext from '../../contexts/DevicesContext';
import DeviceComponent from './DeviceComponent';
import { Power, PowerOff, Refresh } from '@material-ui/icons/';
const styles = theme => ({
root: {
@ -15,7 +15,6 @@ const styles = theme => ({
paper: {
backgroundColor: blueGrey[50],
height: '60px',
margin: 'auto',
},
typo: {
fontSize: theme.typography.pxToRem(20),
@ -69,15 +68,15 @@ class Devices extends React.Component {
<Box className={classes.root}>
<Paper className={classes.paper} square>
<Grid container
spacing={3}
spacing={0}
direction="row"
justify="center"
alignItems="center">
<Grid item>
<Typography className={classes.typo}>All Devices</Typography>
</Grid>
{this.renderButtons()}
<Grid item>
{this.renderButtons()}
</Grid>
</Grid>
</Paper>

View File

@ -1,11 +1,9 @@
import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked';
import PlayCircleFilledIcon from '@material-ui/icons/PlayCircleFilled';
import { shadows } from '@material-ui/system';
import { Box, Popover, Typography, Tooltip, Grid } from '@material-ui/core';
import { Box, Tooltip } from '@material-ui/core';
import { blue, red, yellow } from '@material-ui/core/colors';
import React, { Component } from 'react';
import { useHistory, withRouter } from 'react-router-dom';
import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked';
import { makeStyles } from '@material-ui/styles';
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
class DeviceMarker extends Component {
constructor(props) {
@ -18,9 +16,9 @@ class DeviceMarker extends Component {
getColor() {
const { device } = this.props;
if (device.status == "Online") {
if (device.status === "Online") {
return { color: blue[800] };
} else if (device.status == "Offline") {
} else if (device.status === "Offline") {
return { color: yellow[800] };
} else /* if (device.status == "unknown") */ {
return { color: red[800] };

View File

@ -1,14 +1,22 @@
/*global google*/
import { Box, withStyles } from '@material-ui/core';
import GoogleMapReact from 'google-map-react';
import React, { Component } from 'react';
import DeviceMarker from './DeviceMarker'
import C from '../../common/Constants'
import C from '../../common/Constants';
import DevicesContext from '../../contexts/DevicesContext';
import DeviceMarker from './DeviceMarker';
const lat_offset = 0.000038;
const lng_offset = -0.000058;
export default class MapContainer extends Component {
const styles = theme => ({
root: {
height: '93vh',
width: '100%',
}
});
class MapContainer extends Component {
constructor(props) {
super(props);
@ -20,11 +28,18 @@ export default class MapContainer extends Component {
};
this.probabilityHandler = this.probabilityHandler.bind(this);
this.handlePoint = this.handlePoint.bind(this);
}
static contextType = DevicesContext;
probabilityHandler(point) {
probabilityHandler(points) {
for (var point of points) {
this.handlePoint(point);
}
}
handlePoint(point) {
if (point.prob > 0.5) {
this.setState({
@ -57,6 +72,8 @@ export default class MapContainer extends Component {
}
render() {
const {classes} = this.props;
const heatMapData = {
positions: this.state.heatmapPoints,
options: {
@ -85,7 +102,7 @@ export default class MapContainer extends Component {
));
return (
<div style={{ height: '93vh', width: '100%' }}>
<Box className={classes.root}>
<GoogleMapReact
bootstrapURLKeys={{
key: ["AIzaSyCZ51VFfxqZ2GkCmVrcNZdUKsM0fuBQUCY"],
@ -99,7 +116,9 @@ export default class MapContainer extends Component {
defaultCenter={this.state.center}>
{Markers}
</GoogleMapReact>
</div>
</Box>
);
}
}
}
export default withStyles(styles)(MapContainer);

View File

@ -0,0 +1,208 @@
"use strict";
/* tslint:disable */
/* eslint-disable */
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v13.8.2.0 (NJsonSchema v10.2.1.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
// ReSharper disable InconsistentNaming
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiException = exports.HttpStatusCode = void 0;
var LogService = /** @class */ (function () {
function LogService(baseUrl, http) {
this.jsonParseReviver = undefined;
this.http = http ? http : window;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "api/logs";
}
LogService.prototype.getAll = function () {
var _this = this;
var url_ = this.baseUrl + "/all";
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "GET",
headers: {
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processGetAll(_response);
});
};
LogService.prototype.processGetAll = function (response) {
var _this = this;
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 200) {
return response.text().then(function (_responseText) {
var result200 = null;
var resultData200 = _responseText === "" ? null : JSON.parse(_responseText, _this.jsonParseReviver);
if (Array.isArray(resultData200)) {
result200 = [];
for (var _i = 0, resultData200_1 = resultData200; _i < resultData200_1.length; _i++) {
var item = resultData200_1[_i];
result200.push(item);
}
}
return result200;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
LogService.prototype.getFiles = function (filenames) {
var _this = this;
var url_ = this.baseUrl + "?";
if (filenames !== undefined && filenames !== null)
filenames && filenames.forEach(function (item) { url_ += "filenames=" + encodeURIComponent("" + item) + "&"; });
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "GET",
headers: {
"Accept": "application/octet-stream",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processGetFiles(_response);
});
};
LogService.prototype.processGetFiles = function (response) {
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 200 || status === 206) {
var contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
var fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
var fileName_1 = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
return response.blob().then(function (blob) { return { fileName: fileName_1, data: blob, status: status, headers: _headers }; });
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
return LogService;
}());
exports.default = LogService;
var HttpStatusCode;
(function (HttpStatusCode) {
HttpStatusCode["Continue"] = "Continue";
HttpStatusCode["SwitchingProtocols"] = "SwitchingProtocols";
HttpStatusCode["Processing"] = "Processing";
HttpStatusCode["EarlyHints"] = "EarlyHints";
HttpStatusCode["OK"] = "OK";
HttpStatusCode["Created"] = "Created";
HttpStatusCode["Accepted"] = "Accepted";
HttpStatusCode["NonAuthoritativeInformation"] = "NonAuthoritativeInformation";
HttpStatusCode["NoContent"] = "NoContent";
HttpStatusCode["ResetContent"] = "ResetContent";
HttpStatusCode["PartialContent"] = "PartialContent";
HttpStatusCode["MultiStatus"] = "MultiStatus";
HttpStatusCode["AlreadyReported"] = "AlreadyReported";
HttpStatusCode["IMUsed"] = "IMUsed";
HttpStatusCode["MultipleChoices"] = "Ambiguous";
HttpStatusCode["Ambiguous"] = "Ambiguous";
HttpStatusCode["MovedPermanently"] = "Moved";
HttpStatusCode["Moved"] = "Moved";
HttpStatusCode["Found"] = "Redirect";
HttpStatusCode["Redirect"] = "Redirect";
HttpStatusCode["SeeOther"] = "RedirectMethod";
HttpStatusCode["RedirectMethod"] = "RedirectMethod";
HttpStatusCode["NotModified"] = "NotModified";
HttpStatusCode["UseProxy"] = "UseProxy";
HttpStatusCode["Unused"] = "Unused";
HttpStatusCode["TemporaryRedirect"] = "TemporaryRedirect";
HttpStatusCode["RedirectKeepVerb"] = "TemporaryRedirect";
HttpStatusCode["PermanentRedirect"] = "PermanentRedirect";
HttpStatusCode["BadRequest"] = "BadRequest";
HttpStatusCode["Unauthorized"] = "Unauthorized";
HttpStatusCode["PaymentRequired"] = "PaymentRequired";
HttpStatusCode["Forbidden"] = "Forbidden";
HttpStatusCode["NotFound"] = "NotFound";
HttpStatusCode["MethodNotAllowed"] = "MethodNotAllowed";
HttpStatusCode["NotAcceptable"] = "NotAcceptable";
HttpStatusCode["ProxyAuthenticationRequired"] = "ProxyAuthenticationRequired";
HttpStatusCode["RequestTimeout"] = "RequestTimeout";
HttpStatusCode["Conflict"] = "Conflict";
HttpStatusCode["Gone"] = "Gone";
HttpStatusCode["LengthRequired"] = "LengthRequired";
HttpStatusCode["PreconditionFailed"] = "PreconditionFailed";
HttpStatusCode["RequestEntityTooLarge"] = "RequestEntityTooLarge";
HttpStatusCode["RequestUriTooLong"] = "RequestUriTooLong";
HttpStatusCode["UnsupportedMediaType"] = "UnsupportedMediaType";
HttpStatusCode["RequestedRangeNotSatisfiable"] = "RequestedRangeNotSatisfiable";
HttpStatusCode["ExpectationFailed"] = "ExpectationFailed";
HttpStatusCode["MisdirectedRequest"] = "MisdirectedRequest";
HttpStatusCode["UnprocessableEntity"] = "UnprocessableEntity";
HttpStatusCode["Locked"] = "Locked";
HttpStatusCode["FailedDependency"] = "FailedDependency";
HttpStatusCode["UpgradeRequired"] = "UpgradeRequired";
HttpStatusCode["PreconditionRequired"] = "PreconditionRequired";
HttpStatusCode["TooManyRequests"] = "TooManyRequests";
HttpStatusCode["RequestHeaderFieldsTooLarge"] = "RequestHeaderFieldsTooLarge";
HttpStatusCode["UnavailableForLegalReasons"] = "UnavailableForLegalReasons";
HttpStatusCode["InternalServerError"] = "InternalServerError";
HttpStatusCode["NotImplemented"] = "NotImplemented";
HttpStatusCode["BadGateway"] = "BadGateway";
HttpStatusCode["ServiceUnavailable"] = "ServiceUnavailable";
HttpStatusCode["GatewayTimeout"] = "GatewayTimeout";
HttpStatusCode["HttpVersionNotSupported"] = "HttpVersionNotSupported";
HttpStatusCode["VariantAlsoNegotiates"] = "VariantAlsoNegotiates";
HttpStatusCode["InsufficientStorage"] = "InsufficientStorage";
HttpStatusCode["LoopDetected"] = "LoopDetected";
HttpStatusCode["NotExtended"] = "NotExtended";
HttpStatusCode["NetworkAuthenticationRequired"] = "NetworkAuthenticationRequired";
})(HttpStatusCode = exports.HttpStatusCode || (exports.HttpStatusCode = {}));
var ApiException = /** @class */ (function (_super) {
__extends(ApiException, _super);
function ApiException(message, status, response, headers, result) {
var _this = _super.call(this) || this;
_this.isApiException = true;
_this.message = message;
_this.status = status;
_this.response = response;
_this.headers = headers;
_this.result = result;
return _this;
}
ApiException.isApiException = function (obj) {
return obj.isApiException === true;
};
return ApiException;
}(Error));
exports.ApiException = ApiException;
function throwException(message, status, response, headers, result) {
if (result !== null && result !== undefined)
throw result;
else
throw new ApiException(message, status, response, headers, null);
}
//# sourceMappingURL=LogService.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,201 @@
/* tslint:disable */
/* eslint-disable */
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v13.8.2.0 (NJsonSchema v10.2.1.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
// ReSharper disable InconsistentNaming
export default class LogService {
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
this.http = http ? http : <any>window;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "api/logs";
}
getAll(): Promise<string[]> {
let url_ = this.baseUrl + "/all";
url_ = url_.replace(/[?&]$/, "");
let options_ = <RequestInit>{
method: "GET",
headers: {
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processGetAll(_response);
});
}
protected processGetAll(response: Response): Promise<string[]> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
if (Array.isArray(resultData200)) {
result200 = [] as any;
for (let item of resultData200)
result200!.push(item);
}
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<string[]>(<any>null);
}
getFiles(filenames: string[] | null | undefined): Promise<FileResponse | null> {
let url_ = this.baseUrl + "?";
if (filenames !== undefined && filenames !== null)
filenames && filenames.forEach(item => { url_ += "filenames=" + encodeURIComponent("" + item) + "&"; });
url_ = url_.replace(/[?&]$/, "");
let options_ = <RequestInit>{
method: "GET",
headers: {
"Accept": "application/octet-stream",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processGetFiles(_response);
});
}
protected processGetFiles(response: Response): Promise<FileResponse | null> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200 || status === 206) {
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
const fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
const fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<FileResponse | null>(<any>null);
}
}
export interface FileResponse {
data: Blob;
status: number;
fileName?: string;
headers?: { [name: string]: any };
}
export enum HttpStatusCode {
Continue = "Continue",
SwitchingProtocols = "SwitchingProtocols",
Processing = "Processing",
EarlyHints = "EarlyHints",
OK = "OK",
Created = "Created",
Accepted = "Accepted",
NonAuthoritativeInformation = "NonAuthoritativeInformation",
NoContent = "NoContent",
ResetContent = "ResetContent",
PartialContent = "PartialContent",
MultiStatus = "MultiStatus",
AlreadyReported = "AlreadyReported",
IMUsed = "IMUsed",
MultipleChoices = "Ambiguous",
Ambiguous = "Ambiguous",
MovedPermanently = "Moved",
Moved = "Moved",
Found = "Redirect",
Redirect = "Redirect",
SeeOther = "RedirectMethod",
RedirectMethod = "RedirectMethod",
NotModified = "NotModified",
UseProxy = "UseProxy",
Unused = "Unused",
TemporaryRedirect = "TemporaryRedirect",
RedirectKeepVerb = "TemporaryRedirect",
PermanentRedirect = "PermanentRedirect",
BadRequest = "BadRequest",
Unauthorized = "Unauthorized",
PaymentRequired = "PaymentRequired",
Forbidden = "Forbidden",
NotFound = "NotFound",
MethodNotAllowed = "MethodNotAllowed",
NotAcceptable = "NotAcceptable",
ProxyAuthenticationRequired = "ProxyAuthenticationRequired",
RequestTimeout = "RequestTimeout",
Conflict = "Conflict",
Gone = "Gone",
LengthRequired = "LengthRequired",
PreconditionFailed = "PreconditionFailed",
RequestEntityTooLarge = "RequestEntityTooLarge",
RequestUriTooLong = "RequestUriTooLong",
UnsupportedMediaType = "UnsupportedMediaType",
RequestedRangeNotSatisfiable = "RequestedRangeNotSatisfiable",
ExpectationFailed = "ExpectationFailed",
MisdirectedRequest = "MisdirectedRequest",
UnprocessableEntity = "UnprocessableEntity",
Locked = "Locked",
FailedDependency = "FailedDependency",
UpgradeRequired = "UpgradeRequired",
PreconditionRequired = "PreconditionRequired",
TooManyRequests = "TooManyRequests",
RequestHeaderFieldsTooLarge = "RequestHeaderFieldsTooLarge",
UnavailableForLegalReasons = "UnavailableForLegalReasons",
InternalServerError = "InternalServerError",
NotImplemented = "NotImplemented",
BadGateway = "BadGateway",
ServiceUnavailable = "ServiceUnavailable",
GatewayTimeout = "GatewayTimeout",
HttpVersionNotSupported = "HttpVersionNotSupported",
VariantAlsoNegotiates = "VariantAlsoNegotiates",
InsufficientStorage = "InsufficientStorage",
LoopDetected = "LoopDetected",
NotExtended = "NotExtended",
NetworkAuthenticationRequired = "NetworkAuthenticationRequired",
}
export class ApiException extends Error {
message: string;
status: number;
response: string;
headers: { [key: string]: any; };
result: any;
constructor(message: string, status: number, response: string, headers: { [key: string]: any; }, result: any) {
super();
this.message = message;
this.status = status;
this.response = response;
this.headers = headers;
this.result = result;
}
protected isApiException = true;
static isApiException(obj: any): obj is ApiException {
return obj.isApiException === true;
}
}
function throwException(message: string, status: number, response: string, headers: { [key: string]: any; }, result?: any): any {
if (result !== null && result !== undefined)
throw result;
else
throw new ApiException(message, status, response, headers, null);
}

View File

@ -0,0 +1,128 @@
import { Box, Button, Checkbox, List, ListItem, ListItemIcon, ListItemText, Paper, withStyles } from '@material-ui/core';
import { blueGrey } from '@material-ui/core/colors';
import React, { Component } from 'react';
import LogService from './LogService';
const styles = theme => ({
root: {
padding: '64px',
backgroundColor: theme.palette.primary.dark,
},
paper: {
backgroundColor: blueGrey[50],
},
});
class Logs extends Component {
constructor(props) {
super(props)
this.state = {
service: null,
files: [],
checked: [],
selectAllChecked: false,
}
}
componentDidMount() {
var service = new LogService();
this.setState({service: service});
service.getAll().then(result => {
this.setState({files: result});
}).catch(ex => console.log(ex));
}
handleToggle = (value) => {
const currentIndex = this.state.checked.indexOf(value);
const newChecked = [...this.state.checked];
if (currentIndex === -1) {
newChecked.push(value);
} else {
newChecked.splice(currentIndex, 1);
}
this.setState({checked: newChecked});
}
handleSelectAllToggle = () => {
this.setState({selectAllChecked: !this.state.selectAllChecked});
if (this.state.selectAllChecked) {
this.setState({checked: []});
} else {
const newChecked = [...this.state.files];
this.setState({checked: newChecked});
}
}
onDownload = () => {
this.state.service.getFiles(this.state.checked)
.then(result => {
const filename = `Logs-${new Date().toISOString()}.zip`;
const textUrl = URL.createObjectURL(result.data);
const element = document.createElement('a');
element.setAttribute('href', textUrl);
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
this.setState({checked: []});
this.setState({selectAllChecked: false});
})
.catch(ex => console.log(ex));
}
render() {
const { classes } = this.props;
const Files = this.state.files.map((value) => {
const labelId = `checkbox-list-label-${value}`;
return (
<ListItem key={value} role={undefined} dense button onClick={() => this.handleToggle(value)}>
<ListItemIcon>
<Checkbox
edge="start"
checked={this.state.checked.indexOf(value) !== -1}
tabIndex={-1}
disableRipple
inputProps={{ 'aria-labelledby': labelId }}
/>
</ListItemIcon>
<ListItemText id={labelId} primary={`${value}`} />
</ListItem>
);
})
return (
<Box className={classes.root}>
<Paper className={classes.paper}>
<List className={classes.paper}>
<ListItem key="Select-all" role={undefined} dense button onClick={this.handleSelectAllToggle}>
<ListItemIcon>
<Checkbox
edge="start"
checked={this.state.selectAllChecked}
tabIndex={-1}
disableRipple
inputProps={{ 'aria-labelledby': "Select-all" }}
/>
</ListItemIcon>
<ListItemText id="checkbox-list-label-Select-all" primary={(this.state.selectAllChecked ? "Uns" : "S") + "elect all"} />
</ListItem>
{Files}
</List>
<Button onClick={this.onDownload}>
Download
</Button>
</Paper>
</Box>
)
}
}
export default withStyles(styles)(Logs);

View File

@ -60,6 +60,7 @@ export default class DevicesContextProvider extends Component {
}
service.getall().then(result => {
this.setState({ devices: result });
this.invokeHandlers(C.update_all_method_name, null);
}).catch(ex => {
console.log(ex);
});
@ -96,26 +97,29 @@ export default class DevicesContextProvider extends Component {
newConnection.start()
.then(_ => {
console.log('Hub Connected!');
console.log('Devices hub Connected!');
newConnection.on(C.probability_method_name, (id, date, prob) => {
newConnection.on(C.probability_method_name, (messages) => {
//console.log(method_name + " recieved: [id: " + id + ", date: " + date + ", prob: " + prob + "]");
var device = this.state.devices.filter(function (x) { return x.id === id })[0]
var newPoint = { lat: device.coordinates.latitude, lng: device.coordinates.longitude, prob: prob, date: date };
const newPoints = [];
for (var message of messages) {
var device = this.state.devices.filter(function (x) { return x.id === message.deviceId })[0]
var newPoint = { deviceId: device.id, lat: device.coordinates.latitude, lng: device.coordinates.longitude, prob: message.probability, date: new Date(message.date) };
newPoints.push(newPoint);
}
this.setState({
heatmapPoints: [...this.state.heatmapPoints, newPoint]
heatmapPoints: this.state.heatmapPoints.concat(newPoints)
});
this.invokeHandlers(C.probability_method_name, newPoint);
this.invokeHandlers(C.probability_method_name, newPoints);
});
newConnection.on(C.update_all_method_name, () => {
this.updateAllDevicesInternal(service);
this.invokeHandlers(C.update_all_method_name, null);
});
newConnection.on(C.update_method_name, (id) => this.updateDeviceInternal(id, service));
}).catch(e => console.log('Hub Connection failed: ', e));
}).catch(e => console.log('Devices hub Connection failed: ', e));
}
componentWillUnmount() {
@ -123,7 +127,7 @@ export default class DevicesContextProvider extends Component {
this.state.hubConnection.off(C.probability_method_name);
this.state.hubConnection.off(C.update_all_method_name);
this.state.hubConnection.off(C.update_method_name);
console.log('Hub Disconnected!');
console.log('Devices hub Disconnected!');
}
}

View File

@ -0,0 +1,66 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Birdmap.API.Controllers
{
[Authorize(Roles = "Admin")]
[ApiController]
[Route("api/[controller]")]
public class LogsController : ControllerBase
{
private readonly ILogger<LogsController> _logger;
private readonly string _logFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Logs");
public LogsController(ILogger<LogsController> logger)
{
_logger = logger;
}
[HttpGet("all")]
public ActionResult<List<string>> GetAll()
{
_logger.LogInformation($"Getting all log filenames from folder: '{_logFolderPath}'...");
return Directory.EnumerateFiles(_logFolderPath, "*.log")
.Select(f => Path.GetFileName(f))
.ToList();
}
[HttpGet]
public async Task<IActionResult> GetFiles([FromQuery] params string[] filenames)
{
if (!filenames.Any())
return null;
return await Task.Run(() =>
{
var zipStream = new MemoryStream();
using (var zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
{
foreach (var file in Directory.GetFiles(_logFolderPath, "*.log"))
{
var filename = Path.GetFileName(file);
if (filenames.Contains(filename))
{
zip.CreateEntryFromFile(file, filename);
}
}
}
zipStream.Position = 0;
return File(zipStream, "application/octet-stream");
});
}
}
}

View File

@ -1,14 +1,19 @@
using AutoMapper;
using Birdmap.API.DTOs;
using Birdmap.API.Services;
using Birdmap.API.Services.Hubs;
using Birdmap.API.Services.Mqtt;
using Birdmap.BLL.Interfaces;
using Birdmap.DAL.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
@ -21,21 +26,34 @@ namespace Birdmap.API.Controllers
{
private readonly IServiceService _service;
private readonly IMapper _mapper;
private readonly IMqttClientService _mqttClientService;
private readonly IHubContext<ServicesHub, IServicesHubClient> _hubContext;
private readonly ILogger<ServicesController> _logger;
public ServicesController(IServiceService service, IMapper mapper, ILogger<ServicesController> logger)
public ServicesController(IServiceService service, IMapper mapper, MqttClientServiceProvider mqttClientProvider,
IHubContext<ServicesHub, IServicesHubClient> hubContext, ILogger<ServicesController> logger)
{
_service = service;
_mapper = mapper;
_mqttClientService = mqttClientProvider.MqttClientService;
_hubContext = hubContext;
_logger = logger;
}
[HttpGet("count"), ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<int>> GetCountAsync()
{
_logger.LogInformation($"Getting service count from db...");
return await _service.GetServiceCountAsync() + 1;
}
[HttpGet, ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<List<ServiceInfo>>> GetAsync()
{
_logger.LogInformation($"Getting all services from db...");
var serviceInfos = (await _service.GetAllServicesAsync())
.Select(s => new ServiceInfo { Service = _mapper.Map<ServiceRequest>(s) });
.Select(s => new ServiceInfo { Service = _mapper.Map<ServiceRequest>(s) }).ToList();
var client = new HttpClient();
foreach (var si in serviceInfos)
@ -50,11 +68,23 @@ namespace Birdmap.API.Controllers
catch (Exception ex)
{
_logger.LogWarning($"Requesting service [{si.Service.Name}] faulted.");
si.StatusCode = System.Net.HttpStatusCode.ServiceUnavailable;
si.StatusCode = HttpStatusCode.ServiceUnavailable;
si.Response = ex.ToString();
}
}
serviceInfos.Add(new()
{
Service = new()
{
Id = 0,
Name = "Mqtt Client Service",
Uri = "localhost",
},
Response = $"IsConnected: {_mqttClientService.IsConnected}",
StatusCode = _mqttClientService.IsConnected ? HttpStatusCode.OK : HttpStatusCode.ServiceUnavailable,
});
return serviceInfos.ToList();
}
@ -67,6 +97,7 @@ namespace Birdmap.API.Controllers
_mapper.Map<Service>(request));
_logger.LogInformation($"Created service [{created.Id}].");
await _hubContext.Clients.All.NotifyUpdatedAsync();
return CreatedAtAction(
nameof(GetAsync),
@ -82,6 +113,7 @@ namespace Birdmap.API.Controllers
service.IsFromConfig = false;
await _service.UpdateServiceAsync(service);
await _hubContext.Clients.All.NotifyUpdatedAsync();
return NoContent();
}
@ -93,6 +125,7 @@ namespace Birdmap.API.Controllers
_logger.LogInformation($"Deleting service [{id}]...");
await _service.DeleteServiceAsync(id);
await _hubContext.Clients.All.NotifyUpdatedAsync();
return NoContent();
}

27
Birdmap.API/Dockerfile Normal file
View File

@ -0,0 +1,27 @@
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
RUN apt-get update && apt-get install -y curl
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN apt-get update && apt-get install -y nodejs
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
RUN apt-get update && apt-get install -y curl
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN apt-get update && apt-get install -y nodejs
WORKDIR /src
COPY ["Birdmap.API/Birdmap.API.csproj", "Birdmap.API/"]
COPY ["Birdmap.BLL/Birdmap.BLL.csproj", "Birdmap.BLL/"]
COPY ["Birdmap.Common/Birdmap.Common.csproj", "Birdmap.Common/"]
COPY ["Birdmap.DAL/Birdmap.DAL.csproj", "Birdmap.DAL/"]
RUN dotnet restore "Birdmap.API/Birdmap.API.csproj"
COPY . .
WORKDIR "/src/Birdmap.API"
RUN dotnet build "Birdmap.API.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Birdmap.API.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Birdmap.API.dll"]

View File

@ -1,5 +1,6 @@
using Birdmap.DAL;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@ -39,6 +40,10 @@ namespace Birdmap.API
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddEnvironmentVariables(prefix: "Birdmap_");
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
@ -53,8 +58,8 @@ namespace Birdmap.API
private static void SeedDatabase(IHost host)
{
using var scope = host.Services.CreateScope();
var dbInitializer = scope.ServiceProvider.GetRequiredService<DbInitializer>();
var dbInitializer = scope.ServiceProvider.GetRequiredService<DbInitializer>();
dbInitializer.Initialize();
}
}

View File

@ -1,7 +1,11 @@
{
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iis": {
"applicationUrl": "http://localhost/Birdmap.API",
"sslPort": 0
},
"iisExpress": {
"applicationUrl": "http://localhost:63288",
"sslPort": 44331
@ -12,16 +16,24 @@
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"Birdmap_LocalDbConnectionString": "Data Source=DESKTOP-3600;Initial Catalog=birdmap2;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Birdmap": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"publishAllPorts": true,
"useSSL": true
}
}
}
}

View File

@ -16,31 +16,16 @@ namespace Birdmap.API.Services.Hubs
public override Task OnConnectedAsync()
{
_logger.LogInformation("Hub Client connected.");
_logger.LogInformation("Devices Hub Client connected.");
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
_logger.LogInformation("Hub Client disconnected.");
_logger.LogInformation("Devices Hub Client disconnected.");
return base.OnDisconnectedAsync(exception);
}
public Task SendProbabilityAsync(Guid deviceId, DateTime date, double probability)
{
return Clients.All.NotifyDeviceAsync(deviceId, date, probability);
}
public Task SendDeviceUpdateAsync(Guid deviceId)
{
return Clients.All.NotifyDeviceUpdatedAsync(deviceId);
}
public Task SendAllUpdatedAsync()
{
return Clients.All.NotifyAllUpdatedAsync();
}
}
}

View File

@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Birdmap.API.Services
{
public record Message(Guid DeviceId, DateTime Date, double Probability);
public interface IDevicesHubClient
{
Task NotifyDeviceAsync(Guid deviceId, DateTime date, double probability);
Task NotifyMessagesAsync(IEnumerable<Message> messages);
Task NotifyDeviceUpdatedAsync(Guid deviceId);
Task NotifyAllUpdatedAsync();
}

View File

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Birdmap.API.Services.Hubs
{
public interface IServicesHubClient
{
Task NotifyUpdatedAsync();
}
}

View File

@ -0,0 +1,31 @@
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace Birdmap.API.Services.Hubs
{
public class ServicesHub : Hub<IServicesHubClient>
{
private readonly ILogger<ServicesHub> _logger;
public ServicesHub(ILogger<ServicesHub> logger)
{
_logger = logger;
}
public override Task OnConnectedAsync()
{
_logger.LogInformation("Services Hub Client connected.");
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
_logger.LogInformation("Services Hub Client disconnected.");
return base.OnDisconnectedAsync(exception);
}
}
}

View File

@ -10,6 +10,6 @@ namespace Birdmap.API.Services
IMqttClientDisconnectedHandler,
IMqttApplicationMessageReceivedHandler
{
public bool IsConnected { get; }
}
}

View File

@ -13,6 +13,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Timer = System.Timers.Timer;
namespace Birdmap.API.Services.Mqtt
{
@ -23,6 +24,11 @@ namespace Birdmap.API.Services.Mqtt
private readonly ILogger<MqttClientService> _logger;
private readonly IInputService _inputService;
private readonly IHubContext<DevicesHub, IDevicesHubClient> _hubContext;
private readonly Timer _hubTimer;
private readonly List<Message> _messages = new();
private readonly object _messageLock = new();
public bool IsConnected => _mqttClient.IsConnected;
public MqttClientService(IMqttClientOptions options, ILogger<MqttClientService> logger, IInputService inputService, IHubContext<DevicesHub, IDevicesHubClient> hubContext)
{
@ -30,10 +36,30 @@ namespace Birdmap.API.Services.Mqtt
_logger = logger;
_inputService = inputService;
_hubContext = hubContext;
_hubTimer = new Timer()
{
AutoReset = true,
Interval = 1000,
};
_hubTimer.Elapsed += SendMqttMessagesWithSignalR;
_mqttClient = new MqttFactory().CreateMqttClient();
ConfigureMqttClient();
}
private void SendMqttMessagesWithSignalR(object sender, System.Timers.ElapsedEventArgs e)
{
lock (_messageLock)
{
if (_messages.Any())
{
_logger.LogInformation($"Sending ({_messages.Count}) messages: {string.Join(" | ", _messages)}");
_hubContext.Clients.All.NotifyMessagesAsync(_messages);
_messages.Clear();
}
}
}
private void ConfigureMqttClient()
{
_mqttClient.ConnectedHandler = this;
@ -54,7 +80,7 @@ namespace Birdmap.API.Services.Mqtt
{
var message = eventArgs.ApplicationMessage.ConvertPayloadToString();
_logger.LogInformation($"Recieved [{eventArgs.ClientId}] " +
_logger.LogDebug($"Recieved [{eventArgs.ClientId}] " +
$"Topic: {eventArgs.ApplicationMessage.Topic} | Payload: {message} | QoS: {eventArgs.ApplicationMessage.QualityOfServiceLevel} | Retain: {eventArgs.ApplicationMessage.Retain}");
try
@ -62,7 +88,10 @@ namespace Birdmap.API.Services.Mqtt
var payload = JsonConvert.DeserializeObject<Payload>(message);
var inputResponse = await _inputService.GetInputAsync(payload.TagID);
await _hubContext.Clients.All.NotifyDeviceAsync(inputResponse.Message.Device_id, inputResponse.Message.Date.UtcDateTime, payload.Probability);
lock (_messageLock)
{
_messages.Add(new Message(inputResponse.Message.Device_id, inputResponse.Message.Date.UtcDateTime, payload.Probability));
}
}
catch (Exception ex)
{
@ -78,6 +107,7 @@ namespace Birdmap.API.Services.Mqtt
_logger.LogInformation($"Connected. Auth result: {eventArgs.AuthenticateResult}. Subscribing to topic: {topic}");
await _mqttClient.SubscribeAsync(topic);
_hubTimer.Start();
}
catch (Exception ex)
{
@ -87,17 +117,18 @@ namespace Birdmap.API.Services.Mqtt
public async Task HandleDisconnectedAsync(MqttClientDisconnectedEventArgs eventArgs)
{
_logger.LogWarning(eventArgs.Exception, $"Disconnected. Reason {eventArgs.ReasonCode}. Auth result: {eventArgs.AuthenticateResult}. Reconnecting...");
_logger.LogDebug(eventArgs.Exception, $"Disconnected. Reason {eventArgs.ReasonCode}. Auth result: {eventArgs.AuthenticateResult}. Reconnecting...");
await Task.Delay(TimeSpan.FromSeconds(5));
try
{
_hubTimer.Stop();
await _mqttClient.ConnectAsync(_options, CancellationToken.None);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Reconnect failed...");
_logger.LogDebug(ex, $"Reconnect failed...");
}
}

View File

@ -139,6 +139,7 @@ namespace Birdmap.API
endpoints.MapHealthChecks("/health");
endpoints.MapControllers();
endpoints.MapHub<DevicesHub>("/hubs/devices");
endpoints.MapHub<ServicesHub>("/hubs/services");
});
app.UseSpa(spa =>

View File

@ -5,5 +5,50 @@
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Kestrel": {
"Certificates": {
"Default": {
"Password": "certpass123",
"Path": "C:\\Users\\Ricsi\\AppData\\Roaming\\ASP.NET\\Https\\aspnetapp.pfx"
}
}
},
"AllowedHosts": "*",
"Secret": "7vj.3KW.hYE!}4u6",
// "LocalDbConnectionString": "Data Source=DESKTOP-3600\\SQLEXPRESS;Initial Catalog=birdmap;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"LocalDbConnectionString": "Data Source=DESKTOP-3600;Initial Catalog=birdmap2;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"Defaults": {
"Services": {
"Local Database": "https://localhost:44331/health",
"KMLabz Services": "https://birb.k8s.kmlabz.com/devices"
},
"Users": [
{
"Name": "admin",
"Password": "pass",
"Role": "Admin"
},
{
"Name": "user",
"Password": "pass",
"Role": "User"
}
]
},
"UseDummyServices": true,
"ServicesBaseUrl": "https://birb.k8s.kmlabz.com/",
"Mqtt": {
"BrokerHostSettings": {
"Host": "localhost",
"Port": 1883
},
"ClientSettings": {
"Id": "ASP.NET Core client",
"Username": "username",
"Password": "password",
"Topic": "devices/output"
}
}
}

View File

@ -6,40 +6,35 @@
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Kestrel": {
"Certificates": {
"Default": {
"Password": "",
"Path": ""
}
}
},
"AllowedHosts": "*",
"Secret": "7vj.3KW.hYE!}4u6",
// "LocalDbConnectionString": "Data Source=DESKTOP-3600\\SQLEXPRESS;Initial Catalog=birdmap;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"LocalDbConnectionString": "Data Source=DESKTOP-3600;Initial Catalog=birdmap;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"Secret": "",
"LocalDbConnectionString": "",
"Defaults": {
"Services": {
"Local Database": "https://localhost:44331/health",
"KMLabz Services": "https://birb.k8s.kmlabz.com/devices"
},
"Users": [
{
"Name": "admin",
"Password": "pass",
"Role": "Admin"
},
{
"Name": "user",
"Password": "pass",
"Role": "User"
}
]
"Users": []
},
"UseDummyServices": true,
"UseDummyServices": false,
"ServicesBaseUrl": "https://birb.k8s.kmlabz.com/",
"Mqtt": {
"BrokerHostSettings": {
"Host": "localhost",
"Host": "",
"Port": 1883
},
"ClientSettings": {
"Id": "ASP.NET Core client",
"Username": "username",
"Password": "password",
"Topic": "devices/output"
"Username": "",
"Password": "",
"Topic": ""
}
}
}

5
Birdmap.API/libman.json Normal file
View File

@ -0,0 +1,5 @@
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": []
}

View File

@ -3,7 +3,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Info"
internalLogFile="${basedir}Log/internal-nlog.txt"
internalLogFile="${basedir}Logs/internal-nlog.txt"
throwConfigExceptions="true">
<!-- enable asp.net core layout renderers -->
@ -14,17 +14,17 @@
<!-- the targets to write to -->
<targets async="true">
<default-target-parameters xsi:type="File" keepFileOpen="false" maxArchiveFiles="10" archiveAboveSize="1048576"/>
<target xsi:type="File" name="allFile" fileName="${basedir}Log/birdmap-all-${shortdate}.log"
<target xsi:type="File" name="allFile" fileName="${basedir}Logs/birdmap-all-${shortdate}.log"
layout="${longdate} [${threadname:whenEmpty=${threadid}}] ${uppercase:${level}} ${logger} - ${message} ${exception:format=tostring}" />
<target xsi:type="File" name="mqttFile" fileName="${basedir}Log/birdmap-mqtt-${shortdate}.log"
<target xsi:type="File" name="mqttFile" fileName="${basedir}Logs/birdmap-mqtt-${shortdate}.log"
layout="${longdate} [${threadname:whenEmpty=${threadid}}] ${uppercase:${level}} ${logger} - ${message} ${exception:format=tostring}" />
<target xsi:type="File" name="hubsFile" fileName="${basedir}Log/birdmap-hubs-${shortdate}.log"
<target xsi:type="File" name="hubsFile" fileName="${basedir}Logs/birdmap-hubs-${shortdate}.log"
layout="${longdate} [${threadname:whenEmpty=${threadid}}] ${uppercase:${level}} ${logger} - ${message} ${exception:format=tostring}" />
<!-- another file log, only own logs. Uses some ASP.NET core renderers -->
<target xsi:type="File" name="ownFile" fileName="${basedir}Log/birdmap-own-${shortdate}.log"
<target xsi:type="File" name="ownFile" fileName="${basedir}Logs/birdmap-own-${shortdate}.log"
layout="${longdate} [${threadname:whenEmpty=${threadid}}] ${uppercase:${level}} ${callsite} - ${message} ${exception:format=tostring} (url: ${aspnet-request-url})(action: ${aspnet-mvc-action})" />
</targets>

View File

@ -6,6 +6,7 @@ namespace Birdmap.BLL.Interfaces
{
public interface IServiceService
{
Task<int> GetServiceCountAsync();
Task<List<Service>> GetAllServicesAsync();
Task<Service> GetServiceAsync(int id);
Task<Service> CreateServiceAsync(Service service);

View File

@ -16,12 +16,12 @@ namespace Birdmap.BLL.Services
private const double centerLat = 48.275939;
private const double radius = 0.001;
private static readonly Random Rand = new Random();
private static readonly Random Rand = new();
private static readonly Lazy<ICollection<Device>> Devices = new Lazy<ICollection<Device>>(GenerateDevices);
private static readonly Lazy<ICollection<Device>> Devices = new(GenerateDevices);
private static readonly Dictionary<Guid, InputSingeResponse> TagToInput = new Dictionary<Guid, InputSingeResponse>();
private static readonly object InputLock = new object();
private static readonly Dictionary<Guid, InputSingeResponse> TagToInput = new();
private static readonly object InputLock = new();
private static ListOfDevices GenerateDevices()
{
@ -43,20 +43,20 @@ namespace Birdmap.BLL.Services
var sensors = new ArrayofSensors();
for (int s = 0; s < Rand.Next(1, 6); s++)
{
sensors.Add(new Sensor
sensors.Add(new()
{
Id = Guid.NewGuid(),
Status = GetRandomEnum<SensorStatus>(),
});
}
devices.Add(new Device
devices.Add(new()
{
Id = Guid.NewGuid(),
Sensors = sensors,
Status = GetRandomEnum<DeviceStatus>(),
Url = "dummyservice.device.url",
Coordinates = new Coordinates
Coordinates = new()
{
Latitude = GetPlusMinus(centerLat, radius),
Longitude = GetPlusMinus(centerLong, radius),
@ -150,10 +150,10 @@ namespace Birdmap.BLL.Services
{
if (!TagToInput.TryGetValue(tagID, out var value))
{
value = new InputSingeResponse
value = new()
{
Status = "Dummy_OK",
Message = new InputObject
Message = new()
{
Tag = tagID,
Date = DateTime.Now,

View File

@ -23,8 +23,9 @@ namespace Birdmap.BLL.Services
private System.Net.Http.HttpClient _httpClient;
private System.Lazy<Newtonsoft.Json.JsonSerializerSettings> _settings;
public LiveDummyService(System.Net.Http.HttpClient httpClient)
public LiveDummyService(string baseUrl, System.Net.Http.HttpClient httpClient)
{
_baseUrl = baseUrl;
_httpClient = httpClient;
_settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings);
}

View File

@ -23,8 +23,9 @@ namespace Birdmap.BLL.Services
private System.Net.Http.HttpClient _httpClient;
private System.Lazy<Newtonsoft.Json.JsonSerializerSettings> _settings;
public LiveInputService(System.Net.Http.HttpClient httpClient)
public LiveInputService(string baseUrl, System.Net.Http.HttpClient httpClient)
{
_baseUrl = baseUrl;
_httpClient = httpClient;
_settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings);
}

View File

@ -17,6 +17,11 @@ namespace Birdmap.BLL.Services
_context = context;
}
public Task<int> GetServiceCountAsync()
{
return _context.Services.CountAsync();
}
public async Task<Service> CreateServiceAsync(Service service)
{
_context.Services.Add(service);

View File

@ -2,6 +2,7 @@
using Birdmap.BLL.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http;
namespace Birdmap.BLL
{
@ -20,8 +21,20 @@ namespace Birdmap.BLL
}
else
{
services.AddTransient<IInputService, LiveInputService>();
services.AddTransient<IDeviceService, LiveDummyService>();
var baseUrl = configuration.GetValue<string>("ServicesBaseUrl");
services.AddTransient<IInputService, LiveInputService>(serviceProvider =>
{
var httpClient = serviceProvider.GetService<HttpClient>();
var service = new LiveInputService(baseUrl, httpClient);
return service;
});
services.AddTransient<IDeviceService, LiveDummyService>(serviceProvider =>
{
var httpClient = serviceProvider.GetService<HttpClient>();
var service = new LiveDummyService(baseUrl, httpClient);
return service;
});
}
return services;

View File

@ -22,10 +22,17 @@ namespace Birdmap.DAL
public void Initialize()
{
EnsureCreated();
AddDefaultUsers();
AddDefaultServices();
}
private void EnsureCreated()
{
_logger.LogInformation("Ensuring database is created...");
_context.Database.EnsureCreated();
}
private void AddDefaultServices()
{
_logger.LogInformation("Removing previously added default services...");

View File

@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Birdmap.Common", "Birdmap.C
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.WinForm", "MQTTnet.TestApp.WinForm\MQTTnet.TestApp.WinForm.csproj", "{E1707FE7-4A65-42AC-B71C-6CC1A55FC42A}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{9443433B-1D13-41F0-B345-B36ACD15EF81}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -39,6 +41,10 @@ Global
{E1707FE7-4A65-42AC-B71C-6CC1A55FC42A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1707FE7-4A65-42AC-B71C-6CC1A55FC42A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1707FE7-4A65-42AC-B71C-6CC1A55FC42A}.Release|Any CPU.Build.0 = Release|Any CPU
{9443433B-1D13-41F0-B345-B36ACD15EF81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9443433B-1D13-41F0-B345-B36ACD15EF81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9443433B-1D13-41F0-B345-B36ACD15EF81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9443433B-1D13-41F0-B345-B36ACD15EF81}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -0,0 +1 @@
[{"Topic":"devices/output","Payload":"eyJ0YWciOiJkNDhhODAwOC0wYjU2LTRkYzAtODYxMy0zMWY0MDY4OTk0ZDciLCJwcm9iYWJpbGl0eSI6MC4yNTMxNDI4NDgyNjMwOTU1fQ==","QualityOfServiceLevel":1,"Retain":true,"UserProperties":null,"ContentType":null,"ResponseTopic":null,"PayloadFormatIndicator":null,"MessageExpiryInterval":null,"TopicAlias":null,"CorrelationData":null,"SubscriptionIdentifiers":null}]

15
docker-compose.dcproj Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectVersion>2.1</ProjectVersion>
<DockerTargetOS>Linux</DockerTargetOS>
<ProjectGuid>9443433b-1d13-41f0-b345-b36acd15ef81</ProjectGuid>
<DockerLaunchAction>LaunchBrowser</DockerLaunchAction>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}</DockerServiceUrl>
<DockerServiceName>birdmap.api</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose.yml" />
<None Include=".dockerignore" />
</ItemGroup>
</Project>

46
docker-compose.yml Normal file
View File

@ -0,0 +1,46 @@
version: '3.4'
services:
db:
image: "mcr.microsoft.com/mssql/server:2019-latest"
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=RPSsql12345
birdmap.api:
image: ${DOCKER_REGISTRY-}birdmapapi
ports:
- "8000:80"
- "8001:443"
volumes:
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
build:
context: .
dockerfile: Birdmap.API/Dockerfile
depends_on:
- db
environment:
- ASPNETCORE_ENVIRONMENT=Docker
- ASPNETCORE_URLS=https://+;http://+
- ASPNETCORE_HTTPS_PORT=8001
- Birdmap_Kestrel__Certificates__Default__Password=certpass123
- Birdmap_Kestrel__Certificates__Default__Path=/root/.aspnet/https/aspnetapp.pfx
- Birdmap_Secret=7gz;]=bQe}n#3~RwC+Y<SrjoE:sHwO
- Birdmap_LocalDbConnectionString=Data Source=db;Initial Catalog=birdmap;User=sa;Password=RPSsql12345
- Birdmap_Defaults__Users__0__Name=admin
- Birdmap_Defaults__Users__0__Password=pass
- Birdmap_Defaults__Users__0__Role=Admin
- Birdmap_Defaults__Users__1__Name=user
- Birdmap_Defaults__Users__1__Password=pass
- Birdmap_Defaults__Users__1__Role=User
- Birdmap_Defaults__Services__Local-Database=https://localhost:8001/health
- Birdmap_Defaults__Services__KMLabz-Service=https://birb.k8s.kmlabz.com/devices
- Birdmap_UseDummyServices=true
- Birdmap_ServicesBaseUrl=https://birb.k8s.kmlabz.com/
- Birdmap_Mqtt__BrokerHostSettings__Host=localhost
- Birdmap_Mqtt__BrokerHostSettings__Port=1883
- Birdmap_Mqtt__ClientSettings__Id=ASP.NET Core client
- Birdmap_Mqtt__ClientSettings__Username=username
- Birdmap_Mqtt__ClientSettings__Password=password
- Birdmap_Mqtt__ClientSettings__Topic=devices/output

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 78 KiB

68
docs/thesis/Makefile Normal file
View File

@ -0,0 +1,68 @@
DOCUMENT=thesis
#MODE=-interaction=batchmode
all: clean xelatex
echo
xelatex: compile_xelatex
mv $(DOCUMENT)-xelatex.pdf ../pdf/$(DOCUMENT).pdf
compile_xelatex:
xelatex $(MODE) $(DOCUMENT)
bibtex $(DOCUMENT)
xelatex $(MODE) $(DOCUMENT)
xelatex $(MODE) $(DOCUMENT)
mv $(DOCUMENT).pdf $(DOCUMENT)-xelatex.pdf
pdflatex: compile_pdflatex
mv $(DOCUMENT)-pdflatex.pdf ../pdf/$(DOCUMENT).pdf
compile_pdflatex:
pdflatex $(MODE) $(DOCUMENT)
bibtex $(DOCUMENT)
pdflatex $(MODE) $(DOCUMENT)
pdflatex $(MODE) $(DOCUMENT)
mv $(DOCUMENT).pdf $(DOCUMENT)-pdflatex.pdf
lualatex: compile_lualatex
mv $(DOCUMENT)-lualatex.pdf ../pdf/$(DOCUMENT).pdf
compile_lualatex:
lualatex $(MODE) $(DOCUMENT)
bibtex $(DOCUMENT)
lualatex $(MODE) $(DOCUMENT)
lualatex $(MODE) $(DOCUMENT)
mv $(DOCUMENT).pdf $(DOCUMENT)-lualatex.pdf
switch_to_hungarian:
sed -i "s|^\\\input{include/thesis-en}|%\\\input{include/thesis-en}|" $(DOCUMENT).tex
sed -i "s|^%\\\input{include/thesis-hu}|\\\input{include/thesis-hu}|" $(DOCUMENT).tex
test_hu:
${MAKE} clean compile_xelatex
${MAKE} clean compile_pdflatex
${MAKE} clean compile_lualatex
mv $(DOCUMENT)-xelatex.pdf ../pdf/$(DOCUMENT)-xelatex-hu.pdf
mv $(DOCUMENT)-pdflatex.pdf ../pdf/$(DOCUMENT)-pdflatex-hu.pdf
mv $(DOCUMENT)-lualatex.pdf ../pdf/$(DOCUMENT)-lualatex-hu.pdf
switch_to_english:
sed -i "s|^\\\input{include/thesis-hu}|%\\\input{include/thesis-hu}|" $(DOCUMENT).tex
sed -i "s|^%\\\input{include/thesis-en}|\\\input{include/thesis-en}|" $(DOCUMENT).tex
test_en:
${MAKE} switch_to_english
${MAKE} clean compile_xelatex
${MAKE} clean compile_pdflatex
${MAKE} clean compile_lualatex
mv $(DOCUMENT)-xelatex.pdf ../pdf/$(DOCUMENT)-xelatex-en.pdf
mv $(DOCUMENT)-pdflatex.pdf ../pdf/$(DOCUMENT)-pdflatex-en.pdf
mv $(DOCUMENT)-lualatex.pdf ../pdf/$(DOCUMENT)-lualatex-en.pdf
${MAKE} switch_to_hungarian
test: test_hu test_en
echo
clean:
echo Cleaning temporary files...
rm -f *.aux *.dvi *.thm *.lof *.log *.lot *.fls *.out *.toc *.bbl *.blg

79
docs/thesis/bib/mybib.bib Normal file
View File

@ -0,0 +1,79 @@
@book{Wettl04,
author = {Ferenc Wettl and Gyula Mayer and Péter Szabó},
publisher = {Panem Könyvkiadó},
title = {\LaTeX~kézikönyv},
year = {2004},
}
@article{Candy86,
author = {James C. Candy},
journaltitle = {{IEEE} Trans.\ on Communications},
month = {01},
note = {\doi{10.1109/TCOM.1986.1096432}},
number = {1},
pages = {72--76},
title = {Decimation for Sigma Delta Modulation},
volume = {34},
year = {1986},
}
@inproceedings{Lee87,
author = {Wai L. Lee and Charles G. Sodini},
booktitle = {Proc.\ of the IEEE International Symposium on Circuits and Systems},
location = {Philadelphia, PA, USA},
month = {05~4--7},
pages = {459--462},
title = {A Topology for Higher Order Interpolative Coders},
vol = {2},
year = {1987},
}
@thesis{KissPhD,
author = {Peter Kiss},
institution = {Technical University of Timi\c{s}oara, Romania},
month = {04},
title = {Adaptive Digital Compensation of Analog Circuit Imperfections for Cascaded Delta-Sigma Analog-to-Digital Converters},
type = {phdthesis},
year = {2000},
}
@manual{Schreier00,
author = {Richard Schreier},
month = {01},
note = {\url{http://www.mathworks.com/matlabcentral/fileexchange/}},
organization = {Oregon State University},
title = {The Delta-Sigma Toolbox v5.2},
year = {2000},
}
@misc{DipPortal,
author = {{Budapesti Műszaki és Gazdaságtudományi Egyetem Villamosmérnöki és Informatikai Kar}},
howpublished = {\url{http://diplomaterv.vik.bme.hu/}},
title = {Diplomaterv portál (2011. február 26.)},
}
@incollection{Mkrtychev:1997,
author = {Mkrtychev, Alexey},
booktitle = {Logical Foundations of Computer Science},
doi = {10.1007/3-540-63045-7_27},
editor = {Adian, Sergei and Nerode, Anil},
isbn = {978-3-540-63045-6},
pages = {266-275},
publisher = {Springer Berlin Heidelberg},
series = {Lecture Notes in Computer Science},
title = {Models for the logic of proofs},
url = {http://dx.doi.org/10.1007/3-540-63045-7_27},
volume = {1234},
year = {1997},
}
@report{Jeney,
author = {Jeney, Gábor},
institution = {Budapesti Műszaki és Gazdaságtudományi Egyetem, Híradástechnikai Tanszék},
location = {Budapest},
note = {\url{http://www.mcl.hu/~jeneyg/kinezet.pdf}},
title = {Hogyan néz ki egy igényes dokumentum? {N}éhány szóban az alapvető tipográfiai szabályokról},
type = {techreport},
year = {2014},
}

View File

@ -0,0 +1,47 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@writefile{toc}{\select@language{magyar} \contentsline {chapter}{Kivonat}{i}{chapter*.2}\protected@file@percent }
\@writefile{toc}{\select@language{english} \contentsline {chapter}{Abstract}{iii}{chapter*.3}\protected@file@percent }
\@setckpt{content/abstract}{
\setcounter{page}{4}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
\setcounter{enumiii}{0}
\setcounter{enumiv}{0}
\setcounter{footnote}{0}
\setcounter{mpfootnote}{0}
\setcounter{part}{0}
\setcounter{chapter}{0}
\setcounter{section}{0}
\setcounter{subsection}{0}
\setcounter{subsubsection}{0}
\setcounter{paragraph}{0}
\setcounter{subparagraph}{0}
\setcounter{figure}{0}
\setcounter{table}{0}
\setcounter{footnote@add}{0}
\setcounter{footnote@ch}{0}
\setcounter{parentequation}{0}
\setcounter{Item}{0}
\setcounter{Hfootnote}{0}
\setcounter{bookmark@seq@number}{2}
\setcounter{lstnumber}{1}
\setcounter{endNonectr}{2}
\setcounter{currNonectr}{0}
\setcounter{caption@flags}{0}
\setcounter{continuedfloat}{0}
\setcounter{NAT@ctr}{0}
\setcounter{currexamplectr}{0}
\setcounter{endexamplectr}{0}
\setcounter{example}{0}
\setcounter{currdefinitionctr}{0}
\setcounter{enddefinitionctr}{0}
\setcounter{definition}{0}
\setcounter{currtheoremctr}{0}
\setcounter{endtheoremctr}{0}
\setcounter{theorem}{0}
\setcounter{section@level}{0}
\setcounter{lstlisting}{0}
\setcounter{romanPage}{4}
}

View File

@ -0,0 +1,63 @@
\pagenumbering{roman}
\setcounter{page}{1}
\selecthungarian
%----------------------------------------------------------------------------
% Abstract in Hungarian
%----------------------------------------------------------------------------
\chapter*{Kivonat}\addcontentsline{toc}{chapter}{Kivonat}
Adott egy tanszéken fejlesztett felhő alapú elosztott rendszer, melynek eszközei madárhangok azonosítására képesek.
Ha a rendszer úgy észleli, hogy az egyik álatala vezérelt eszköz mikrofonja felvételén madárhang található,
akkor riasztást kezdeményez az eszközön ezzel elijesztve a madarat ezáltal megóvva a növényzetet.
A rendszernek több kisebb komponense van, amelyek rengeteg adatot dolgoznak fel és nincs jelenleg egy olyan egységes grafikus felület ahol a rendszer teljes állapotát
át lehetne tekinteni, illetve ahol a feldolgozott adatokat vizualizálni lehetne.
A piacon létezik már több olyan szoftver csomag, amely hasonló problémákra próbál megoldást nyújtani, de ezek sem mindig
tudják kielégíteni azokat a speciális igényeket, amelyek egy ilyen rendszernél felmerülnek.
Jelen szakdolgozat célja egy olyan vizualizációs megoldás bemutatása, amelynek segítségével a rendszer könnyedén áttekinthető
és kezelhető. A tanszéki rendszer által kezelt eszközök a felületen is vezérelhetők
és azok működéséről különböző statisztikákat felhasználva egyszerűen értelmezhető diagrammok generálódnak.
A backend megvalósítására az ASP.NET Core-t választottam, mely platformfüggetlen megoldást nyújt a web kérések kiszolgálására.
A frontend-et a React.js használatával készítettem, mely segítségével egyszerűen és gyorsan lehet reszponzív felhasználói felületeket készíteni.
Dolgozatomban bemutatom a tanszéken fejlesztett rendszert, a mikroszolgáltatások vizualizálásának alternatíváit,
ismertetem az általam választott technológiákat és a készített alkalmazás felépítését.
\vfill
\selectenglish
%----------------------------------------------------------------------------
% Abstract in English
%----------------------------------------------------------------------------
\chapter*{Abstract}\addcontentsline{toc}{chapter}{Abstract}
There is a department developed cloud-based distributed system whose devices are capable of identifying bird sounds.
If the system detects a bird's voice on the recording of a microphone on one of the devices, it will trigger
an alarm on the device scaring the bird away thereby protecting the vegetation.
The system has several smaller components that process a lot of data and currently there is no unified graphical user interface where the overall state of the system
could be reviewed or where the processed data could be visualized.
There are already several software packages on the market that try to solve similar problems,
however they aren't always able to meet the special needs that arise with such a system.
The purpose of this thesis is to present a visualization solution that allows the users to easily review
and manage the system. The devices maintained by the department developed system can be controlled on the interface
and easy-to-understand diagrams are generated using statistics about their operation.
I chose ASP.NET Core as the backend framework, which provides a platform-independent solution for serving web requests.
The frontend was created using React.js, which allows for an easy and quick way to create responsive user interfaces.
In my thesis I present the system developed at the department, the alternatives of visualization of microservices,
I describe the technologies I have chosen and the structure of the application I have created.
\vfill
\selectthesislanguage
\newcounter{romanPage}
\setcounter{romanPage}{\value{page}}
\stepcounter{romanPage}

View File

@ -0,0 +1,49 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@writefile{toc}{\select@language{magyar} \contentsline {chapter}{Függelék}{9}{appendix*.6}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {section}{\numberline {F.1}A TeXstudio felülete}{9}{section.F.1}\protected@file@percent }
\@writefile{lof}{\select@language{magyar} \contentsline {figure}{\numberline {F.1.1.}{\ignorespaces A TeXstudio \LaTeX -szerkesztő.\relax }}{9}{figure.caption.7}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {section}{\numberline {F.2}Válasz az ,,Élet, a világmindenség, meg minden'' kérdésére}{10}{section.F.2}\protected@file@percent }
\@setckpt{content/appendices}{
\setcounter{page}{11}
\setcounter{equation}{2}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
\setcounter{enumiii}{0}
\setcounter{enumiv}{0}
\setcounter{footnote}{3}
\setcounter{mpfootnote}{0}
\setcounter{part}{0}
\setcounter{chapter}{6}
\setcounter{section}{2}
\setcounter{subsection}{0}
\setcounter{subsubsection}{0}
\setcounter{paragraph}{0}
\setcounter{subparagraph}{0}
\setcounter{figure}{0}
\setcounter{table}{0}
\setcounter{footnote@add}{0}
\setcounter{footnote@ch}{0}
\setcounter{parentequation}{0}
\setcounter{Item}{0}
\setcounter{Hfootnote}{3}
\setcounter{bookmark@seq@number}{24}
\setcounter{lstnumber}{1}
\setcounter{endNonectr}{5}
\setcounter{currNonectr}{0}
\setcounter{caption@flags}{0}
\setcounter{continuedfloat}{0}
\setcounter{NAT@ctr}{0}
\setcounter{currexamplectr}{0}
\setcounter{endexamplectr}{0}
\setcounter{example}{0}
\setcounter{currdefinitionctr}{0}
\setcounter{enddefinitionctr}{0}
\setcounter{definition}{0}
\setcounter{currtheoremctr}{0}
\setcounter{endtheoremctr}{0}
\setcounter{theorem}{0}
\setcounter{section@level}{1}
\setcounter{lstlisting}{0}
\setcounter{romanPage}{4}
}

View File

@ -0,0 +1,32 @@
%----------------------------------------------------------------------------
\appendix
%----------------------------------------------------------------------------
\chapter*{\fuggelek}\addcontentsline{toc}{chapter}{\fuggelek}
\setcounter{chapter}{\appendixnumber}
%\setcounter{equation}{0} % a fofejezet-szamlalo az angol ABC 6. betuje (F) lesz
\numberwithin{equation}{section}
\numberwithin{figure}{section}
\numberwithin{lstlisting}{section}
%\numberwithin{tabular}{section}
%----------------------------------------------------------------------------
\section{A TeXstudio felülete}
%----------------------------------------------------------------------------
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/TeXstudio.png}
\caption{A TeXstudio \LaTeX-szerkesztő.}
\end{figure}
%----------------------------------------------------------------------------
\clearpage\section{Válasz az ,,Élet, a világmindenség, meg minden'' kérdésére}
%----------------------------------------------------------------------------
A Pitagorasz-tételből levezetve
\begin{align}
c^2=a^2+b^2=42.
\end{align}
A Faraday-indukciós törvényből levezetve
\begin{align}
\rot E=-\frac{dB}{dt}\hspace{1cm}\longrightarrow \hspace{1cm}
U_i=\oint\limits_\mathbf{L}{\mathbf{E}\mathbf{dl}}=-\frac{d}{dt}\int\limits_A{\mathbf{B}\mathbf{da}}=42.
\end{align}

View File

@ -0,0 +1,45 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@setckpt{content/bir}{
\setcounter{page}{3}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
\setcounter{enumiii}{0}
\setcounter{enumiv}{0}
\setcounter{footnote}{0}
\setcounter{mpfootnote}{0}
\setcounter{part}{0}
\setcounter{chapter}{1}
\setcounter{section}{3}
\setcounter{subsection}{0}
\setcounter{subsubsection}{0}
\setcounter{paragraph}{0}
\setcounter{subparagraph}{0}
\setcounter{figure}{0}
\setcounter{table}{0}
\setcounter{footnote@add}{0}
\setcounter{footnote@ch}{0}
\setcounter{parentequation}{0}
\setcounter{Item}{0}
\setcounter{Hfootnote}{0}
\setcounter{bookmark@seq@number}{6}
\setcounter{lstnumber}{1}
\setcounter{endNonectr}{2}
\setcounter{currNonectr}{0}
\setcounter{caption@flags}{0}
\setcounter{continuedfloat}{0}
\setcounter{NAT@ctr}{0}
\setcounter{currexamplectr}{0}
\setcounter{endexamplectr}{0}
\setcounter{example}{0}
\setcounter{currdefinitionctr}{0}
\setcounter{enddefinitionctr}{0}
\setcounter{definition}{0}
\setcounter{currtheoremctr}{0}
\setcounter{endtheoremctr}{0}
\setcounter{theorem}{0}
\setcounter{section@level}{1}
\setcounter{lstlisting}{0}
\setcounter{romanPage}{3}
}

View File

@ -0,0 +1,65 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@writefile{toc}{\select@language{magyar} \contentsline {chapter}{\numberline {2}A Birdnetes részletes bemutatása}{3}{chapter.2}\protected@file@percent }
\@writefile{lof}{\select@language{magyar} \addvspace {10\p@ }}
\@writefile{lot}{\select@language{magyar} \addvspace {10\p@ }}
\newlabel{chapt:birdnetes-introduction}{{2}{3}{A szakdolgozat felépítése}{chapter.2}{}}
\@writefile{toc}{\select@language{magyar} \contentsline {section}{\numberline {2.1}Gyors elméleti összefoglaló}{3}{section.2.1}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {subsection}{\numberline {2.1.1}Cloud, felhő}{3}{subsection.2.1.1}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {subsubsection}{\numberline {2.1.1.1}Mikroszolgáltatások}{3}{subsubsection.2.1.1.1}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {subsubsection}{\numberline {2.1.1.2}Konténerek}{4}{subsubsection.2.1.1.2}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {subsubsection}{\numberline {2.1.1.3}Kubernetes}{4}{subsubsection.2.1.1.3}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {subsection}{\numberline {2.1.2}MQTT}{4}{subsection.2.1.2}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {subsection}{\numberline {2.1.3}Open API}{4}{subsection.2.1.3}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {section}{\numberline {2.2}Rendszerszintű architektúra}{5}{section.2.2}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {subsection}{\numberline {2.2.1}Főbb komponensek}{5}{subsection.2.2.1}\protected@file@percent }
\@writefile{lof}{\select@language{magyar} \contentsline {figure}{\numberline {2.1.}{\ignorespaces A Birdnetes rendszer architektúrája\relax }}{5}{figure.caption.4}\protected@file@percent }
\providecommand*\caption@xref[2]{\@setref\relax\@undefined{#1}}
\newlabel{fig:birdnetes-components}{{2.1}{5}{A Birdnetes rendszer architektúrája\relax }{figure.caption.4}{}}
\@writefile{toc}{\select@language{magyar} \contentsline {subsubsection}{\numberline {2.2.1.1}Input Service}{5}{subsubsection.2.2.1.1}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {subsubsection}{\numberline {2.2.1.2}AI Service}{6}{subsubsection.2.2.1.2}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {subsubsection}{\numberline {2.2.1.3}Guard Service}{6}{subsubsection.2.2.1.3}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {subsubsection}{\numberline {2.2.1.4}Command and Control Service}{6}{subsubsection.2.2.1.4}\protected@file@percent }
\@setckpt{content/birdnetes-introduction}{
\setcounter{page}{7}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
\setcounter{enumiii}{0}
\setcounter{enumiv}{0}
\setcounter{footnote}{3}
\setcounter{mpfootnote}{0}
\setcounter{part}{0}
\setcounter{chapter}{2}
\setcounter{section}{2}
\setcounter{subsection}{1}
\setcounter{subsubsection}{4}
\setcounter{paragraph}{0}
\setcounter{subparagraph}{0}
\setcounter{figure}{1}
\setcounter{table}{0}
\setcounter{footnote@add}{0}
\setcounter{footnote@ch}{0}
\setcounter{parentequation}{0}
\setcounter{Item}{0}
\setcounter{Hfootnote}{3}
\setcounter{bookmark@seq@number}{20}
\setcounter{lstnumber}{1}
\setcounter{endNonectr}{3}
\setcounter{currNonectr}{0}
\setcounter{caption@flags}{0}
\setcounter{continuedfloat}{0}
\setcounter{NAT@ctr}{0}
\setcounter{currexamplectr}{0}
\setcounter{endexamplectr}{0}
\setcounter{example}{0}
\setcounter{currdefinitionctr}{0}
\setcounter{enddefinitionctr}{0}
\setcounter{definition}{0}
\setcounter{currtheoremctr}{0}
\setcounter{endtheoremctr}{0}
\setcounter{theorem}{0}
\setcounter{section@level}{3}
\setcounter{lstlisting}{0}
\setcounter{romanPage}{4}
}

View File

@ -0,0 +1,122 @@
%----------------------------------------------------------------------------
\chapter{A Birdnetes részletes bemutatása}
\label{chapt:birdnetes-introduction}
%----------------------------------------------------------------------------
Ebben a fejezetben ismertetem a Birdnetes mikroszolgáltatás rendszerének architektúráját.
Részletesen kifejtem az alkalmazásom szempontjából fontos komponensek feladatát és működését.
Majd egy példával ábrázolom a rendszer hangfelismerő folyamatát.
%----------------------------------------------------------------------------
\section{Gyors elméleti összefoglaló}
%----------------------------------------------------------------------------
Ez a szakasz nem azt a célt szolgálja, hogy minnél részletesebb képet mutasson az itt leírt technológiákról.
Ez csupán egy rövid összefoglaló a Birdnetes működésének megértése szempontjából elengedhetetlen technológiákról és elvekről,
hogy valamilyen szinten tisztában legyünk a fejezetben elhangzó kifejezésekkel.
%----------------------------------------------------------------------------
\subsection{Cloud, felhő}
%----------------------------------------------------------------------------
A cloud lényegében annyit jelent, hogy a szervert, amin az alkalmazás fut, nem a fejlesztőnek kell üzemeltetnie,
hanem valamilyen másik szervezet\footnotemark által vannak karban tartva.
Ez több okból is hasznos:
\begin{itemize}
\item Olcsóbb. Nem kell berendezéseket vásárolni, nincs üzemeltetési díj. Az egyetlen költség a bérlés, ami általában töredéke annak, amit akkor fizetnénk ha magunk csinálnánk az egészet.
\item Gyorsabb fejlesztés. Az alkalmazás futtatására használt szervereket általában a fejlesztő nem látja, ezekkel nem kell foglalkoznia. Ha az alkalmazásnak hirtelen nagyobb erőforrás igénye lesz, a rendszer automatikusan skálázódik.
\item Nagyobb megbízhatóság. Az ilyen szolgáltatást nyújtó szervezeteknek ez az egyik legnagyobb feladata. Az alkalmazás bárhol és bármikor elérhető.
\end{itemize}
\footnotetext{Ilyenek például a Microsoft Azure, az Amazon Web Services vagy a Google Cloud.}
%----------------------------------------------------------------------------
\subsubsection{Mikroszolgáltatások}
%----------------------------------------------------------------------------
A mikroszolgáltatások nem sok mindenben különböznek egy általános szolgáltatástól.
Ugyan úgy valamilyen kéréseket kiszolgáló egységek, legyen az web kérések kiszolgálása HTTP-n keresztül
vagy akár parancssori utasítások feldolgozása. Az egyetlen fő különbség az a szolgáltatások felelősségköre.
A mikroszolgáltatások fejlesztésénél a fejlesztők elsősorban arra törekednek, hogy egy komponensnek minnél kevesebb feladata és függősége legyen,
ezzel megnő a tesztelhetőség és könyebb a skálázhatóság.
%----------------------------------------------------------------------------
\subsubsection{Konténerek}
%----------------------------------------------------------------------------
A konténer technikailag semmivel sem több mint egy Linux-on futó processz amelyre különböző korlátozásokat szabtak.
Ilyen korlátozások lehetnek például, hogy a konténer nem látja a teljes fájlrendszert, annak csak egy kijelölt részét,
megadható a konténer által használható processzor és memória igény vagy akár korlátozható az is, hogy a konténer hogyan használhatja a hálózatot.
Léteznek eszközök, például a Docker\footnote{https://www.docker.com/}, mely lehetővé teszi a fejlesztők számára az ilyen konténerek könnyed létrehozását és futtatását.
%----------------------------------------------------------------------------
\subsubsection{Kubernetes}
%----------------------------------------------------------------------------
A Kubernetes\footnote{https://kubernetes.io/} az ilyen komplex konténerizált mikroszolgáltatás rendszerek menedzselésének könnyítését szolgálja.
Kihasználja és ötvözi az imént említett technológiák előnyeit, hogy egy robosztus rendszert alkosson.
Használatával felgyorsulhat és automatizált lehet az egyes konténerek telepítése, futtatása, de talán a legfőbb előnye,
hogy segítségével könnyedén megoldható a rendszert ért terhelési igények szerinti dinamikus skálázódás.
Azok a mikroszolgáltatások, amikre a rendszernek épp nincs szüksége, nem futnak, nem igényelnek erőforrást a szerveren,
így nem kell utánnuk fizetni sem. Ezzel ellentétben, ha valamely szolgáltatás után hirtelen megnő az igény,
akkor az könnyedén duplikálható.
%----------------------------------------------------------------------------
\subsection{MQTT}
%----------------------------------------------------------------------------
Az MQTT (Message Queue Telemetry Transport) az egy kliens-szerver publish/subscribe üzenetküldő protokoll. Könnyű implementálni és alacsony a sávszélesség igénye,
mellyel tökéletes jelöltje a Machine to Machine (M2M), illetve az Internet of Things (IoT) kommunikáció megvalósítására.
Működéséhez szükség van egy szerverre, amelynek feladata a beérkező üzenetek továbbküldése témák alapján. Egyes kliensek fel tudnak iratkozni bizonyos témákra, míg más kliensek publikálnak
és a szerver levezényli a két fél között a kommunikációt.
%----------------------------------------------------------------------------
\subsection{Open API}
%----------------------------------------------------------------------------
Az Open API egy nyilvános alkalmazás-programozási leíró, amely a fejlesztők számára hozzáférést biztosít egy másik alkalmazáshoz.
Az API-k lírják és meghatározzák, hogy egy alkalmazás hogyan kommunikálhat egy másikkal,
melyet használva a fejlesztők könnyedén képesek a kommunikációra képes kódot írni vagy generálni.
%----------------------------------------------------------------------------
\section{Rendszerszintű architektúra}
%----------------------------------------------------------------------------
A Birdnetes fejlesztése során kifejezetten fontos szerepe volt a mikroszolgáltatás alapú rendszerek elvei követésének.
A rendszer egy Kubernetes klaszterben van telepítve és több kisebb komponensből áll, melyek egymás között a HTTP és az MQTT protokollok segítségével kommunikálnak.
A rendszer összes szolgáltatásának van egy Open API leírója, melyet használva hamar volt egy olyan kódbázisom, amely képes volt a rendszerrel való kommunikációra.
%----------------------------------------------------------------------------
\subsection{Főbb komponensek}
%----------------------------------------------------------------------------
A \ref{fig:birdnetes-components}-es ábrán láthatóak a rendszer komponensei, melyek mind egy-egy mikroszolgáltatás.
Az egymás mellett lévő kék levélborítékok az MQTT kommunikációt jelölik,
amellyel például a természetben elhelyezett eszközök felé irányuló kommunikációja is történik.
A következő alszakaszokban bemutatom az alkalmazásom szempontjából fontosabb komponenseket.
\begin{figure}[!ht]
\centering
\includegraphics[width=150mm, keepaspectratio]{figures/architecture-redesigned.png}
\caption{A Birdnetes rendszer architektúrája}
\label{fig:birdnetes-components}
\end{figure}
%----------------------------------------------------------------------------
\subsubsection{Input Service}
%----------------------------------------------------------------------------
A kihelyezett IoT eszközök által felvett hangfájlok ezen a komponensen keresztül érkeznek be a rendszerbe.
Itt történik a hanganyaghoz tartozó metaadatok lementése az Input Service saját adatbázisába.
Ilyenek például a beküldő eszköz azonosítója, a beérkezés dátuma vagy a hangüzenet rendszerszintű egyedi azonosítója.
Amint a szolgáltatás a berékezett üzenettel kapcsolatban elvégezte az összes feladatát,
publikál egy üzenetet az MQTT üzenetsorra a többi kliensnek feldolgozásra.
%----------------------------------------------------------------------------
\subsubsection{AI Service}
%----------------------------------------------------------------------------
Az AI Service példányai fogadják az Input Service-től érkező üzeneteket és elkezdik klasszifikálni az abban található hanganyagot.
Meghatározzák, hogy a hanganyag mekkora valószínűséggel volt seregély hang vagy sem.
Ennek eredményét a hangminta egyedi azonosítójával együtt publikálják egy másik üzenetsoron.
%----------------------------------------------------------------------------
\subsubsection{Guard Service}
%----------------------------------------------------------------------------
A Guard Service feliratkozik az AI Service által publikált üzenetek témájára
és valamilyen valószínűségi kritérium alapján eldönti, hogy a hangminta tartalmaz-e seregély hangot.
Ha igen, akkor az üzenetsoron küld egy riasztás parancsot a hanganyagot küldő eszköznek.
%----------------------------------------------------------------------------
\subsubsection{Command and Control Service}
%----------------------------------------------------------------------------
A Command and Control Service az előzőekkel ellentétben nem vesz részt a minták fogadásában, feldolgozásában vagy kezelésében.
Felelősége az eszközök és azok szenzorai állapotának menedzselése és követése.
Ezen keresztül lehet az egyes eszközöket ki- és bekapcsolni.

View File

@ -0,0 +1,51 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@writefile{toc}{\select@language{magyar} \contentsline {chapter}{\numberline {1}Bevezetés}{1}{chapter.1}\protected@file@percent }
\@writefile{lof}{\select@language{magyar} \addvspace {10\p@ }}
\@writefile{lot}{\select@language{magyar} \addvspace {10\p@ }}
\@writefile{toc}{\select@language{magyar} \contentsline {section}{\numberline {1.1}A probléma}{1}{section.1.1}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {section}{\numberline {1.2}A megoldás}{1}{section.1.2}\protected@file@percent }
\@writefile{toc}{\select@language{magyar} \contentsline {section}{\numberline {1.3}A szakdolgozat felépítése}{2}{section.1.3}\protected@file@percent }
\@setckpt{content/introduction}{
\setcounter{page}{3}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
\setcounter{enumiii}{0}
\setcounter{enumiv}{0}
\setcounter{footnote}{0}
\setcounter{mpfootnote}{0}
\setcounter{part}{0}
\setcounter{chapter}{1}
\setcounter{section}{3}
\setcounter{subsection}{0}
\setcounter{subsubsection}{0}
\setcounter{paragraph}{0}
\setcounter{subparagraph}{0}
\setcounter{figure}{0}
\setcounter{table}{0}
\setcounter{footnote@add}{0}
\setcounter{footnote@ch}{0}
\setcounter{parentequation}{0}
\setcounter{Item}{0}
\setcounter{Hfootnote}{0}
\setcounter{bookmark@seq@number}{6}
\setcounter{lstnumber}{1}
\setcounter{endNonectr}{2}
\setcounter{currNonectr}{0}
\setcounter{caption@flags}{0}
\setcounter{continuedfloat}{0}
\setcounter{NAT@ctr}{0}
\setcounter{currexamplectr}{0}
\setcounter{endexamplectr}{0}
\setcounter{example}{0}
\setcounter{currdefinitionctr}{0}
\setcounter{enddefinitionctr}{0}
\setcounter{definition}{0}
\setcounter{currtheoremctr}{0}
\setcounter{endtheoremctr}{0}
\setcounter{theorem}{0}
\setcounter{section@level}{1}
\setcounter{lstlisting}{0}
\setcounter{romanPage}{4}
}

View File

@ -0,0 +1,38 @@
%----------------------------------------------------------------------------
\chapter{\bevezetes}
%----------------------------------------------------------------------------
Szőlőtulajdonosoknak éves szinten jelentős kárt okoznak a seregélyek, akik előszeretettel választják táplálékul a megtermelt szőlőt.
Erre a problémára dolgoztak ki a tanszéken diáktársaim egy felhő alapú konténerizált rendszert, a Birdnetes-t
mely a természetben elkelyezett eszközökkel kommunikál, azokat vezérli.
Az eszközök bizonyos időközönként hangfelvételt készítenek a környezetükről,
majd valamilyen formában elküldik ezeket a felvételeket a központi rendszernek,
amely egy erre a célra kifejlesztett mesterséges intelligenciát használva eldönti
a felvételről, hogy azon található-e seregély hang vagy sem.
Ha igen akkor jelez a felvételt küldő eszköznek, hogy szólaltassa meg a riasztó
berendezését, hogy elijessze a madarakat.
%----------------------------------------------------------------------------
\section{A probléma}
%----------------------------------------------------------------------------
A jelen rendszer használata során nincs vizuális visszacsatolás az esetleges riasztásokról azok gyakoriságáról
és a rendszer állapotáról sem. Különböző diagnosztikai eszközök ugyan implementálva lettek mint például
a logolás vagy a hiba bejelentés, de ezek használata nehézkes, nem kézenfekvő.
Szükség van valamire amivel egy helyen és egyszerűen lehet kezelni és felügyelni a rendszer egyes elemeit.
%----------------------------------------------------------------------------
\section{A megoldás}
%----------------------------------------------------------------------------
A jelen szakdolgozat egy olyan webes alkalmazás elkészítését dokumentálja, melyel a felhasználók képesek
a természetben elhelyezett eszközök állapotát vizsgálni, azokat akár ki és bekapcsolni igény szerint.
Az egyes rendszer eseményeket vizsgálva a szoftver statisztikákat készít, melyeket különböző diagrammokon ábrázolok.
Ilyen statisztikák például, hogy időben melyik eszköz mikor észlelt madár hangot, vagy hogy hány hang üzenet érkezik
az eszközöktől másodpercenként.
%----------------------------------------------------------------------------
\section{A szakdolgozat felépítése}
%----------------------------------------------------------------------------
A szakdolgozatom első részében, a \ref{chapt:birdnetes-introduction}. fejezetben, bemutatom a Birdnetes felépítését, az egyes komponensek közötti kapcsolatokat és a technológiát, amire épült.
A 3. fejezetben ismertetem a jelenleg az iparban is használt mikroszolgáltatás működését vizualizáló alternatívákat, majd a saját megoldásom tervezetét, az arra vonatkozó elvárásokat.
A 4. fejezetben az alkalmazásom által használt technológiákat mutatom be, ezzel előkészítve az 5. és 6. fejezetet, ahol ismertetem a szerver- és kliensalkalmazások felépítését.
A 7. és 8. fejezet az alkalmazás teszteléséről és telepítéséről szól.
Az utolsó fejezetben értékelem a munkám eredményét, levonom a tapasztalatokat és bemutatok néhány továbbfejlesztési lehetőséget.

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

View File

@ -0,0 +1,7 @@
@echo off
for %%j in (*.eps) do (
echo converting file "%%j"
epstopdf "%%j"
)
echo done .

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

1910
docs/thesis/huplain.bst Normal file

File diff suppressed because it is too large Load Diff

62
docs/thesis/ideas.txt Normal file
View File

@ -0,0 +1,62 @@
Forma
- Kivonat
- Abstract
- Bevezetés
- Az alaphelyzet
- A probléma
- A megoldás
- Diplomaterv felépítésének bemutatása
- Az alaphelyzet részletes bemutatása
- mikroszolgáltatás alapú rendszerek alapelvei
- a tanszéken fejlesztett elosztott madárhang azonosító megoldás
teljeskörű bemutatása
- A megoldás tervezete
- mikroszolgáltatások működését vizualizáló alternatívák
- használati tervek
- design terverk
- A használt technológiák
- fejlesztési folyamat
- fejlesztő környezet(ek)
- agilis módszertan
- trello
- verziókezelés git
- Backend
- ASP.NET Core 5
- SignalR
- EFCore 5
- Frontend
- material-ui
- React.js
- Apexcharts
- google-map-react
- Tesztkörnyezet
- WinForms .NET 5 (githubról)
- Kubenetes
- Backend
- Felépítés architektúra
- ábra
- konfigurálás
- startup mindenhol
- ...
- DAL
- EFCore
- Entitások
- Seed
- ...
- BLL
- servicek
- API
- controllerek
- servicek
- mqtt
- signalR
- Frontend
- felépítés architektúra
- App and navigation
- Components and services
- Tesztkörnyezet
- Kubenetes
- Összefoglaló
- Értékelés, eredmények, tapasztalatok.
- Továbbfejlesztési lehetőségek

View File

@ -0,0 +1,44 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@setckpt{include/declaration}{
\setcounter{page}{2}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
\setcounter{enumiii}{0}
\setcounter{enumiv}{0}
\setcounter{footnote}{0}
\setcounter{mpfootnote}{0}
\setcounter{part}{0}
\setcounter{chapter}{0}
\setcounter{section}{0}
\setcounter{subsection}{0}
\setcounter{subsubsection}{0}
\setcounter{paragraph}{0}
\setcounter{subparagraph}{0}
\setcounter{figure}{0}
\setcounter{table}{0}
\setcounter{footnote@add}{0}
\setcounter{footnote@ch}{0}
\setcounter{parentequation}{0}
\setcounter{Item}{0}
\setcounter{Hfootnote}{0}
\setcounter{bookmark@seq@number}{0}
\setcounter{lstnumber}{1}
\setcounter{endNonectr}{2}
\setcounter{currNonectr}{0}
\setcounter{caption@flags}{0}
\setcounter{continuedfloat}{0}
\setcounter{NAT@ctr}{0}
\setcounter{currexamplectr}{0}
\setcounter{endexamplectr}{0}
\setcounter{example}{0}
\setcounter{currdefinitionctr}{0}
\setcounter{enddefinitionctr}{0}
\setcounter{definition}{0}
\setcounter{currtheoremctr}{0}
\setcounter{endtheoremctr}{0}
\setcounter{theorem}{0}
\setcounter{section@level}{0}
\setcounter{lstlisting}{0}
}

View File

@ -0,0 +1,32 @@
\selectlanguage{magyar}
\pagenumbering{gobble}
%--------------------------------------------------------------------------------------
% Nyilatkozat
%--------------------------------------------------------------------------------------
\begin{center}
\large
\textbf{HALLGATÓI NYILATKOZAT}\\
\end{center}
Alulírott \emph{\vikszerzoVezeteknev{} \vikszerzoKeresztnev}, szigorló hallgató kijelentem, hogy ezt a \vikmunkatipusat{} meg nem engedett segítség nélkül, saját magam készítettem, csak a megadott forrásokat (szakirodalom, eszközök stb.) használtam fel. Minden olyan részt, melyet szó szerint, vagy azonos értelemben, de átfogalmazva más forrásból átvettem, egyértelműen, a forrás megadásával megjelöltem.
Hozzájárulok, hogy a jelen munkám alapadatait (szerző(k), cím, angol és magyar nyelvű tartalmi kivonat, készítés éve, konzulens(ek) neve) a BME VIK nyilvánosan hozzáférhető elektronikus formában, a munka teljes szövegét pedig az egyetem belső hálózatán keresztül (vagy autentikált felhasználók számára) közzétegye. Kijelentem, hogy a benyújtott munka és annak elektronikus verziója megegyezik. Dékáni engedéllyel titkosított diplomatervek esetén a dolgozat szövege csak 3 év eltelte után válik hozzáférhetővé.
\begin{flushleft}
\vspace*{1cm}
Budapest, \today
\end{flushleft}
\begin{flushright}
\vspace*{1cm}
\makebox[7cm]{\rule{6cm}{.4pt}}\\
\makebox[7cm]{\emph{\vikszerzoVezeteknev{} \vikszerzoKeresztnev}}\\
\makebox[7cm]{hallgató}
\end{flushright}
\thispagestyle{empty}
\vfill
\clearpage
\thispagestyle{empty} % an empty page
\selectthesislanguage

View File

@ -0,0 +1,54 @@
\selecthungarian
%--------------------------------------------------------------------------------------
% Rovid formai es tartalmi tajekoztato
%--------------------------------------------------------------------------------------
\footnotesize
\begin{center}
\large
\textbf{\Large Általános információk, a diplomaterv szerkezete}\\
\end{center}
A diplomaterv szerkezete a BME Villamosmérnöki és Informatikai Karán:
\begin{enumerate}
\item Diplomaterv feladatkiírás
\item Címoldal
\item Tartalomjegyzék
\item A diplomatervező nyilatkozata az önálló munkáról és az elektronikus adatok kezeléséről
\item Tartalmi összefoglaló magyarul és angolul
\item Bevezetés: a feladat értelmezése, a tervezés célja, a feladat indokoltsága, a diplomaterv felépítésének rövid összefoglalása
\item A feladatkiírás pontosítása és részletes elemzése
\item Előzmények (irodalomkutatás, hasonló alkotások), az ezekből levonható következtetések
\item A tervezés részletes leírása, a döntési lehetőségek értékelése és a választott megoldások indoklása
\item A megtervezett műszaki alkotás értékelése, kritikai elemzése, továbbfejlesztési lehetőségek
\item Esetleges köszönetnyilvánítások
\item Részletes és pontos irodalomjegyzék
\item Függelék(ek)
\end{enumerate}
Felhasználható a következő oldaltól kezdődő \LaTeX diplomatervsablon dokumentum tartalma.
A diplomaterv szabványos méretű A4-es lapokra kerüljön. Az oldalak tükörmargóval készüljenek (mindenhol 2,5~cm, baloldalon 1~cm-es kötéssel). Az alapértelmezett betűkészlet a 12 pontos Times New Roman, másfeles sorközzel, de ettől kismértékben el lehet térni, ill. más betűtípus használata is megengedett.
Minden oldalon -- az első négy szerkezeti elem kivételével -- szerepelnie kell az oldalszámnak.
A fejezeteket decimális beosztással kell ellátni. Az ábrákat a megfelelő helyre be kell illeszteni, fejezetenként decimális számmal és kifejező címmel kell ellátni. A fejezeteket decimális aláosztással számozzuk, maximálisan 3 aláosztás mélységben (pl. 2.3.4.1.). Az ábrákat, táblázatokat és képleteket célszerű fejezetenként külön számozni (pl. 2.4. ábra, 4.2. táblázat vagy képletnél (3.2)). A fejezetcímeket igazítsuk balra, a normál szövegnél viszont használjunk sorkiegyenlítést. Az ábrákat, táblázatokat és a hozzájuk tartozó címet igazítsuk középre. A cím a jelölt rész alatt helyezkedjen el.
A képeket lehetőleg rajzoló programmal készítsék el, az egyenleteket egyenlet-szerkesztő segítségével írják le (A \LaTeX~ehhez kézenfekvő megoldásokat nyújt).
Az irodalomjegyzék szövegközi hivatkozása történhet sorszámozva (ez a preferált megoldás) vagy a Harvard-rendszerben (a szerző és az évszám megadásával). A teljes lista névsor szerinti sorrendben a szöveg végén szerepeljen (sorszámozott irodalmi hivatkozások esetén hivatkozási sorrendben). A szakirodalmi források címeit azonban mindig az eredeti nyelven kell megadni, esetleg zárójelben a fordítással. A listában szereplő valamennyi publikációra hivatkozni kell a szövegben (a \LaTeX-sablon a Bib\TeX~segítségével mindezt automatikusan kezeli). Minden publikáció a szerzők után a következő adatok szerepelnek: folyóirat cikkeknél a pontos cím, a folyóirat címe, évfolyam, szám, oldalszám tól-ig. A folyóiratok címét csak akkor rövidítsük, ha azok nagyon közismertek vagy nagyon hosszúak. Internetes hivatkozások megadásakor fontos, hogy az elérési út előtt megadjuk az oldal tulajdonosát és tartalmát (mivel a link egy idő után akár elérhetetlenné is válhat), valamint az elérés időpontját.
\vspace{5mm}
Fontos:
\begin{itemize}
\item A szakdolgozatkészítő / diplomatervező nyilatkozata (a jelen sablonban szereplő szövegtartalommal) kötelező előírás, Karunkon ennek hiányában a szakdolgozat/diplomaterv nem bírálható és nem védhető!
\item Mind a dolgozat, mind a melléklet maximálisan 15~MB méretű lehet!
\end{itemize}
\vspace{5mm}
\begin{center}
Jó munkát, sikeres szakdolgozatkészítést, ill. diplomatervezést kívánunk!
\end{center}
\normalsize
\selectthesislanguage

View File

@ -0,0 +1,64 @@
% thanks to http://tex.stackexchange.com/a/47579/71109
\usepackage{ifxetex}
\usepackage{ifluatex}
\newif\ifxetexorluatex % a new conditional starts as false
\ifnum 0\ifxetex 1\fi\ifluatex 1\fi>0
\xetexorluatextrue
\fi
\ifxetexorluatex
\usepackage{fontspec}
\else
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[lighttt]{lmodern}
\fi
\usepackage[english,magyar]{babel} % Alapértelmezés szerint utoljára definiált nyelv lesz aktív, de később külön beállítjuk az aktív nyelvet.
%\usepackage{cmap}
\usepackage{amsfonts,amsmath,amssymb} % Mathematical symbols.
%\usepackage[ruled,boxed,resetcount,linesnumbered]{algorithm2e} % For pseudocodes. % beware: this is not compatible with LuaLaTeX, see http://tex.stackexchange.com/questions/34814/lualatex-and-algorithm2e
\usepackage{booktabs} % For publication quality tables for LaTeX
\usepackage{graphicx}
%\usepackage{fancyhdr}
%\usepackage{lastpage}
\usepackage{anysize}
%\usepackage{sectsty}
\usepackage{setspace} % For setting line spacing
\usepackage[unicode]{hyperref} % For hyperlinks in the generated document.
\usepackage{xcolor}
\usepackage{listings} % For source code snippets.
\usepackage[amsmath,thmmarks]{ntheorem} % Theorem-like environments.
\usepackage[hang]{caption}
\onehalfspacing
\newcommand{\selecthungarian}{
\selectlanguage{magyar}
\setlength{\parindent}{2em}
\setlength{\parskip}{0em}
\frenchspacing
}
\newcommand{\selectenglish}{
\selectlanguage{english}
\setlength{\parindent}{0em}
\setlength{\parskip}{0.5em}
\nonfrenchspacing
\renewcommand{\figureautorefname}{Figure}
\renewcommand{\tableautorefname}{Table}
\renewcommand{\partautorefname}{Part}
\renewcommand{\chapterautorefname}{Chapter}
\renewcommand{\sectionautorefname}{Section}
\renewcommand{\subsectionautorefname}{Section}
\renewcommand{\subsubsectionautorefname}{Section}
}
\usepackage[numbers]{natbib}
\usepackage{xspace}

View File

@ -0,0 +1,122 @@
%--------------------------------------------------------------------------------------
% Page layout setup
%--------------------------------------------------------------------------------------
% we need to redefine the pagestyle plain
% another possibility is to use the body of this command without \fancypagestyle
% and use \pagestyle{fancy} but in that case the special pages
% (like the ToC, the References, and the Chapter pages)remain in plane style
\pagestyle{plain}
\marginsize{35mm}{25mm}{15mm}{15mm}
\setcounter{tocdepth}{3}
%\sectionfont{\large\upshape\bfseries}
\setcounter{secnumdepth}{3}
\sloppy % Margón túllógó sorok tiltása.
\widowpenalty=10000 \clubpenalty=10000 %A fattyú- és árvasorok elkerülése
\def\hyph{-\penalty0\hskip0pt\relax} % Kötőjeles szavak elválasztásának engedélyezése
%--------------------------------------------------------------------------------------
% Setup hyperref package
%--------------------------------------------------------------------------------------
\hypersetup{
% bookmarks=true, % show bookmarks bar?
unicode=true, % non-Latin characters in Acrobat's bookmarks
pdftitle={\vikcim}, % title
pdfauthor={\szerzoMeta}, % author
pdfsubject={\vikdoktipus}, % subject of the document
pdfcreator={\szerzoMeta}, % creator of the document
pdfproducer={}, % producer of the document
pdfkeywords={}, % list of keywords (separate then by comma)
pdfnewwindow=true, % links in new window
colorlinks=true, % false: boxed links; true: colored links
linkcolor=black, % color of internal links
citecolor=black, % color of links to bibliography
filecolor=black, % color of file links
urlcolor=black % color of external links
}
%--------------------------------------------------------------------------------------
% Set up listings
%--------------------------------------------------------------------------------------
\definecolor{lightgray}{rgb}{0.95,0.95,0.95}
\lstset{
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
% commentstyle=\color{green}, % for e.g. green comments
stringstyle=\scriptsize,
showstringspaces=false, % no special string spaces
aboveskip=3pt,
belowskip=3pt,
backgroundcolor=\color{lightgray},
columns=flexible,
keepspaces=true,
escapeinside={(*@}{@*)},
captionpos=b,
breaklines=true,
frame=single,
float=!ht,
tabsize=2,
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
}
%--------------------------------------------------------------------------------------
% Set up theorem-like environments
%--------------------------------------------------------------------------------------
% Using ntheorem package -- see http://www.math.washington.edu/tex-archive/macros/latex/contrib/ntheorem/ntheorem.pdf
\theoremstyle{plain}
\theoremseparator{.}
\newtheorem{example}{\pelda}
\theoremseparator{.}
%\theoremprework{\bigskip\hrule\medskip}
%\theorempostwork{\hrule\bigskip}
\theorembodyfont{\upshape}
\theoremsymbol{{\large \ensuremath{\centerdot}}}
\newtheorem{definition}{\definicio}
\theoremseparator{.}
%\theoremprework{\bigskip\hrule\medskip}
%\theorempostwork{\hrule\bigskip}
\newtheorem{theorem}{\tetel}
%--------------------------------------------------------------------------------------
% Some new commands and declarations
%--------------------------------------------------------------------------------------
\newcommand{\code}[1]{{\upshape\ttfamily\scriptsize\indent #1}}
\newcommand{\doi}[1]{DOI: \href{http://dx.doi.org/\detokenize{#1}}{\raggedright{\texttt{\detokenize{#1}}}}} % A hivatkozások közt így könnyebb DOI-t megadni.
\DeclareMathOperator*{\argmax}{arg\,max}
%\DeclareMathOperator*[1]{\floor}{arg\,max}
\DeclareMathOperator{\sign}{sgn}
\DeclareMathOperator{\rot}{rot}
%--------------------------------------------------------------------------------------
% Setup captions
%--------------------------------------------------------------------------------------
\captionsetup[figure]{
width=.75\textwidth,
aboveskip=10pt}
\renewcommand{\captionlabelfont}{\bf}
%\renewcommand{\captionfont}{\footnotesize\it}
%--------------------------------------------------------------------------------------
% Hyphenation exceptions
%--------------------------------------------------------------------------------------
\hyphenation{Shakes-peare Mar-seilles ár-víz-tű-rő tü-kör-fú-ró-gép}
\author{\vikszerzo}
\title{\viktitle}

View File

@ -0,0 +1,10 @@
%--------------------------------------------------------------------------------------
% Feladatkiiras (a tanszeken atveheto, kinyomtatott valtozat)
%--------------------------------------------------------------------------------------
\clearpage
\begin{center}
\large
\textbf{FELADATKIÍRÁS}\\
\end{center}
A feladatkiírást a tanszéki adminisztrációban lehet átvenni, és a leadott munkába eredeti, tanszéki pecséttel ellátott és a tanszékvezető által aláírt lapot kell belefűzni (ezen oldal \emph{helyett}, ez az oldal csak útmutatás). Az elektronikusan feltöltött dolgozatban már nem kell beleszerkeszteni ezt a feladatkiírást.

View File

@ -0,0 +1,11 @@
%--------------------------------------------------------------------------------------
% TDK-specifikus változók
%--------------------------------------------------------------------------------------
\newcommand{\tdkszerzoB}{Második Szerző} % Második szerző neve; hagyd üresen, ha egyedül írtad a TDK-t.
\newcommand{\tdkev}{2014} % A dolgozat írásának éve (pl. "2014") (Ez OTDK-nál eltérhet az aktuális évtől.)
% További adatok az OTDK címlaphoz (BME-s TDK-hoz nem kell kitölteni)
\newcommand{\tdkevfolyamA}{IV} % Első szerző évfolyama, római számmal (pl. IV).
\newcommand{\tdkevfolyamB}{III} % Második szerző évfolyama, római számmal (pl. III).
\newcommand{\tdkkonzulensbeosztasA}{egyetemi tanár} % Első konzulens beosztása (pl. egyetemi docens)
\newcommand{\tdkkonzulensbeosztasB}{doktorandusz} % Második konzulens beosztása (pl. egyetemi docens)

View File

@ -0,0 +1,56 @@
%--------------------------------------------------------------------------------------
% Elnevezések
%--------------------------------------------------------------------------------------
\newcommand{\bme}{Budapest University of Technology and Economics}
\newcommand{\vik}{Faculty of Electrical Engineering and Informatics}
\newcommand{\bmemit}{Department of Measurement and Information Systems}
\newcommand{\keszitette}{Author}
\newcommand{\konzulens}{Advisor}
\newcommand{\bsc}{Bachelor's Thesis}
\newcommand{\msc}{Master's Thesis}
\newcommand{\tdk}{Scientific Students' Association Report}
\newcommand{\bsconlab}{BSc Project Laboratory}
\newcommand{\msconlabi}{MSc Project Laboratory 1}
\newcommand{\msconlabii}{MSc Project Laboratory 2}
\newcommand{\pelda}{Example}
\newcommand{\definicio}{Definition}
\newcommand{\tetel}{Theorem}
\newcommand{\bevezetes}{Introduction}
\newcommand{\koszonetnyilvanitas}{Acknowledgements}
\newcommand{\fuggelek}{Appendix}
% Optional custom titles
%\addto\captionsenglish{%
%\renewcommand*{\listfigurename}{Your list of figures title}
%\renewcommand*{\listtablename}{Your list of tables title}
%\renewcommand*{\bibname}{Your bibliography title}
%}
\newcommand{\szerzo}{\vikszerzoKeresztnev{} \vikszerzoVezeteknev}
\newcommand{\vikkonzulensA}{\vikkonzulensAMegszolitas\vikkonzulensAKeresztnev{} \vikkonzulensAVezeteknev}
\newcommand{\vikkonzulensB}{\vikkonzulensBMegszolitas\vikkonzulensBKeresztnev{} \vikkonzulensBVezeteknev}
\newcommand{\vikkonzulensC}{\vikkonzulensCMegszolitas\vikkonzulensCKeresztnev{} \vikkonzulensCVezeteknev}
\newcommand{\selectthesislanguage}{\selectenglish}
\bibliographystyle{plainnat}
\newcommand{\ie}{i.e.\@\xspace}
\newcommand{\Ie}{I.e.\@\xspace}
\newcommand{\eg}{e.g.\@\xspace}
\newcommand{\Eg}{E.g.\@\xspace}
\newcommand{\etal}{et al.\@\xspace}
\newcommand{\etc}{etc.\@\xspace}
\newcommand{\vs}{vs.\@\xspace}
\newcommand{\viz}{viz.\@\xspace} % videlicet
\newcommand{\cf}{cf.\@\xspace} % confer
\newcommand{\Cf}{Cf.\@\xspace}
\newcommand{\wrt}{w.r.t.\@\xspace} % with respect to
\newcommand{\approximately}{approx.\@\xspace}
\newcommand{\appendixnumber}{1} % a fofejezet-szamlalo az angol ABC 1. betuje (A) lesz

View File

@ -0,0 +1,45 @@
%--------------------------------------------------------------------------------------
% Elnevezések
%--------------------------------------------------------------------------------------
\newcommand{\bme}{Budapesti Műszaki és Gazdaságtudományi Egyetem}
\newcommand{\vik}{Villamosmérnöki és Informatikai Kar}
\newcommand{\bmemit}{Méréstechnika és Információs Rendszerek Tanszék}
\newcommand{\keszitette}{Készítette}
\newcommand{\konzulens}{Konzulens}
\newcommand{\bsc}{Szakdolgozat}
\newcommand{\msc}{Diplomaterv}
\newcommand{\tdk}{TDK dolgozat}
\newcommand{\bsconlab}{BSc Önálló laboratórium}
\newcommand{\msconlabi}{MSc Önálló laboratórium 1.}
\newcommand{\msconlabii}{MSc Önálló laboratórium 2.}
\newcommand{\pelda}{Példa}
\newcommand{\definicio}{Definíció}
\newcommand{\tetel}{Tétel}
\newcommand{\bevezetes}{Bevezetés}
\newcommand{\koszonetnyilvanitas}{Köszönetnyilvánítás}
\newcommand{\fuggelek}{Függelék}
% Opcionálisan átnevezhető címek
%\addto\captionsmagyar{%
%\renewcommand{\listfigurename}{Saját ábrajegyzék cím}
%\renewcommand{\listtablename}{Saját táblázatjegyzék cím}
%\renewcommand{\bibname}{Saját irodalomjegyzék név}
%}
\newcommand{\szerzo}{\vikszerzoVezeteknev{} \vikszerzoKeresztnev}
\newcommand{\vikkonzulensA}{\vikkonzulensAMegszolitas\vikkonzulensAVezeteknev{} \vikkonzulensAKeresztnev}
\newcommand{\vikkonzulensB}{\vikkonzulensBMegszolitas\vikkonzulensBVezeteknev{} \vikkonzulensBKeresztnev}
\newcommand{\vikkonzulensC}{\vikkonzulensCMegszolitas\vikkonzulensCVezeteknev{} \vikkonzulensCKeresztnev}
\newcommand{\selectthesislanguage}{\selecthungarian}
\bibliographystyle{huplain}
\def\lstlistingname{lista}
\newcommand{\appendixnumber}{6} % a fofejezet-szamlalo az angol ABC 6. betuje (F) lesz

View File

@ -0,0 +1,58 @@
%% OTDK külső címlap
\begin{titlepage}
$\;$
\vspace{5cm}
\begin{center}
\Huge
\textbf{TDK-dolgozat}\let\thefootnote\relax\footnote{A dolgozat bemutatását a XXXXXXXXX ``Lorem ipsum dolor sit amet'' című program támogatta.}
\end{center}
\vspace{13cm}
\Large
\hspace{8cm} \szerzo
\hspace{8cm} \tdkszerzoB
\hspace{8cm} \tdkev.
\end{titlepage}
\newpage
\thispagestyle{empty}
%% OTDK belső címlap
\begin{titlepage}
\begin{center}
\includegraphics[width=7cm]{./figures/bme_logo.pdf}
\vspace{0.3cm}
\bme \\
\vik \\
\viktanszek \\
\vspace{3.5cm}
\huge {\vikcim}
\vspace{1.5cm}
\large {\textbf{\vikdoktipus}}
\vfill
{\Large
{\large \keszitette:} \\ \vspace{0.2cm}
\szerzo \\ \tdkevfolyamA. évfolyam \\
\vspace{0.5cm}
\tdkszerzoB \\ \tdkevfolyamB. évfolyam \\
\vspace{1.5cm}
{\large \konzulens:} \\ \vspace{0.2cm}
\vikkonzulensA,\\ \tdkkonzulensbeosztasA \\
\vspace{0.5cm}
\vikkonzulensB,\\ \tdkkonzulensbeosztasB \\
}
\vspace{2cm}
\large {\tdkev.}
\end{center}
\end{titlepage}

View File

@ -0,0 +1,32 @@
%% TDK címlap
\begin{titlepage}
\begin{center}
\includegraphics[width=7cm]{./figures/bme_logo.pdf}
\vspace{0.3cm}
\bme \\
\vik \\
\viktanszek \\
\vspace{5cm}
\huge {\vikcim}
\vspace{1.5cm}
\large {\textbf{\tdk}}
\vfill
{\Large
\keszitette: \\ \vspace{0.3cm}
\szerzo \\
\tdkszerzoB \\
\vspace{1.5cm}
\konzulens: \\ \vspace{0.3cm}
\vikkonzulensA \\
\vikkonzulensB \\
}
\vspace{2cm}
\large {\tdkev}
\end{center}
\end{titlepage}
%% Címlap vége

View File

@ -0,0 +1,44 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@setckpt{include/titlepage}{
\setcounter{page}{2}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
\setcounter{enumiii}{0}
\setcounter{enumiv}{0}
\setcounter{footnote}{0}
\setcounter{mpfootnote}{0}
\setcounter{part}{0}
\setcounter{chapter}{0}
\setcounter{section}{0}
\setcounter{subsection}{0}
\setcounter{subsubsection}{0}
\setcounter{paragraph}{0}
\setcounter{subparagraph}{0}
\setcounter{figure}{0}
\setcounter{table}{0}
\setcounter{footnote@add}{0}
\setcounter{footnote@ch}{0}
\setcounter{parentequation}{0}
\setcounter{Item}{0}
\setcounter{Hfootnote}{0}
\setcounter{bookmark@seq@number}{0}
\setcounter{lstnumber}{1}
\setcounter{endNonectr}{1}
\setcounter{currNonectr}{0}
\setcounter{caption@flags}{0}
\setcounter{continuedfloat}{0}
\setcounter{NAT@ctr}{0}
\setcounter{currexamplectr}{0}
\setcounter{endexamplectr}{0}
\setcounter{example}{0}
\setcounter{currdefinitionctr}{0}
\setcounter{enddefinitionctr}{0}
\setcounter{definition}{0}
\setcounter{currtheoremctr}{0}
\setcounter{endtheoremctr}{0}
\setcounter{theorem}{0}
\setcounter{section@level}{0}
\setcounter{lstlisting}{0}
}

View File

@ -0,0 +1,33 @@
\hypersetup{pageanchor=false}
%--------------------------------------------------------------------------------------
% The title page
%--------------------------------------------------------------------------------------
\begin{titlepage}
\begin{center}
\includegraphics[width=60mm,keepaspectratio]{figures/bme_logo.pdf}\\
\vspace{0.3cm}
\textbf{\bme}\\
\textmd{\vik}\\
\textmd{\viktanszek}\\[5cm]
\vspace{0.4cm}
{\huge \bfseries \vikcim}\\[0.8cm]
\vspace{0.5cm}
\textsc{\Large \vikdoktipus}\\[4cm]
{
\renewcommand{\arraystretch}{0.85}
\begin{tabular}{cc}
\makebox[7cm]{\emph{\keszitette}} & \makebox[7cm]{\emph{\konzulens}} \\ \noalign{\smallskip}
\makebox[7cm]{\szerzo} & \makebox[7cm]{\vikkonzulensA} \\
& \makebox[7cm]{\vikkonzulensB} \\
& \makebox[7cm]{\vikkonzulensC} \\
\end{tabular}
}
\vfill
{\large \today}
\end{center}
\end{titlepage}
\hypersetup{pageanchor=false}

View File

@ -0,0 +1,731 @@
-- Generated by ./install-tl on Sun Feb 12 19:17:26 2017
-- $Id: language.us.lua 18737 2010-06-04 17:09:02Z karl $
--[[
language.us.dat (and the start of language.dat.lua), used by:
- a special luatex version of hyphen.cfg (derived from the babel system);
- a special luatex version of etex.src (from the e-TeX distributon).
See luatex-hyphen.pdf (currently part of the hyph-utf8 package) for details.
DO NOT EDIT THIS FILE (language.dat.lua)! It is generated by tlmgr.
See language.dat (or language.us) for more information.
Warning: formats using this file also use one of language.dat or
language.def. Update them accordingly. The interaction between these
files is documented in luatex-hyphen.pdf, but here is a summary:
- a language must be mentioned in language.dat or language.def to be
available; if, in addition, it is:
- not mentioned in language.dat.lua, then it is dumped in the format;
- mentioned in language.dat.lua with a key special="disabled:<reason>",
then it is not available at all;
- mentioned in language.dat.lua with a normal entry, then it will not
be dumped in the format, but loaded at runtime when activated.
]]
return {
["english"]={
loader="hyphen.tex",
special="language0", -- should be dumped in the format
lefthyphenmin=2,
righthyphenmin=3,
synonyms={"usenglish","USenglish","american"},
},
-- dumylang and zerohyph are dumped in the format,
-- since they contain either very few or no patterns at all
-- END of language.us.lua (missing '}' appended after all entries)
-- from dehyph-exptl:
['german-x-2014-05-21'] = {
loader = 'dehypht-x-2014-05-21.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { 'german-x-latest' },
patterns = 'hyph-de-1901.pat.txt',
hyphenation = 'hyph-de-1901.hyp.txt',
},
['ngerman-x-2014-05-21'] = {
loader = 'dehyphn-x-2014-05-21.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { 'ngerman-x-latest' },
patterns = 'hyph-de-1996.pat.txt',
hyphenation = 'hyph-de-1996.hyp.txt',
},
-- from hyphen-afrikaans:
['afrikaans'] = {
loader = 'loadhyph-af.tex',
lefthyphenmin = 1,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-af.pat.txt',
hyphenation = 'hyph-af.hyp.txt',
},
-- from hyphen-ancientgreek:
['ancientgreek'] = {
loader = 'loadhyph-grc.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-grc.pat.txt',
hyphenation = '',
},
['ibycus'] = {
loader = 'ibyhyph.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
special = 'disabled:8-bit only',
},
-- from hyphen-arabic:
['arabic'] = {
loader = 'zerohyph.tex',
lefthyphenmin = 2,
righthyphenmin = 3,
synonyms = { },
patterns = '',
},
-- from hyphen-armenian:
['armenian'] = {
loader = 'loadhyph-hy.tex',
lefthyphenmin = 1,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-hy.pat.txt',
hyphenation = '',
},
-- from hyphen-basque:
['basque'] = {
loader = 'loadhyph-eu.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-eu.pat.txt',
hyphenation = '',
},
-- from hyphen-bulgarian:
['bulgarian'] = {
loader = 'loadhyph-bg.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-bg.pat.txt',
hyphenation = '',
},
-- from hyphen-catalan:
['catalan'] = {
loader = 'loadhyph-ca.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-ca.pat.txt',
hyphenation = 'hyph-ca.hyp.txt',
},
-- from hyphen-chinese:
['pinyin'] = {
loader = 'loadhyph-zh-latn-pinyin.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-zh-latn-pinyin.pat.txt',
hyphenation = '',
},
-- from hyphen-churchslavonic:
['churchslavonic'] = {
loader = 'loadhyph-cu.tex',
lefthyphenmin = 1,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-cu.pat.txt',
hyphenation = 'hyph-cu.hyp.txt',
},
-- from hyphen-coptic:
['coptic'] = {
loader = 'loadhyph-cop.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-cop.pat.txt',
hyphenation = '',
},
-- from hyphen-croatian:
['croatian'] = {
loader = 'loadhyph-hr.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-hr.pat.txt',
hyphenation = '',
},
-- from hyphen-czech:
['czech'] = {
loader = 'loadhyph-cs.tex',
lefthyphenmin = 2,
righthyphenmin = 3,
synonyms = { },
patterns = 'hyph-cs.pat.txt',
hyphenation = 'hyph-cs.hyp.txt',
},
-- from hyphen-danish:
['danish'] = {
loader = 'loadhyph-da.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-da.pat.txt',
hyphenation = '',
},
-- from hyphen-dutch:
['dutch'] = {
loader = 'loadhyph-nl.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-nl.pat.txt',
hyphenation = 'hyph-nl.hyp.txt',
},
-- from hyphen-english:
['ukenglish'] = {
loader = 'loadhyph-en-gb.tex',
lefthyphenmin = 2,
righthyphenmin = 3,
synonyms = { 'british', 'UKenglish' },
patterns = 'hyph-en-gb.pat.txt',
hyphenation = 'hyph-en-gb.hyp.txt',
},
['usenglishmax'] = {
loader = 'loadhyph-en-us.tex',
lefthyphenmin = 2,
righthyphenmin = 3,
synonyms = { },
patterns = 'hyph-en-us.pat.txt',
hyphenation = 'hyph-en-us.hyp.txt',
},
-- from hyphen-esperanto:
['esperanto'] = {
loader = 'loadhyph-eo.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-eo.pat.txt',
hyphenation = '',
},
-- from hyphen-estonian:
['estonian'] = {
loader = 'loadhyph-et.tex',
lefthyphenmin = 2,
righthyphenmin = 3,
synonyms = { },
patterns = 'hyph-et.pat.txt',
hyphenation = '',
},
-- from hyphen-ethiopic:
['ethiopic'] = {
loader = 'loadhyph-mul-ethi.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { 'amharic', 'geez' },
patterns = 'hyph-mul-ethi.pat.txt',
hyphenation = '',
},
-- from hyphen-farsi:
['farsi'] = {
loader = 'zerohyph.tex',
lefthyphenmin = 2,
righthyphenmin = 3,
synonyms = { 'persian' },
patterns = '',
},
-- from hyphen-finnish:
['finnish'] = {
loader = 'loadhyph-fi.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-fi.pat.txt',
hyphenation = '',
},
-- from hyphen-french:
['french'] = {
loader = 'loadhyph-fr.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { 'patois', 'francais' },
patterns = 'hyph-fr.pat.txt',
hyphenation = '',
},
-- from hyphen-friulan:
['friulan'] = {
loader = 'loadhyph-fur.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-fur.pat.txt',
hyphenation = '',
},
-- from hyphen-galician:
['galician'] = {
loader = 'loadhyph-gl.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-gl.pat.txt',
hyphenation = '',
},
-- from hyphen-georgian:
['georgian'] = {
loader = 'loadhyph-ka.tex',
lefthyphenmin = 1,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-ka.pat.txt',
hyphenation = '',
},
-- from hyphen-german:
['german'] = {
loader = 'loadhyph-de-1901.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-de-1901.pat.txt',
hyphenation = '',
},
['ngerman'] = {
loader = 'loadhyph-de-1996.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-de-1996.pat.txt',
hyphenation = '',
},
['swissgerman'] = {
loader = 'loadhyph-de-ch-1901.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-de-ch-1901.pat.txt',
hyphenation = '',
},
-- from hyphen-greek:
['monogreek'] = {
loader = 'loadhyph-el-monoton.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-el-monoton.pat.txt',
hyphenation = '',
},
['greek'] = {
loader = 'loadhyph-el-polyton.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { 'polygreek' },
patterns = 'hyph-el-polyton.pat.txt',
hyphenation = '',
},
-- from hyphen-hungarian:
['hungarian'] = {
loader = 'loadhyph-hu.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { 'hungarian', 'magyar' },
patterns = 'hyph-hu.pat.txt',
hyphenation = '',
},
-- from hyphen-icelandic:
['icelandic'] = {
loader = 'loadhyph-is.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-is.pat.txt',
hyphenation = '',
},
-- from hyphen-indic:
['assamese'] = {
loader = 'loadhyph-as.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-as.pat.txt',
hyphenation = '',
},
['bengali'] = {
loader = 'loadhyph-bn.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-bn.pat.txt',
hyphenation = '',
},
['gujarati'] = {
loader = 'loadhyph-gu.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-gu.pat.txt',
hyphenation = '',
},
['hindi'] = {
loader = 'loadhyph-hi.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-hi.pat.txt',
hyphenation = '',
},
['kannada'] = {
loader = 'loadhyph-kn.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-kn.pat.txt',
hyphenation = '',
},
['malayalam'] = {
loader = 'loadhyph-ml.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-ml.pat.txt',
hyphenation = '',
},
['marathi'] = {
loader = 'loadhyph-mr.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-mr.pat.txt',
hyphenation = '',
},
['oriya'] = {
loader = 'loadhyph-or.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-or.pat.txt',
hyphenation = '',
},
['panjabi'] = {
loader = 'loadhyph-pa.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-pa.pat.txt',
hyphenation = '',
},
['tamil'] = {
loader = 'loadhyph-ta.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-ta.pat.txt',
hyphenation = '',
},
['telugu'] = {
loader = 'loadhyph-te.tex',
lefthyphenmin = 1,
righthyphenmin = 1,
synonyms = { },
patterns = 'hyph-te.pat.txt',
hyphenation = '',
},
-- from hyphen-indonesian:
['indonesian'] = {
loader = 'loadhyph-id.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-id.pat.txt',
hyphenation = 'hyph-id.hyp.txt',
},
-- from hyphen-interlingua:
['interlingua'] = {
loader = 'loadhyph-ia.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-ia.pat.txt',
hyphenation = 'hyph-ia.hyp.txt',
},
-- from hyphen-irish:
['irish'] = {
loader = 'loadhyph-ga.tex',
lefthyphenmin = 2,
righthyphenmin = 3,
synonyms = { },
patterns = 'hyph-ga.pat.txt',
hyphenation = 'hyph-ga.hyp.txt',
},
-- from hyphen-italian:
['italian'] = {
loader = 'loadhyph-it.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-it.pat.txt',
hyphenation = '',
},
-- from hyphen-kurmanji:
['kurmanji'] = {
loader = 'loadhyph-kmr.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-kmr.pat.txt',
hyphenation = '',
},
-- from hyphen-latin:
['latin'] = {
loader = 'loadhyph-la.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-la.pat.txt',
hyphenation = '',
},
['classiclatin'] = {
loader = 'loadhyph-la-x-classic.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-la-x-classic.pat.txt',
hyphenation = '',
},
['liturgicallatin'] = {
loader = 'loadhyph-la-x-liturgic.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-la-x-liturgic.pat.txt',
hyphenation = '',
},
-- from hyphen-latvian:
['latvian'] = {
loader = 'loadhyph-lv.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-lv.pat.txt',
hyphenation = '',
},
-- from hyphen-lithuanian:
['lithuanian'] = {
loader = 'loadhyph-lt.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-lt.pat.txt',
hyphenation = '',
},
-- from hyphen-mongolian:
['mongolian'] = {
loader = 'loadhyph-mn-cyrl.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-mn-cyrl.pat.txt',
hyphenation = '',
},
['mongolianlmc'] = {
loader = 'loadhyph-mn-cyrl-x-lmc.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
special = 'disabled:only for 8bit montex with lmc encoding',
},
-- from hyphen-norwegian:
['bokmal'] = {
loader = 'loadhyph-nb.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { 'norwegian', 'norsk' },
patterns = 'hyph-nb.pat.txt',
hyphenation = 'hyph-nb.hyp.txt',
},
['nynorsk'] = {
loader = 'loadhyph-nn.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-nn.pat.txt',
hyphenation = 'hyph-nn.hyp.txt',
},
-- from hyphen-occitan:
['occitan'] = {
loader = 'loadhyph-oc.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-oc.pat.txt',
hyphenation = '',
},
-- from hyphen-piedmontese:
['piedmontese'] = {
loader = 'loadhyph-pms.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-pms.pat.txt',
hyphenation = '',
},
-- from hyphen-polish:
['polish'] = {
loader = 'loadhyph-pl.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-pl.pat.txt',
hyphenation = 'hyph-pl.hyp.txt',
},
-- from hyphen-portuguese:
['portuguese'] = {
loader = 'loadhyph-pt.tex',
lefthyphenmin = 2,
righthyphenmin = 3,
synonyms = { 'portuges' },
patterns = 'hyph-pt.pat.txt',
hyphenation = 'hyph-pt.hyp.txt',
},
-- from hyphen-romanian:
['romanian'] = {
loader = 'loadhyph-ro.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-ro.pat.txt',
hyphenation = '',
},
-- from hyphen-romansh:
['romansh'] = {
loader = 'loadhyph-rm.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-rm.pat.txt',
hyphenation = '',
},
-- from hyphen-russian:
['russian'] = {
loader = 'loadhyph-ru.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-ru.pat.txt',
hyphenation = 'hyph-ru.hyp.txt',
},
-- from hyphen-sanskrit:
['sanskrit'] = {
loader = 'loadhyph-sa.tex',
lefthyphenmin = 1,
righthyphenmin = 3,
synonyms = { },
patterns = 'hyph-sa.pat.txt',
hyphenation = '',
},
-- from hyphen-serbian:
['serbian'] = {
loader = 'loadhyph-sr-latn.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-sh-latn.pat.txt,hyph-sh-cyrl.pat.txt',
hyphenation = 'hyph-sh-latn.hyp.txt,hyph-sh-cyrl.hyp.txt',
},
['serbianc'] = {
loader = 'loadhyph-sr-cyrl.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-sh-latn.pat.txt,hyph-sh-cyrl.pat.txt',
hyphenation = 'hyph-sh-latn.hyp.txt,hyph-sh-cyrl.hyp.txt',
},
-- from hyphen-slovak:
['slovak'] = {
loader = 'loadhyph-sk.tex',
lefthyphenmin = 2,
righthyphenmin = 3,
synonyms = { },
patterns = 'hyph-sk.pat.txt',
hyphenation = 'hyph-sk.hyp.txt',
},
-- from hyphen-slovenian:
['slovenian'] = {
loader = 'loadhyph-sl.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { 'slovene' },
patterns = 'hyph-sl.pat.txt',
hyphenation = '',
},
-- from hyphen-spanish:
['spanish'] = {
loader = 'loadhyph-es.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { 'espanol' },
patterns = 'hyph-es.pat.txt',
hyphenation = '',
},
-- from hyphen-swedish:
['swedish'] = {
loader = 'loadhyph-sv.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-sv.pat.txt',
hyphenation = '',
},
-- from hyphen-thai:
['thai'] = {
loader = 'loadhyph-th.tex',
lefthyphenmin = 2,
righthyphenmin = 3,
synonyms = { },
patterns = 'hyph-th.pat.txt',
hyphenation = '',
},
-- from hyphen-turkish:
['turkish'] = {
loader = 'loadhyph-tr.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-tr.pat.txt',
hyphenation = '',
},
-- from hyphen-turkmen:
['turkmen'] = {
loader = 'loadhyph-tk.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-tk.pat.txt',
hyphenation = '',
},
-- from hyphen-ukrainian:
['ukrainian'] = {
loader = 'loadhyph-uk.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-uk.pat.txt',
hyphenation = '',
},
-- from hyphen-uppersorbian:
['uppersorbian'] = {
loader = 'loadhyph-hsb.tex',
lefthyphenmin = 2,
righthyphenmin = 2,
synonyms = { },
patterns = 'hyph-hsb.pat.txt',
hyphenation = 'hyph-hsb.hyp.txt',
},
-- from hyphen-welsh:
['welsh'] = {
loader = 'loadhyph-cy.tex',
lefthyphenmin = 2,
righthyphenmin = 3,
synonyms = { },
patterns = 'hyph-cy.pat.txt',
hyphenation = '',
},
}

View File

@ -0,0 +1,10 @@
set DOCUMENT=thesis
pdflatex --quiet %DOCUMENT%
bibtex -quiet %DOCUMENT%
pdflatex --quiet %DOCUMENT%
pdflatex --quiet %DOCUMENT%
@REM move %DOCUMENT%.pdf ../pdf/%DOCUMENT%.pdf
@del *.aux *.dvi *.thm *.lof *.log *.lot *.fls *.out *.toc *.bbl *.blg

BIN
docs/thesis/thesis.pdf Normal file

Binary file not shown.

99
docs/thesis/thesis.tex Normal file
View File

@ -0,0 +1,99 @@
% !TeX spellcheck = hu_HU
% !TeX encoding = UTF-8
% !TeX program = xelatex
% TODO Change language to en_GB (recommended) or en_US for English documents
%\documentclass[11pt,a4paper,oneside]{report} % Single-side
\documentclass[11pt,a4paper,twoside,openright]{report} % Duplex
\input{include/packages}
%TODO Set the main variables
\newcommand{\vikszerzoVezeteknev}{Kunkli}
\newcommand{\vikszerzoKeresztnev}{Richárd}
\newcommand{\vikkonzulensAMegszolitas}{dr.~}
\newcommand{\vikkonzulensAVezeteknev}{Simon}
\newcommand{\vikkonzulensAKeresztnev}{Csaba}
\newcommand{\vikkonzulensBMegszolitas}{}
\newcommand{\vikkonzulensBVezeteknev}{}
\newcommand{\vikkonzulensBKeresztnev}{}
\newcommand{\vikkonzulensCMegszolitas}{}
\newcommand{\vikkonzulensCVezeteknev}{}
\newcommand{\vikkonzulensCKeresztnev}{}
\newcommand{\vikcim}{Vizualizációs megoldás IoT adat elemző rendszerhez} % Cím
\newcommand{\viktanszek}{\bmetmit} % Tanszék
\newcommand{\vikdoktipus}{\bsc} % Dokumentum típusa (\bsc vagy \msc)
\newcommand{\vikmunkatipusat}{szakdolgozatot} % a "hallgató nyilatkozat" részhez: szakdolgozatot vagy diplomatervet
\input{include/tdk-variables}
\newcommand{\szerzoMeta}{\vikszerzoVezeteknev{} \vikszerzoKeresztnev} % egy szerző esetén
%\newcommand{\szerzoMeta}{\vikszerzoVezeteknev{} \vikszerzoKeresztnev, \tdkszerzoB} % két szerző esetén
%TODO Language configuration -- choose one
% Beállítások magyar nyelvű dolgozathoz
\input{include/thesis-hu}
% Settings for English documents
%\input{include/thesis-en}
\input{include/preamble}
%--------------------------------------------------------------------------------------
% Table of contents and the main text
%--------------------------------------------------------------------------------------
\begin{document}
\pagenumbering{gobble}
%TODO These includes define guidelines -- remove these
%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%\include{include/guideline}
%\include{include/project}
\selectthesislanguage
%TODO Titlepage -- choose one from below
%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
\include{include/titlepage} % Szakdolgozat/Diplomaterv címlap
%\include{include/titlepage-tdk} % TDK címlap
%\include{include/titlepage-otdk} % OTDK címlap
% Table of Contents
%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
\tableofcontents\vfill
% Declaration and Abstract
%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
\include{include/declaration} %TODO Hallgatói nyilatkozat -- TDK és OTDK esetén törlendő!
\include{content/abstract} %TODO Összefoglaló -- TDK és OTDK esetén nem kötelező
% The main part of the thesis
%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
\pagenumbering{arabic}
%TODO import your own content
\include{content/introduction}
\include{content/birdnetes-introduction}
% List of Figures, Tables
%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%\listoffigures\addcontentsline{toc}{chapter}{\listfigurename}
%\listoftables\addcontentsline{toc}{chapter}{\listtablename}
% Bibliography
%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
\addcontentsline{toc}{chapter}{\bibname}
\bibliography{bib/mybib}
% Appendix
%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
\include{content/appendices}
%\label{page:last}
\end{document}