├── .gitattributes ├── .dockerignore ├── .prettierignore ├── bin ├── mavlink │ ├── .gitignore │ ├── mavlink-routerd-amd64 │ ├── mavlink-routerd-arm │ └── mavlink-routerd-arm64 ├── includes │ ├── app.h │ ├── flight_controller.h │ ├── camera.h │ ├── modem.h │ ├── mavlink.h │ ├── flight_controller.cpp │ ├── logger.h │ ├── logger.cpp │ ├── utils.h │ ├── app.cpp │ └── rapidjson │ │ └── internal │ │ └── swap.h └── Makefile ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── support_request.yml ├── workflows │ ├── releases.yml │ └── pull_request.yml └── dependabot.yml ├── backend ├── src │ ├── @types │ │ └── connect-sqlite3.d.ts │ ├── graphql-types │ │ ├── MyContext.ts │ │ └── Upload.ts │ ├── global.d.ts │ ├── graphql-input-types │ │ ├── MapInput.ts │ │ ├── AuthInput.ts │ │ ├── MavlinkInput.ts │ │ ├── KernelInput.ts │ │ ├── SupervisorInput.ts │ │ ├── ApplicationInput.ts │ │ ├── VpnInput.ts │ │ ├── FlightControllerInput.ts │ │ ├── ModemInput.ts │ │ ├── Endpoint.ts │ │ ├── LogViewer.ts │ │ └── CameraInput.ts │ ├── graphql-response-types │ │ ├── Map.ts │ │ ├── FieldError.ts │ │ ├── KernelResponse.ts │ │ ├── UserResponse.ts │ │ ├── ModemResponse.ts │ │ ├── ApplicationResponse.ts │ │ ├── FlightController.ts │ │ ├── Endpoint.ts │ │ ├── GlobalResponse.ts │ │ └── VpnResponse.ts │ ├── factories │ │ └── logger.factory.ts │ ├── utils │ │ ├── pubsub.ts │ │ ├── supervisor.ts │ │ └── postUpdateActions.ts │ ├── resolvers │ │ ├── MapResovler.ts │ │ ├── ChildProcessCmd.ts │ │ └── ModemResolver.ts │ ├── mavlink │ │ └── v2.0 │ │ │ ├── mavLog.js │ │ │ ├── udp_client.ts │ │ │ ├── definitions │ │ │ ├── firmware.ts │ │ │ └── frame.ts │ │ │ ├── mavCommand │ │ │ └── mav.waypoints.js │ │ │ └── processor.js │ ├── seeds │ │ ├── vpn.seed.ts │ │ ├── camera.seed.ts │ │ ├── application.seed.ts │ │ ├── logger.seed.ts │ │ ├── modem.seed.ts │ │ └── flightController.seed.ts │ ├── config │ │ └── paths.ts │ ├── entity │ │ ├── User.ts │ │ ├── Map.ts │ │ ├── Application.ts │ │ ├── Vpn.ts │ │ ├── Endpoint.ts │ │ ├── Logger.ts │ │ ├── Modem.ts │ │ └── FlightController.ts │ ├── createTypeormCon.ts │ ├── migration │ │ └── intitTableValues.js │ └── logger │ │ └── index.ts ├── .eslintignore ├── tsconfig.json └── ormconfig.js ├── frontend ├── src │ ├── react-app-env.d.ts │ ├── app │ │ ├── config.tsx │ │ ├── changelog.tsx │ │ ├── publicRoute.tsx │ │ ├── app.tsx │ │ └── layouts.tsx │ ├── graphql │ │ ├── query │ │ │ ├── map.graphql │ │ │ ├── application.graphql │ │ │ ├── fligth_controller.graphql │ │ │ ├── endpoints.graphql │ │ │ ├── modem.graphql │ │ │ ├── vpn.graphql │ │ │ ├── camera.graphql │ │ │ ├── supervisor.graphql │ │ │ └── logs.graphql │ │ ├── mutation │ │ │ ├── backup-restore.graphql │ │ │ ├── mavlink.graphql │ │ │ ├── application.graphql │ │ │ ├── modem.graphql │ │ │ ├── kernel_message.graphql │ │ │ ├── vpn.graphql │ │ │ ├── supervisor.graphql │ │ │ ├── flight_controller.graphql │ │ │ ├── logs.graphql │ │ │ ├── endpoint.graphql │ │ │ └── camera.graphql │ │ └── subscription │ │ │ ├── stdout_kernel.graphql │ │ │ ├── camera.graphql │ │ │ ├── supervisor.graphql │ │ │ ├── global.graphql │ │ │ └── mavlink.graphql │ ├── images │ │ ├── map_icons │ │ │ ├── layers.png │ │ │ ├── layers-2x.png │ │ │ ├── quad-icon.png │ │ │ ├── plane-icon.png │ │ │ ├── marker-icon-2x.png │ │ │ ├── marker-shadow.png │ │ │ └── marker-icon_bak.png │ │ ├── header │ │ │ ├── LogoDarkTheme.png │ │ │ └── uavcast-logominimized.png │ │ └── flightController │ │ │ ├── rpi-fc-gpio.jpg │ │ │ ├── rpi-fc-navio.jpg │ │ │ └── rpi-fc-usb.jpg │ ├── pages │ │ ├── map │ │ │ ├── images │ │ │ │ ├── layers.png │ │ │ │ ├── layers-2x.png │ │ │ │ ├── quad-icon.png │ │ │ │ ├── plane-icon.png │ │ │ │ ├── marker-icon-2x.png │ │ │ │ ├── marker-shadow.png │ │ │ │ └── marker-icon_bak.png │ │ │ ├── container │ │ │ │ └── settings.tab.tsx │ │ │ └── components │ │ │ │ └── video │ │ │ │ └── index.js │ │ ├── pageNotFound │ │ │ ├── p404.png │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── dashboard │ │ │ ├── _helpers │ │ │ │ └── colors.tsx │ │ │ └── containers │ │ │ │ ├── kernel_status.tsx │ │ │ │ ├── casting_status.tsx │ │ │ │ └── mavlinkStatus.tsx │ │ ├── modem │ │ │ ├── components │ │ │ │ ├── dropdown-arrays.tsx │ │ │ │ └── help.tsx │ │ │ └── containers │ │ │ │ ├── hilink.footer.tsx │ │ │ │ └── stick.footer.tsx │ │ ├── vpn │ │ │ ├── components │ │ │ │ ├── dropdown-arrays.tsx │ │ │ │ └── help.tsx │ │ │ └── containers │ │ │ │ ├── dropzone.tsx │ │ │ │ ├── zerotier.footer.tsx │ │ │ │ ├── openvpn.input.tsx │ │ │ │ ├── openvpn.footer.tsx │ │ │ │ └── zerotierId.tsx │ │ ├── flightController │ │ │ ├── components │ │ │ │ └── dropdown-arrays.tsx │ │ │ └── containers │ │ │ │ ├── tcpPort.tsx │ │ │ │ ├── internalAddress.tsx │ │ │ │ ├── flightLog.tsx │ │ │ │ ├── baudRate.tsx │ │ │ │ └── connectionType.tsx │ │ ├── logs │ │ │ ├── containers │ │ │ │ ├── logger-delete.tsx │ │ │ │ ├── logger-download.tsx │ │ │ │ ├── logger-delete-ulog.tsx │ │ │ │ ├── logger-delete-stats.tsx │ │ │ │ ├── logger-delete-docker.tsx │ │ │ │ └── logger-delete-server.tsx │ │ │ ├── components │ │ │ │ └── modal.tsx │ │ │ └── index.tsx │ │ ├── settings │ │ │ ├── containers │ │ │ │ ├── host.tsx │ │ │ │ └── application.tsx │ │ │ └── components │ │ │ │ └── rebootHostModal.tsx │ │ ├── camera │ │ │ ├── containers │ │ │ │ ├── customPath.tsx │ │ │ │ ├── contrast.tsx │ │ │ │ ├── framesPrSecond.tsx │ │ │ │ ├── rotation.tsx │ │ │ │ ├── brightness.tsx │ │ │ │ ├── bitratePrSecond.tsx │ │ │ │ └── customPipeline.tsx │ │ │ └── components │ │ │ │ └── help.tsx │ │ ├── endpoints │ │ │ └── components │ │ │ │ ├── apModuleActive.tsx │ │ │ │ ├── epVideoPort.tsx │ │ │ │ ├── epName.tsx │ │ │ │ ├── epTelemetryPort.tsx │ │ │ │ ├── epIPaddress.tsx │ │ │ │ └── placeHolder.tsx │ │ ├── backup │ │ │ ├── index.tsx │ │ │ └── containers │ │ │ │ ├── restore-database-modal.tsx │ │ │ │ └── dropzone.tsx │ │ └── logviewer │ │ │ └── index.tsx │ ├── components │ │ ├── Footer │ │ │ ├── package.json │ │ │ └── Footer.js │ │ ├── Header │ │ │ ├── package.json │ │ │ ├── HeaderDropdown.tsx │ │ │ └── Header.tsx │ │ ├── SidebarForm │ │ │ ├── package.json │ │ │ └── SidebarForm.js │ │ ├── SidebarFooter │ │ │ ├── package.json │ │ │ └── SidebarFooter.js │ │ ├── SidebarHeader │ │ │ ├── package.json │ │ │ └── SidebarHeader.js │ │ ├── SidebarMinimizer │ │ │ ├── package.json │ │ │ └── SidebarMinimizer.js │ │ ├── spinner.tsx │ │ ├── led.tsx │ │ ├── slider.tsx │ │ ├── suspense1columns.tsx │ │ └── serverHasDisconnected.tsx │ ├── scss │ │ ├── core │ │ │ ├── _logviewer.scss │ │ │ ├── _chart.scss │ │ │ ├── core.scss │ │ │ ├── _switch.scss │ │ │ ├── _nav.scss │ │ │ └── _slider.scss │ │ ├── style.scss │ │ └── bootstrap │ │ │ ├── _utilities.scss │ │ │ ├── mixins │ │ │ ├── _text-truncate.scss │ │ │ ├── _text-hide.scss │ │ │ ├── _text-emphasis.scss │ │ │ ├── _deprecate.scss │ │ │ └── _hover.scss │ │ │ ├── utilities │ │ │ ├── _visibility.scss │ │ │ ├── _stretched-link.scss │ │ │ └── _display.scss │ │ │ ├── _transitions.scss │ │ │ ├── bootstrap.scss │ │ │ ├── bootstrap-reboot.scss │ │ │ ├── _mixins.scss │ │ │ ├── _root.scss │ │ │ └── bootstrap-grid.scss │ ├── __tests__ │ │ ├── tsconfig.json │ │ └── pages │ │ │ └── endpoints │ │ │ └── end.test.tsx │ ├── setupTests.ts │ ├── reportWebVitals.ts │ ├── utils │ │ └── customRender.tsx │ ├── translations │ │ ├── i18next.tsx │ │ └── locales │ │ │ └── Readme.md │ └── index.tsx ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json ├── .eslintignore ├── jest.config.ts ├── codegen.yml ├── tsconfig.json └── script │ └── analyse.js ├── .prettierrc ├── docker ├── bin │ ├── entrypoint.sh │ ├── install-prod-addon.sh │ └── install-dev-addon.sh ├── Dockerfile.dev └── Dockerfile.mavlink ├── etc └── mavlink-router-example.conf ├── init ├── mavlink-router.service ├── uavcast-vpn.service ├── uavcast-camera.service ├── uavcast-web.service ├── uavcast.service └── journalctl3.py ├── .gitmodules ├── package.json ├── .vscode ├── c_cpp_properties.json ├── ssh_connect.config ├── launch.json └── extensions.json ├── .gitignore ├── docker-compose.yml ├── license.audit.js └── LICENSE /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | frontend/node_modules 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | frontend/src/.eslintrc.js -------------------------------------------------------------------------------- /bin/mavlink/.gitignore: -------------------------------------------------------------------------------- 1 | mavlink-routerd 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /backend/src/@types/connect-sqlite3.d.ts: -------------------------------------------------------------------------------- 1 | declare module "connect-sqlite3"; 2 | -------------------------------------------------------------------------------- /frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/src/app/config.tsx: -------------------------------------------------------------------------------- 1 | export const config = { 2 | admin: 'ADMIN' 3 | }; 4 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/graphql/query/map.graphql: -------------------------------------------------------------------------------- 1 | query map { 2 | map { 3 | mavCockpitDisable 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | src/utils/apolloClient.tsx 2 | src/graphql/* 3 | node_modules/* 4 | build/* 5 | public/* -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/public/logo512.png -------------------------------------------------------------------------------- /bin/includes/app.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class App 5 | { 6 | 7 | public: 8 | int serverport(); 9 | }; -------------------------------------------------------------------------------- /bin/mavlink/mavlink-routerd-amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/bin/mavlink/mavlink-routerd-amd64 -------------------------------------------------------------------------------- /bin/mavlink/mavlink-routerd-arm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/bin/mavlink/mavlink-routerd-arm -------------------------------------------------------------------------------- /bin/mavlink/mavlink-routerd-arm64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/bin/mavlink/mavlink-routerd-arm64 -------------------------------------------------------------------------------- /frontend/src/app/changelog.tsx: -------------------------------------------------------------------------------- 1 | 2 | // Loaded in Aside page 3 | export const Changelog = () => { 4 | return
; 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/images/map_icons/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/images/map_icons/layers.png -------------------------------------------------------------------------------- /frontend/src/pages/map/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/pages/map/images/layers.png -------------------------------------------------------------------------------- /frontend/src/pages/pageNotFound/p404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/pages/pageNotFound/p404.png -------------------------------------------------------------------------------- /frontend/src/graphql/mutation/backup-restore.graphql: -------------------------------------------------------------------------------- 1 | mutation uploadDatabaseFile($file: Upload!) { 2 | uploadDatabaseFile(file: $file) 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/images/map_icons/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/images/map_icons/layers-2x.png -------------------------------------------------------------------------------- /frontend/src/images/map_icons/quad-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/images/map_icons/quad-icon.png -------------------------------------------------------------------------------- /frontend/src/pages/map/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/pages/map/images/layers-2x.png -------------------------------------------------------------------------------- /frontend/src/pages/map/images/quad-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/pages/map/images/quad-icon.png -------------------------------------------------------------------------------- /frontend/src/images/header/LogoDarkTheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/images/header/LogoDarkTheme.png -------------------------------------------------------------------------------- /frontend/src/images/map_icons/plane-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/images/map_icons/plane-icon.png -------------------------------------------------------------------------------- /frontend/src/pages/map/images/plane-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/pages/map/images/plane-icon.png -------------------------------------------------------------------------------- /frontend/src/components/Footer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Footer", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./Footer.js" 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/components/Header/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Header", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./Header.js" 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/images/map_icons/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/images/map_icons/marker-icon-2x.png -------------------------------------------------------------------------------- /frontend/src/images/map_icons/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/images/map_icons/marker-shadow.png -------------------------------------------------------------------------------- /frontend/src/pages/map/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/pages/map/images/marker-icon-2x.png -------------------------------------------------------------------------------- /frontend/src/pages/map/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/pages/map/images/marker-shadow.png -------------------------------------------------------------------------------- /frontend/src/graphql/mutation/mavlink.graphql: -------------------------------------------------------------------------------- 1 | mutation sendMavCommand($type: String!, $value: String!) { 2 | sendMavCommand(type: $type, value: $value) 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/graphql/query/application.graphql: -------------------------------------------------------------------------------- 1 | query getApplication { 2 | getApplication { 3 | properties { 4 | webPort 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/images/map_icons/marker-icon_bak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/images/map_icons/marker-icon_bak.png -------------------------------------------------------------------------------- /frontend/src/pages/map/images/marker-icon_bak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/pages/map/images/marker-icon_bak.png -------------------------------------------------------------------------------- /frontend/src/images/flightController/rpi-fc-gpio.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/images/flightController/rpi-fc-gpio.jpg -------------------------------------------------------------------------------- /frontend/src/images/flightController/rpi-fc-navio.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/images/flightController/rpi-fc-navio.jpg -------------------------------------------------------------------------------- /frontend/src/images/flightController/rpi-fc-usb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/images/flightController/rpi-fc-usb.jpg -------------------------------------------------------------------------------- /frontend/src/images/header/uavcast-logominimized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinamics/uavcast-community/HEAD/frontend/src/images/header/uavcast-logominimized.png -------------------------------------------------------------------------------- /frontend/src/scss/core/_logviewer.scss: -------------------------------------------------------------------------------- 1 | :is(.logContainer) { 2 | max-height: calc(100vh - 130px); 3 | // overflow-y: scroll; 4 | position: relative; 5 | } 6 | -------------------------------------------------------------------------------- /backend/src/graphql-types/MyContext.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | 3 | export interface MyContext { 4 | req: Request; 5 | res: Response; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "react" 5 | }, 6 | "includes": ["../"] 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/components/SidebarForm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SidebarForm", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./SidebarForm.js" 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/components/SidebarFooter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SidebarFooter", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./SidebarFooter.js" 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/components/SidebarHeader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SidebarHeader", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./SidebarHeader.js" 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 130, 3 | "useTabs": false, 4 | "jsxSingleQuote": true, 5 | "singleQuote": true, 6 | "trailingComma": "none", 7 | "tabWidth": 2 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/global.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | declare module 'winston' { 3 | interface Logger { 4 | server: any; 5 | modem: any; 6 | applicationState: any; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/components/SidebarMinimizer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SidebarMinimizer", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./SidebarMinimizer.js" 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/graphql/subscription/stdout_kernel.graphql: -------------------------------------------------------------------------------- 1 | subscription stdout { 2 | stdout { 3 | message 4 | errors { 5 | path 6 | message 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/graphql-input-types/MapInput.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field } from 'type-graphql'; 2 | 3 | @InputType() 4 | export class MapInput { 5 | @Field() 6 | mavCockpitDisable: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /bin/includes/flight_controller.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class FlightController 5 | { 6 | 7 | public: 8 | FlightControllerRecords get_flight_controller(); 9 | }; 10 | -------------------------------------------------------------------------------- /frontend/src/graphql/subscription/camera.graphql: -------------------------------------------------------------------------------- 1 | subscription camera_stdout { 2 | camera_stdout { 3 | message 4 | errors { 5 | path 6 | message 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/graphql/subscription/supervisor.graphql: -------------------------------------------------------------------------------- 1 | subscription supervisor { 2 | supervisor { 3 | message 4 | errors { 5 | path 6 | message 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | dist/* 3 | src/mavlink/v2.0/dialects 4 | src/mavlink/v2.0/processor.js 5 | src/mavlink/v2.0/mavCommand/* 6 | src/mavlink/lib/* 7 | src/migration 8 | 9 | ormconfig.js -------------------------------------------------------------------------------- /frontend/src/scss/style.scss: -------------------------------------------------------------------------------- 1 | @import 'theme'; 2 | 3 | @import 'variables'; 4 | 5 | @import 'bootstrap/bootstrap'; 6 | 7 | @import 'core/core'; 8 | 9 | @import '~semantic-ui-css/semantic.css'; 10 | -------------------------------------------------------------------------------- /backend/src/graphql-types/Upload.ts: -------------------------------------------------------------------------------- 1 | import { Stream } from 'stream'; 2 | 3 | export interface Upload { 4 | filename: string; 5 | mimetype: string; 6 | encoding: string; 7 | createReadStream: () => Stream; 8 | } 9 | -------------------------------------------------------------------------------- /bin/includes/camera.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class Camera 5 | { 6 | 7 | public: 8 | int rtsp_docker_start(); 9 | int gst_docker_start(); 10 | int initialize(); 11 | int teardown(); 12 | }; -------------------------------------------------------------------------------- /frontend/src/scss/bootstrap/_utilities.scss: -------------------------------------------------------------------------------- 1 | @import 'utilities/borders'; 2 | @import 'utilities/display'; 3 | @import 'utilities/flex'; 4 | @import 'utilities/spacing'; 5 | @import 'utilities/text'; 6 | @import 'utilities/visibility'; 7 | -------------------------------------------------------------------------------- /frontend/src/scss/core/_chart.scss: -------------------------------------------------------------------------------- 1 | .c3-axis-y text { 2 | fill: rgb(71, 71, 71); 3 | font-size: 13px; 4 | } 5 | .c3-axis-x, 6 | .c3-legend-item-event text { 7 | font-size: 13px; 8 | fill: rgb(126, 126, 126); 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/graphql-response-types/Map.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field } from 'type-graphql'; 2 | 3 | @ObjectType() 4 | export class MapResponse { 5 | @Field(() => Boolean, { nullable: false }) 6 | mavCockpitDisable?: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /docker/bin/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # set -e 3 | 4 | # Make sure we set the webport stored in the database, if user uses another port than standard 80 5 | /app/uavcast/bin/build/uav_main -p >> waitforit.txt 6 | 7 | 8 | exec "$@" 9 | -------------------------------------------------------------------------------- /frontend/src/graphql/mutation/application.graphql: -------------------------------------------------------------------------------- 1 | mutation updateApplication($properties: ApplicationProperties!) { 2 | updateApplication(properties: $properties) { 3 | properties { 4 | id 5 | webPort 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/graphql/mutation/modem.graphql: -------------------------------------------------------------------------------- 1 | mutation storeModemValues($properties: ModemProperties!) { 2 | storeModemValues(properties: $properties) { 3 | message { 4 | modemType 5 | modemInformation 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/graphql-input-types/AuthInput.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field } from 'type-graphql'; 2 | 3 | @InputType() 4 | export class AuthInput { 5 | @Field() 6 | email: string; 7 | 8 | @Field() 9 | password: string; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/scss/bootstrap/mixins/_text-truncate.scss: -------------------------------------------------------------------------------- 1 | // Text truncate 2 | // Requires inline-block or block for proper styling 3 | 4 | @mixin text-truncate() { 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | white-space: nowrap; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@jest/types'; 2 | // Or async function 3 | // Sync object 4 | const config: Config.InitialOptions = { 5 | verbose: true, 6 | transform: { '^.+\\.cjs$': 'babel-jest' } 7 | }; 8 | export default config; 9 | -------------------------------------------------------------------------------- /bin/includes/modem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class Modem 5 | { 6 | 7 | public: 8 | std::string exec(const char *cmd); 9 | int connectWithModemManager(); 10 | int connect(); 11 | std::string get_modem(db_modem *modem); 12 | }; -------------------------------------------------------------------------------- /bin/includes/mavlink.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class Mavlink 5 | { 6 | 7 | public: 8 | int disconnect(); 9 | int connect(); 10 | int navio(FlightControllerRecords fc_val); 11 | int ardupilot(FlightControllerRecords fc_val); 12 | }; -------------------------------------------------------------------------------- /frontend/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /backend/src/graphql-response-types/FieldError.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from "type-graphql"; 2 | 3 | @ObjectType() 4 | export class FieldError { 5 | @Field({ nullable: true }) 6 | path?: string; 7 | 8 | @Field({ nullable: true }) 9 | message?: string; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/graphql/subscription/global.graphql: -------------------------------------------------------------------------------- 1 | subscription status { 2 | status { 3 | mavproxy 4 | has_camera 5 | video 6 | modem 7 | uavcast_systemd_active 8 | uavcast_systemd_enabled 9 | vpn 10 | undervoltage 11 | arch 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /etc/mavlink-router-example.conf: -------------------------------------------------------------------------------- 1 | [General] 2 | TcpServerPort=5790 3 | ReportStats=false 4 | MavlinkDialect=auto 5 | 6 | [UartEndpoint controller] 7 | Device=/dev/ttyACM0 8 | Baud=57600 9 | 10 | [UdpEndpoint localhost] 11 | Mode=Normal 12 | Address=127.0.0.1 13 | Port=12550 -------------------------------------------------------------------------------- /frontend/src/scss/bootstrap/utilities/_visibility.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable declaration-no-important 2 | 3 | // 4 | // Visibility utilities 5 | // 6 | 7 | .visible { 8 | visibility: visible !important; 9 | } 10 | 11 | .invisible { 12 | visibility: hidden !important; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: 'http://localhost:4000/graphql' 3 | documents: 'src/graphql/**/*.graphql' 4 | generates: 5 | src/graphql/generated/dist.tsx: 6 | plugins: 7 | - 'typescript' 8 | - 'typescript-operations' 9 | - 'typescript-react-apollo' 10 | -------------------------------------------------------------------------------- /init/mavlink-router.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=MAVLink Router 3 | [Service] 4 | Type=simple 5 | ExecStart=/app/uavcast/bin/mavlink/mavlink-routerd 6 | StandardOutput=journal+console 7 | StandardError=inherit 8 | Restart=on-failure 9 | RestartSec=5 10 | [Install] 11 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /backend/src/graphql-input-types/MavlinkInput.ts: -------------------------------------------------------------------------------- 1 | import { ArgsType, InputType, Field } from 'type-graphql'; 2 | 3 | @ArgsType() 4 | @InputType() 5 | export class MavlinkCommandInput { 6 | @Field({ nullable: true }) 7 | type: string; 8 | 9 | @Field({ nullable: true }) 10 | value: string; 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/graphql/mutation/kernel_message.graphql: -------------------------------------------------------------------------------- 1 | mutation childProcessCmd($cmd: String!, $path: String!, $shell: Boolean, $logg: Boolean) { 2 | childProcessCmd(cmd: $cmd, path: $path, shell: $shell, logg: $logg) { 3 | message 4 | errors { 5 | path 6 | message 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/graphql/query/fligth_controller.graphql: -------------------------------------------------------------------------------- 1 | query flightController { 2 | flightController { 3 | data { 4 | id 5 | controller 6 | protocol 7 | connectionType 8 | internalAddress 9 | baudRate 10 | tcpPort 11 | binFlightLog 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/components/spinner.tsx: -------------------------------------------------------------------------------- 1 | import { Dimmer, Loader } from 'semantic-ui-react'; 2 | 3 | const Spinner = ({ children, size = 'massive' }: any) => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | }; 10 | 11 | export default Spinner; 12 | -------------------------------------------------------------------------------- /frontend/src/graphql/query/endpoints.graphql: -------------------------------------------------------------------------------- 1 | query getEndpoints { 2 | getEndpoints { 3 | data { 4 | id 5 | telemEnable 6 | moduleActive 7 | name 8 | endpointIPaddress 9 | telemetryPort 10 | videoPort 11 | videoEnable 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docker/bin/install-prod-addon.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # ---------------------------------------------------------------- 3 | # UAVcast installation file. 4 | # Author Bernt Christian Egeland 5 | # 6 | # !Production specific addons 7 | # ---------------------------------------------------------------- 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "/frontend/src/translations/locales"] 2 | path = frontend/src/translations/locales 3 | url = https://sinamics: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@github.com/UAVmatrix/uavcast-pro-translations.git 4 | [submodule "supervisor"] 5 | path = supervisor 6 | url = https://github.com/sinamics/uavcast-supervisor.git 7 | -------------------------------------------------------------------------------- /backend/src/factories/logger.factory.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '../entity/Logger'; 2 | import { define } from 'typeorm-seeding'; 3 | 4 | // pet.factory.ts 5 | define(Logger, () => { 6 | const log = new Logger(); 7 | log.altitude = true; 8 | log.cellSignal = true; 9 | log.satellites = true; 10 | return log; 11 | }); 12 | -------------------------------------------------------------------------------- /backend/src/utils/pubsub.ts: -------------------------------------------------------------------------------- 1 | import { PubSub } from 'apollo-server-express'; 2 | 3 | const pubsub = new PubSub(); 4 | export default pubsub; 5 | 6 | let isConnected = false; 7 | export const setConStaus = (sta: boolean) => { 8 | isConnected = sta; 9 | }; 10 | export const getConStaus = () => { 11 | return isConnected; 12 | }; 13 | -------------------------------------------------------------------------------- /backend/src/resolvers/MapResovler.ts: -------------------------------------------------------------------------------- 1 | import { MapResponse } from '../graphql-response-types/Map'; 2 | import { Query, Resolver } from 'type-graphql'; 3 | import { Map } from '../entity/Map'; 4 | 5 | @Resolver() 6 | export class MapResovler { 7 | @Query(() => MapResponse) 8 | async map(): Promise { 9 | return await Map.findOne(1); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/_helpers/colors.tsx: -------------------------------------------------------------------------------- 1 | // helper function to set colors when editing surveys 2 | export function hsl_col_perc(percent: number, start: number, end: number) { 3 | const a = percent / 100; 4 | const b = (end - start) * a; 5 | const c = b + start; 6 | 7 | // Return a CSS HSL string 8 | return 'hsl(' + c + ', 100%, 50%)'; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/graphql/query/modem.graphql: -------------------------------------------------------------------------------- 1 | query modemData { 2 | modemData { 3 | message { 4 | modemType 5 | modemInformation 6 | modemInterface 7 | enableModem 8 | internalAddress 9 | pinCode 10 | username 11 | password 12 | } 13 | } 14 | } 15 | query nic { 16 | nic { 17 | interfaces 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/graphql-response-types/KernelResponse.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field } from 'type-graphql'; 2 | import { FieldError } from './FieldError'; 3 | 4 | @ObjectType() 5 | export class KernelResponse { 6 | @Field(() => String, { nullable: true }) 7 | message: JSON; 8 | 9 | @Field(() => [FieldError], { nullable: true }) 10 | errors: FieldError[]; 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/components/SidebarForm/SidebarForm.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | 3 | class SidebarForm extends Component { 4 | render() { 5 | return null; 6 | // Uncomment following code lines to add Sidebar Form 7 | // return ( 8 | //
9 | // ) 10 | } 11 | } 12 | 13 | export default SidebarForm; 14 | -------------------------------------------------------------------------------- /frontend/src/components/SidebarFooter/SidebarFooter.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | 3 | class SidebarFooter extends Component { 4 | render() { 5 | return null; 6 | // Uncomment following code lines to add Sidebar Footer 7 | // return ( 8 | //
9 | // ) 10 | } 11 | } 12 | 13 | export default SidebarFooter; 14 | -------------------------------------------------------------------------------- /frontend/src/components/SidebarHeader/SidebarHeader.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | 3 | class SidebarHeader extends Component { 4 | render() { 5 | return null; 6 | // Uncomment following code lines to add Sidebar Header 7 | // return ( 8 | //
9 | // ) 10 | } 11 | } 12 | 13 | export default SidebarHeader; 14 | -------------------------------------------------------------------------------- /backend/src/graphql-input-types/KernelInput.ts: -------------------------------------------------------------------------------- 1 | import { ArgsType, Field } from 'type-graphql'; 2 | 3 | @ArgsType() 4 | export class KernelInput { 5 | // @Field({ nullable: true }) 6 | @Field() 7 | cmd: string; 8 | 9 | @Field({ nullable: true }) 10 | shell?: boolean; 11 | 12 | @Field() 13 | path?: string; 14 | 15 | @Field({ nullable: true }) 16 | logg?: boolean; 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/components/Header/HeaderDropdown.tsx: -------------------------------------------------------------------------------- 1 | const HeaderDropdown = (props: any) => { 2 | // const { notif, accnt, tasks, mssgs} = props; 3 | return ( 4 |
5 | // notif ? this.dropNotif() : 6 | // accnt ? this.dropAccnt() : 7 | // tasks ? this.dropTasks() : 8 | // mssgs ? this.dropMssgs() : null 9 | ); 10 | }; 11 | 12 | export default HeaderDropdown; 13 | -------------------------------------------------------------------------------- /frontend/src/scss/bootstrap/_transitions.scss: -------------------------------------------------------------------------------- 1 | .fade { 2 | @include transition($transition-fade); 3 | 4 | &:not(.show) { 5 | opacity: 0; 6 | } 7 | } 8 | 9 | .collapse { 10 | &:not(.show) { 11 | display: none; 12 | } 13 | } 14 | 15 | .collapsing { 16 | position: relative; 17 | height: 0; 18 | overflow: hidden; 19 | @include transition($transition-collapse); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/graphql/mutation/vpn.graphql: -------------------------------------------------------------------------------- 1 | mutation storeVpnValues($properties: VpnProperties!) { 2 | storeVpnValues(properties: $properties) { 3 | data { 4 | id 5 | enableVpn 6 | serviceProvider 7 | networkId 8 | username 9 | password 10 | } 11 | } 12 | } 13 | mutation uploadConfigFile($file: Upload!) { 14 | uploadConfigFile(file: $file) 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/graphql-response-types/UserResponse.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field } from "type-graphql"; 2 | import { FieldError } from "./FieldError"; 3 | import { User } from "../entity/User"; 4 | 5 | @ObjectType() 6 | export class UserResponse { 7 | @Field(() => User, { nullable: true }) 8 | user?: User; 9 | 10 | @Field(() => [FieldError], { nullable: true }) 11 | errors?: FieldError[]; 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/scss/bootstrap/mixins/_text-hide.scss: -------------------------------------------------------------------------------- 1 | // CSS image replacement 2 | @mixin text-hide($ignore-warning: false) { 3 | // stylelint-disable-next-line font-family-no-missing-generic-family-keyword 4 | font: 0/0 a; 5 | color: transparent; 6 | text-shadow: none; 7 | background-color: transparent; 8 | border: 0; 9 | 10 | @include deprecate("`text-hide()`", "v4.1.0", "v5", $ignore-warning); 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/mavlink/v2.0/mavLog.js: -------------------------------------------------------------------------------- 1 | // var fs = require('fs'); 2 | 3 | // class mavLogg { 4 | // constructor(name) { 5 | // this.wstream = fs.createWriteStream('05.06.2020.tlog', { highWaterMark: 12000 }); 6 | // } 7 | // startLogging(stream) { 8 | // this.wstream.write(stream); 9 | // } 10 | // stopLogging() { 11 | // this.wstream.end(); 12 | // } 13 | // } 14 | 15 | // module.exports = mavLogg; 16 | -------------------------------------------------------------------------------- /bin/includes/flight_controller.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "db.h" 6 | #include "flight_controller.h" 7 | 8 | FlightControllerRecords FlightController::get_flight_controller() 9 | { 10 | Database db; 11 | db_flight_controller fc_value; 12 | FlightControllerRecords fc_record = db.get_flightcontroller(); 13 | 14 | return {}; 15 | }; -------------------------------------------------------------------------------- /backend/src/graphql-response-types/ModemResponse.ts: -------------------------------------------------------------------------------- 1 | import { Modem } from '../entity/Modem'; 2 | import { ObjectType, Field } from 'type-graphql'; 3 | 4 | @ObjectType() 5 | export class ModemResponse { 6 | @Field(() => Modem, { nullable: true }) 7 | message: Modem; 8 | } 9 | 10 | // NIC Response 11 | 12 | @ObjectType() 13 | export class NicResponse { 14 | @Field(() => String, { nullable: true }) 15 | interfaces: [string]; 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sinamics/uavcast", 3 | "private": false, 4 | "scripts": { 5 | "start": "concurrently \"cd frontend && npm start\" \"cd backend && ts-node-dev --respawn --transpile-only src/index.ts\" ", 6 | "install": "concurrently \"cd frontend && npm install\" \"cd backend && npm install\" ", 7 | "codegen": "cd frontend && npm run codegen" 8 | }, 9 | "dependencies": {}, 10 | "license": "BSD-3-Clause" 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": ["${workspaceFolder}/**", "/app/uavcast/bin/", "/app/uavcast/bin/includes"], 6 | "defines": [], 7 | "compilerPath": "/usr/bin/gcc", 8 | // "compilerPath": "", 9 | "cStandard": "gnu17", 10 | "cppStandard": "gnu++14" 11 | // "intelliSenseMode": "" 12 | } 13 | ], 14 | "version": 4 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/graphql-response-types/ApplicationResponse.ts: -------------------------------------------------------------------------------- 1 | import { Application } from '../entity/Application'; 2 | import { ObjectType, Field } from 'type-graphql'; 3 | import { FieldError } from './FieldError'; 4 | 5 | @ObjectType() 6 | export class ApplicationResponse { 7 | @Field(() => Application, { nullable: true }) 8 | properties?: Application; 9 | 10 | @Field(() => [FieldError], { nullable: true }) 11 | errors?: FieldError[]; 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/graphql/query/vpn.graphql: -------------------------------------------------------------------------------- 1 | query vpnData { 2 | vpnData { 3 | data { 4 | id 5 | enableVpn 6 | serviceProvider 7 | networkId 8 | username 9 | password 10 | } 11 | } 12 | } 13 | 14 | query zerotierNetworks { 15 | zerotierNetworks { 16 | networks { 17 | assignedAddresses 18 | name 19 | nwid 20 | portDeviceName 21 | status 22 | type 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/graphql-response-types/FlightController.ts: -------------------------------------------------------------------------------- 1 | import { FlightController } from '../entity/FlightController'; 2 | import { Field, ObjectType } from 'type-graphql'; 3 | import { FieldError } from './FieldError'; 4 | 5 | @ObjectType() 6 | export class FlightControllerResponse { 7 | @Field(() => FlightController, { nullable: true }) 8 | data: FlightController; 9 | 10 | @Field(() => [FieldError], { nullable: true }) 11 | errors?: FieldError[]; 12 | } 13 | -------------------------------------------------------------------------------- /bin/includes/logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define LOG(x) std::cout << x << std::endl; 4 | 5 | class Logger 6 | { 7 | public: 8 | enum Level {LevelError, LevelWarning, LevelInfo, LevelPlain}; 9 | private: 10 | Level m_loglevel = LevelPlain; 11 | public: 12 | void SetLevel(Level level); 13 | void Plain(const char* message); 14 | void Info(const char* message); 15 | void Warn(const char* message); 16 | void Error(const char* message); 17 | }; 18 | -------------------------------------------------------------------------------- /.github/workflows/releases.yml: -------------------------------------------------------------------------------- 1 | name: 'tagged-release' 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | tagged-release: 11 | name: 'Tagged Release' 12 | runs-on: 'ubuntu-latest' 13 | 14 | steps: 15 | - uses: 'marvinpinto/action-automatic-releases@latest' 16 | with: 17 | repo_token: '${{ secrets.GITHUB_TOKEN }}' 18 | prerelease: false 19 | files: | 20 | LICENSE 21 | -------------------------------------------------------------------------------- /backend/src/seeds/vpn.seed.ts: -------------------------------------------------------------------------------- 1 | import { Factory, Seeder } from 'typeorm-seeding'; 2 | import { Connection } from 'typeorm'; 3 | import { Vpn } from '../entity/Vpn'; 4 | 5 | export class CreateVpn implements Seeder { 6 | public async run(_: Factory, connection: Connection): Promise { 7 | await connection 8 | .createQueryBuilder() 9 | .insert() 10 | .into(Vpn) 11 | .values([{ ensure: 1, enableVpn: false }]) 12 | .execute(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /init/uavcast-vpn.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=uavcast-vpn controller 3 | Requires=network-online.target 4 | Wants=network-online.target 5 | After=network-online.target 6 | [Service] 7 | WorkingDirectory=/app/uavcast/bin/build 8 | Type=simple 9 | GuessMainPID=no 10 | ExecStart=/app/uavcast/bin/build/uav_vpn -o start 11 | KillMode=control-group 12 | SyslogIdentifier=uavcast-vpn 13 | StandardOutput=journal+console 14 | StandardError=inherit 15 | [Install] 16 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /frontend/src/scss/bootstrap/bootstrap.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v4.6.0 (https://getbootstrap.com/) 3 | * Copyright 2011-2021 The Bootstrap Authors 4 | * Copyright 2011-2021 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | */ 7 | 8 | @import 'functions'; 9 | @import 'variables'; 10 | @import 'mixins'; 11 | @import 'root'; 12 | @import 'reboot'; 13 | @import 'grid'; 14 | @import 'nav'; 15 | @import 'navbar'; 16 | @import 'utilities'; 17 | -------------------------------------------------------------------------------- /init/uavcast-camera.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=uavcast-camera controller 3 | Requires=network-online.target 4 | Wants=network-online.target 5 | After=network-online.target 6 | [Service] 7 | WorkingDirectory=/app/uavcast/bin/build 8 | Type=simple 9 | GuessMainPID=no 10 | ExecStart=/app/uavcast/bin/build/uav_camera -start 11 | KillMode=control-group 12 | SyslogIdentifier=uavcast-camera 13 | StandardOutput=journal+console 14 | StandardError=inherit 15 | [Install] 16 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /init/uavcast-web.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Webinterface for UAVcast 3 | [Service] 4 | WorkingDirectory=/app/uavcast 5 | Type=simple 6 | GuessMainPID=no 7 | ExecStart=/usr/bin/node /app/uavcast/backend/dist/index.js 8 | KillMode=control-group 9 | SyslogIdentifier=uavcast-webinterface 10 | StandardOutput=journal+console 11 | StandardError=inherit 12 | Restart=on-failure 13 | RestartSec=10 14 | Environment="NODE_ENV=production" "SERVER_PORT=80" 15 | [Install] 16 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /backend/src/graphql-input-types/SupervisorInput.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field, ArgsType } from 'type-graphql'; 2 | 3 | @InputType() 4 | @ArgsType() 5 | export class SupervisorInput { 6 | @Field({ nullable: true }) 7 | version: string; 8 | 9 | @Field({ nullable: true }) 10 | type: string; 11 | 12 | @Field({ nullable: true }) 13 | command: string; 14 | } 15 | @InputType() 16 | @ArgsType() 17 | export class getApplicationVersionInput { 18 | @Field() 19 | application: string; 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/utils/supervisor.ts: -------------------------------------------------------------------------------- 1 | import { io } from 'socket.io-client'; 2 | import pubsub from './pubsub'; 3 | import { paths } from '../config/paths'; 4 | 5 | export const socket = io(paths.supervisorWsAddress); 6 | 7 | export const supervisor = async() => { 8 | socket.on('SUPERVISOR_MESSAGES', async(message: any, errors: any) => { 9 | if (errors) return await pubsub.publish('U_SUPERVISOR_MESSAGE', { errors }); 10 | await pubsub.publish('U_SUPERVISOR_MESSAGE', { message }); 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/src/scss/bootstrap/bootstrap-reboot.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.6.0 (https://getbootstrap.com/) 3 | * Copyright 2011-2021 The Bootstrap Authors 4 | * Copyright 2011-2021 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */ 8 | 9 | @import "functions"; 10 | @import "variables"; 11 | @import "mixins"; 12 | @import "reboot"; 13 | -------------------------------------------------------------------------------- /backend/src/seeds/camera.seed.ts: -------------------------------------------------------------------------------- 1 | import { Factory, Seeder } from 'typeorm-seeding'; 2 | import { Connection } from 'typeorm'; 3 | import { Camera } from '../entity/Camera'; 4 | 5 | export class CreateCamera implements Seeder { 6 | public async run(_: Factory, connection: Connection): Promise { 7 | await connection 8 | .createQueryBuilder() 9 | .insert() 10 | .into(Camera) 11 | .values([{ ensure: 1, enableCamera: true, protocol: 'udp' }]) 12 | .execute(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /backend/src/mavlink/v2.0/udp_client.ts: -------------------------------------------------------------------------------- 1 | import udp from 'dgram'; 2 | 3 | class UdpClient { 4 | port: any; 5 | localhost: string; 6 | udpStream: udp.Socket; 7 | constructor() { 8 | // the serial device object 9 | this.port = 12550; 10 | this.localhost = '127.0.0.1'; 11 | this.udpStream = udp.createSocket({ type: 'udp4', reuseAddr: true }); 12 | this.udpStream.bind(this.port); 13 | } 14 | 15 | closeLink() { 16 | this.udpStream.close(); 17 | } 18 | } 19 | 20 | export default UdpClient; 21 | -------------------------------------------------------------------------------- /frontend/src/scss/core/core.scss: -------------------------------------------------------------------------------- 1 | // Import core styles 2 | @import 'variables'; 3 | @import 'animate'; 4 | @import 'mixins'; 5 | @import 'switch'; 6 | @import 'switches'; 7 | @import 'reboot'; 8 | 9 | // Animations 10 | @import 'animate'; 11 | 12 | // Components 13 | @import 'nav'; 14 | @import 'navbar'; 15 | 16 | @import 'sidebar'; 17 | @import 'slider'; 18 | 19 | // Layout Options 20 | @import 'layout'; 21 | 22 | @import 'led'; 23 | @import 'switch'; 24 | @import 'map'; 25 | @import 'chart'; 26 | @import 'logviewer'; 27 | -------------------------------------------------------------------------------- /backend/src/config/paths.ts: -------------------------------------------------------------------------------- 1 | import root from 'app-root-path'; 2 | import path from 'path'; 3 | 4 | // set dev and prod docker location 5 | export const paths = { 6 | binFolder: path.join(root.path, '../', '/bin/build/'), 7 | pythonFolder: path.join(root.path, '../', '/bin/python/'), 8 | logFolder: path.join(root.path, '../', '/data/log/'), 9 | dbFolder: path.join(root.path, '../', '/data/sql/'), 10 | dbFile: path.join(root.path, '../', '/data/sql', 'uavcast.db'), 11 | supervisorWsAddress: 'ws://127.0.0.1:32500' 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/src/pages/modem/components/dropdown-arrays.tsx: -------------------------------------------------------------------------------- 1 | export const MM_Model = [ 2 | { name: 'MM_Model', value: 'Stick', label: 'Stick' }, 3 | { name: 'MM_Model', value: 'HiLink', label: 'HiLink' } 4 | ]; 5 | export const NetworkManager = [ 6 | { name: 'MM_init', value: 'nmcli', label: 'Network Manager (nmcli)' }, 7 | { name: 'MM_init', value: 'mmcli', label: 'Modem Manager (mmcli)' } 8 | ]; 9 | export const YesNo = [ 10 | { key: 'true', value: true, text: 'Yes' }, 11 | { key: 'false', value: false, text: 'No' } 12 | ]; 13 | -------------------------------------------------------------------------------- /frontend/src/pages/vpn/components/dropdown-arrays.tsx: -------------------------------------------------------------------------------- 1 | export const MM_Model = [ 2 | { name: 'MM_Model', value: 'Stick', label: 'Stick' }, 3 | { name: 'MM_Model', value: 'HiLink', label: 'HiLink' } 4 | ]; 5 | export const NetworkManager = [ 6 | { name: 'MM_init', value: 'nmcli', label: 'Network Manager (nmcli)' }, 7 | { name: 'MM_init', value: 'mmcli', label: 'Modem Manager (mmcli)' } 8 | ]; 9 | export const YesNo = [ 10 | { key: 'true', value: true, text: 'Yes' }, 11 | { key: 'false', value: false, text: 'No' } 12 | ]; 13 | -------------------------------------------------------------------------------- /backend/src/seeds/application.seed.ts: -------------------------------------------------------------------------------- 1 | import { Factory, Seeder } from 'typeorm-seeding'; 2 | import { Connection } from 'typeorm'; 3 | import { Application } from '../entity/Application'; 4 | 5 | export class CreateApplication implements Seeder { 6 | public async run(_: Factory, connection: Connection): Promise { 7 | await connection 8 | .createQueryBuilder() 9 | .insert() 10 | .into(Application) 11 | .values([{ ensure: 1, hasBeenUpdated: false, webPort: 80 }]) 12 | .execute(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/pages/flightController/components/dropdown-arrays.tsx: -------------------------------------------------------------------------------- 1 | export const MM_Model = [ 2 | { name: 'MM_Model', value: 'Stick', label: 'Stick' }, 3 | { name: 'MM_Model', value: 'HiLink', label: 'HiLink' } 4 | ]; 5 | export const NetworkManager = [ 6 | { name: 'MM_init', value: 'nmcli', label: 'Network Manager (nmcli)' }, 7 | { name: 'MM_init', value: 'mmcli', label: 'Modem Manager (mmcli)' } 8 | ]; 9 | export const YesNo = [ 10 | { key: 'true', value: true, text: 'Yes' }, 11 | { key: 'false', value: false, text: 'No' } 12 | ]; 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | # dependencies 3 | node_modules/ 4 | 5 | /.pnp 6 | .pnp.js 7 | 8 | #exclude all build folders 9 | build/ 10 | 11 | #exclude all dist folders 12 | dist/ 13 | 14 | #data, logs 15 | /data 16 | 17 | #backend 18 | /bin/**/*.o 19 | /bin/build 20 | 21 | /data/ 22 | 23 | # misc 24 | .DS_Store 25 | .env.local 26 | .env.development.local 27 | .env.test.local 28 | .env.production.local 29 | 30 | npm-debug.log* 31 | yarn-debug.log* 32 | yarn-error.log* 33 | 34 | -------------------------------------------------------------------------------- /backend/src/seeds/logger.seed.ts: -------------------------------------------------------------------------------- 1 | import { Factory, Seeder } from 'typeorm-seeding'; 2 | import { Connection } from 'typeorm'; 3 | import { Logger } from '../entity/Logger'; 4 | 5 | export class CreateLogger implements Seeder { 6 | public async run(_: Factory, connection: Connection): Promise { 7 | await connection 8 | .createQueryBuilder() 9 | .insert() 10 | .into(Logger) 11 | .values([{ ensure: 1, cellSignal: true, altitude: true, datetime: 0, satellites: true, resolution: 1.0 }]) 12 | .execute(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /init/uavcast.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=uavcast, casting controller 3 | Requires=network-online.target 4 | Wants=network-online.target 5 | After=network-online.target 6 | [Service] 7 | WorkingDirectory=/app/uavcast 8 | Type=simple 9 | GuessMainPID=no 10 | ExecStart=/app/uavcast/bin/build/uav_main -a 11 | ExecStop=/app/uavcast/bin/build/uav_main -s 12 | RemainAfterExit=yes 13 | KillMode=control-group 14 | SyslogIdentifier=uavcast 15 | StandardOutput=journal+console 16 | StandardError=inherit 17 | Restart=on-failure 18 | [Install] 19 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /backend/src/seeds/modem.seed.ts: -------------------------------------------------------------------------------- 1 | import { Factory, Seeder } from 'typeorm-seeding'; 2 | import { Connection } from 'typeorm'; 3 | import { Modem } from '../entity/Modem'; 4 | 5 | export class CreateModem implements Seeder { 6 | public async run(_: Factory, connection: Connection): Promise { 7 | await connection 8 | .createQueryBuilder() 9 | .insert() 10 | .into(Modem) 11 | .values([{ ensure: 1, enableModem: false, modemInformation: false, modemInterface: 'eth1', internalAddress: 'cdc-wdm0' }]) 12 | .execute(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/scss/bootstrap/utilities/_stretched-link.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Stretched link 3 | // 4 | 5 | .stretched-link { 6 | &::after { 7 | position: absolute; 8 | top: 0; 9 | right: 0; 10 | bottom: 0; 11 | left: 0; 12 | z-index: 1; 13 | // Just in case `pointer-events: none` is set on a parent 14 | pointer-events: auto; 15 | content: ""; 16 | // IE10 bugfix, see https://stackoverflow.com/questions/16947967/ie10-hover-pseudo-class-doesnt-work-without-background-color 17 | background-color: rgba(0, 0, 0, 0); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/scss/bootstrap/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Toggles 2 | // 3 | // Used in conjunction with global variables to enable certain theme features. 4 | 5 | // Vendor 6 | @import 'vendor/rfs'; 7 | 8 | // Deprecate 9 | @import 'mixins/deprecate'; 10 | 11 | // Utilities 12 | @import 'mixins/breakpoints'; 13 | @import 'mixins/hover'; 14 | @import 'mixins/text-emphasis'; 15 | @import 'mixins/text-hide'; 16 | @import 'mixins/text-truncate'; 17 | 18 | // Skins 19 | @import 'mixins/border-radius'; 20 | 21 | // Layout 22 | @import 'mixins/grid-framework'; 23 | @import 'mixins/grid'; 24 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | services: 3 | dev: 4 | container_name: uavcast-dev 5 | user: uavcast 6 | privileged: true 7 | network_mode: host 8 | build: 9 | context: . 10 | dockerfile: docker/Dockerfile.dev 11 | restart: always 12 | volumes: 13 | - .:/app/uavcast:cached 14 | - uavdata:/app/uavcast/data 15 | - /var/lib/zerotier-one:/var/lib/zerotier-one 16 | - /var/run/docker.sock:/var/run/docker.sock 17 | - /dev:/dev 18 | entrypoint: 19 | - '/bin/systemctl' 20 | volumes: 21 | uavdata: 22 | -------------------------------------------------------------------------------- /backend/src/graphql-input-types/ApplicationInput.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field, ArgsType } from 'type-graphql'; 2 | 3 | @InputType() 4 | export class ApplicationProperties { 5 | @Field({ nullable: true }) 6 | id: number; 7 | 8 | @Field({ nullable: true }) 9 | remoteVersionFetched?: boolean; 10 | 11 | @Field({ nullable: true }) 12 | hasBeenUpdated?: boolean; 13 | 14 | @Field({ nullable: true }) 15 | webPort?: number; 16 | } 17 | 18 | @InputType() 19 | @ArgsType() 20 | export class ApplicationInput { 21 | @Field() 22 | properties: ApplicationProperties; 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/app/publicRoute.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Route, Redirect } from 'react-router-dom'; 3 | 4 | interface RouteProps { 5 | component: React.FC; 6 | path: string; 7 | exact: boolean; 8 | } 9 | 10 | const PublicRoute: React.FC = (props): JSX.Element => { 11 | // If we need to validate public routes, lets do it here. Allowing all for now. 12 | const valid = true; 13 | return valid ? : ; 14 | }; 15 | 16 | export default PublicRoute; 17 | -------------------------------------------------------------------------------- /frontend/src/scss/core/_switch.scss: -------------------------------------------------------------------------------- 1 | .switch-green { 2 | background: #0c9c3e !important; 3 | box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 10px, #89ff00 0px 1px 1px; 4 | } 5 | .switch-red { 6 | background-color: #e60d0d !important; 7 | } 8 | .switch-yellow-blink { 9 | -webkit-animation: blinkYellow 1s infinite; 10 | -moz-animation: blinkYellow 1s infinite; 11 | -ms-animation: blinkYellow 1s infinite; 12 | -o-animation: blinkYellow 1s infinite; 13 | animation: blinkYellow 1s infinite; 14 | } 15 | 16 | .switch-off { 17 | background-color: #bcc5d0; 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/mavlink/v2.0/definitions/firmware.ts: -------------------------------------------------------------------------------- 1 | export const firmware: any = { 2 | 0: 'Generic autopilot', 3 | 1: 'Reserved', 4 | 2: 'autopilot', 5 | 3: 'ArduPilot', 6 | 4: 'OpenPilot', 7 | 5: 'Generic autopilot', 8 | 6: 'Generic autopilot', 9 | 7: 'Generic autopilot', 10 | 8: 'No valid autopilot', 11 | 9: 'PPZ UAV', 12 | 10: 'UAV Dev Board', 13 | 11: 'FlexiPilot', 14 | 12: 'PX4 Autopilot', 15 | 13: 'SMACCMPilot', 16 | 14: 'AutoQuad', 17 | 15: 'Armazila', 18 | 16: 'Aerob', 19 | 17: 'ASLUAV autopilot', 20 | 18: 'SmartAP Autopilot', 21 | 19: 'AirRails', 22 | }; -------------------------------------------------------------------------------- /frontend/src/scss/bootstrap/mixins/_text-emphasis.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable declaration-no-important 2 | 3 | // Typography 4 | 5 | @mixin text-emphasis-variant($parent, $color, $ignore-warning: false) { 6 | #{$parent} { 7 | color: $color !important; 8 | } 9 | @if $emphasized-link-hover-darken-percentage != 0 { 10 | a#{$parent} { 11 | @include hover-focus() { 12 | color: darken($color, $emphasized-link-hover-darken-percentage) !important; 13 | } 14 | } 15 | } 16 | @include deprecate("`text-emphasis-variant()`", "v4.4.0", "v5", $ignore-warning); 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/graphql-input-types/VpnInput.ts: -------------------------------------------------------------------------------- 1 | import { ArgsType, InputType, Field } from 'type-graphql'; 2 | 3 | @InputType() 4 | export class VpnProperties { 5 | @Field({ nullable: true }) 6 | enableVpn: boolean; 7 | 8 | @Field({ nullable: true }) 9 | serviceProvider: string; 10 | 11 | @Field({ nullable: true }) 12 | networkId: string; 13 | 14 | @Field({ nullable: true }) 15 | username: string; 16 | 17 | @Field({ nullable: true }) 18 | password: string; 19 | } 20 | 21 | @ArgsType() 22 | @InputType() 23 | export class VpnInput { 24 | @Field({ nullable: true }) 25 | properties: VpnProperties; 26 | } 27 | -------------------------------------------------------------------------------- /bin/includes/logger.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "logger.h" 4 | 5 | void Logger::Plain (const char* message) { 6 | if(m_loglevel >= LevelPlain) 7 | LOG(message); 8 | } 9 | 10 | void Logger::Info (const char* message) { 11 | if(m_loglevel >= LevelInfo) 12 | LOG("[INFO]: " << message); 13 | } 14 | 15 | void Logger::Warn (const char* message) { 16 | if(m_loglevel >= LevelWarning) 17 | LOG("[WARNING]: " << message); 18 | } 19 | 20 | void Logger::Error (const char* message) { 21 | if(m_loglevel >= LevelError) 22 | LOG("[Error]: " << message); 23 | } 24 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/pages/logs/containers/logger-delete.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Grid } from 'semantic-ui-react'; 3 | import PruneDockerLogs from './logger-delete-docker'; 4 | import PruneServerLogs from './logger-delete-server'; 5 | import PruneStatsLogs from './logger-delete-stats'; 6 | import PruneUlog from './logger-delete-ulog'; 7 | 8 | const PruneLogs = () => { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default PruneLogs; 20 | -------------------------------------------------------------------------------- /frontend/src/graphql/query/camera.graphql: -------------------------------------------------------------------------------- 1 | query cameraData { 2 | cameraData { 3 | database { 4 | id 5 | key 6 | name 7 | path 8 | protocol 9 | resolution 10 | enableCamera 11 | customPipeline 12 | framesPrSecond 13 | bitratePrSecond 14 | contrast 15 | rotation 16 | brightness 17 | whiteBalance 18 | flipCamera 19 | } 20 | availableCams { 21 | key 22 | value 23 | text 24 | caps { 25 | value 26 | text 27 | height 28 | width 29 | format 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/app/app.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, useEffect } from 'react'; 2 | import { withRouter } from 'react-router'; 3 | import Spinner from '../components/spinner'; 4 | import Routes from './routes'; 5 | 6 | // AUTH 7 | import './style.css'; 8 | 9 | const App: React.FC = (props: any): JSX.Element => { 10 | useEffect(() => { 11 | // Load theme 12 | document.documentElement.setAttribute('data-theme', window.localStorage.getItem('Theme') || ''); 13 | }, []); 14 | 15 | return ( 16 | }> 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default withRouter(App); 23 | -------------------------------------------------------------------------------- /frontend/src/graphql/mutation/supervisor.graphql: -------------------------------------------------------------------------------- 1 | # mutation connectSupervisor { 2 | # connectSupervisor 3 | # } 4 | 5 | mutation updateUavcastContainer($version: String!) { 6 | updateUavcastContainer(version: $version) { 7 | message 8 | errors { 9 | message 10 | path 11 | } 12 | } 13 | } 14 | mutation updateSupervisorContainer($version: String!) { 15 | updateSupervisorContainer(version: $version) { 16 | message 17 | errors { 18 | message 19 | path 20 | } 21 | } 22 | } 23 | mutation supervisorCommands($type: String!, $command: String) { 24 | supervisorCommands(type: $type, command: $command) 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/graphql-response-types/Endpoint.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field } from 'type-graphql'; 2 | import { FieldError } from './FieldError'; 3 | import { Endpoint } from '../entity/Endpoint'; 4 | 5 | @ObjectType() 6 | export class EndpointResponse { 7 | @Field(() => Endpoint, { nullable: true }) 8 | data?: Endpoint[]; 9 | 10 | @Field(() => [FieldError], { nullable: true }) 11 | errors?: FieldError[]; 12 | } 13 | 14 | @ObjectType() 15 | export class UpdateEndpointResponse { 16 | @Field(() => Endpoint, { nullable: true }) 17 | data?: Endpoint; 18 | 19 | @Field(() => [FieldError], { nullable: true }) 20 | errors?: FieldError[]; 21 | } 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | **Describe what you are trying to accomplish and why in non technical terms** 10 | I want to be able to ... so that I can ... 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.vscode/ssh_connect.config: -------------------------------------------------------------------------------- 1 | Se denne linken 2 | https://code.visualstudio.com/docs/remote/troubleshooting 3 | 4 | Bruk "Authorize your Windows machine to connect" 5 | 6 | 7 | Step #1 (På windows maskina) 8 | - ssh-keygen 9 | 10 | Step #2 (Kopier id_rsa.pub til raspberry) 11 | cd Users/solgt/.ssh 12 | scp .\id_rsa.pub pi@10.0.0.128:~/ 13 | 14 | $USER_AT_HOST="your-user-name-on-host@hostname" 15 | $PUBKEYPATH="$HOME\.ssh\id_rsa.pub" 16 | 17 | Step3 (skriv disse kommandoer til powershell) 18 | $pubKey=(Get-Content "$PUBKEYPATH" | Out-String); ssh "$USER_AT_HOST" "mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '${pubKey}' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" -------------------------------------------------------------------------------- /frontend/src/components/SidebarMinimizer/SidebarMinimizer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class SidebarMinimizer extends Component { 4 | sidebarMinimize() { 5 | document.body.classList.toggle('sidebar-minimized'); 6 | } 7 | 8 | brandMinimize() { 9 | document.body.classList.toggle('brand-minimized'); 10 | } 11 | 12 | render() { 13 | return ( 14 | 22 | 23 | 24 | ); 25 | }; 26 | export default Host; 27 | -------------------------------------------------------------------------------- /backend/src/graphql-response-types/GlobalResponse.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field } from 'type-graphql'; 2 | 3 | @ObjectType() 4 | export class StatusResponse { 5 | @Field(() => Boolean, { nullable: true }) 6 | mavproxy: boolean; 7 | 8 | @Field(() => Boolean, { nullable: true }) 9 | has_camera: boolean; 10 | 11 | @Field(() => Boolean, { nullable: true }) 12 | video: boolean; 13 | 14 | @Field(() => Boolean, { nullable: true }) 15 | modem: boolean; 16 | 17 | @Field(() => Boolean, { nullable: true }) 18 | uavcast_systemd_active: boolean; 19 | 20 | @Field(() => Boolean, { nullable: true }) 21 | uavcast_systemd_enabled: boolean; 22 | 23 | @Field(() => Boolean, { nullable: true }) 24 | vpn: boolean; 25 | 26 | @Field(() => Boolean, { nullable: true }) 27 | undervoltage: boolean; 28 | 29 | @Field(() => String, { nullable: true }) 30 | arch: string; 31 | } 32 | -------------------------------------------------------------------------------- /backend/src/entity/Application.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Check, PrimaryColumn, Column, BaseEntity, getRepository } from 'typeorm'; 2 | import { Field, ID, ObjectType } from 'type-graphql'; 3 | @ObjectType({ isAbstract: true }) 4 | @Entity() 5 | @Check(`ensure = 1`) 6 | export class Application extends BaseEntity { 7 | @PrimaryColumn({ type: 'int', default: () => `1`, nullable: false }) 8 | public ensure: 1; 9 | 10 | @Field(() => ID) 11 | @Column({ type: Number, default: 1 }) 12 | id: number; 13 | 14 | @Field() 15 | @Column({ type: Date, default: false }) 16 | remoteVersionFetched: Date = new Date(); 17 | 18 | @Field() 19 | @Column({ type: Boolean, default: false }) 20 | hasBeenUpdated: boolean; 21 | 22 | @Field() 23 | @Column({ type: Number, default: false }) 24 | webPort: number; 25 | } 26 | export const getApplicationRepository = () => { 27 | return getRepository(Application); 28 | }; 29 | -------------------------------------------------------------------------------- /frontend/src/graphql/mutation/endpoint.graphql: -------------------------------------------------------------------------------- 1 | mutation addEndpoint { 2 | addEndpoint { 3 | data { 4 | id 5 | telemEnable 6 | moduleActive 7 | name 8 | endpointIPaddress 9 | telemetryPort 10 | videoPort 11 | videoEnable 12 | } 13 | } 14 | } 15 | 16 | mutation removeEndpoint($id: String!) { 17 | removeEndpoint(id: $id) { 18 | data { 19 | id 20 | telemEnable 21 | moduleActive 22 | name 23 | endpointIPaddress 24 | telemetryPort 25 | videoPort 26 | videoEnable 27 | } 28 | } 29 | } 30 | 31 | mutation updateEndpoint($endpoint: EndpointProperties!) { 32 | updateEndpoint(endpoint: $endpoint) { 33 | data { 34 | id 35 | telemEnable 36 | moduleActive 37 | name 38 | endpointIPaddress 39 | telemetryPort 40 | videoPort 41 | videoEnable 42 | } 43 | } 44 | } 45 | 46 | 47 | -------------------------------------------------------------------------------- /frontend/src/pages/modem/containers/hilink.footer.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from 'semantic-ui-react'; 2 | import { useChildProcessCmdMutation } from '../../../graphql/generated/dist'; 3 | 4 | const HiLinkFooter = () => { 5 | const [kernelCommand] = useChildProcessCmdMutation(); 6 | 7 | return ( 8 | 9 | {/* */} 10 | 19 | 28 | {/* */} 29 | 30 | ); 31 | }; 32 | 33 | export default HiLinkFooter; 34 | -------------------------------------------------------------------------------- /backend/src/utils/postUpdateActions.ts: -------------------------------------------------------------------------------- 1 | import pubsub, { getConStaus } from './pubsub'; 2 | import { getApplicationRepository } from '../entity/Application'; 3 | import winston from 'winston'; 4 | 5 | let interval: any; 6 | const ServerLog = winston.loggers.get('server'); 7 | 8 | export const postUpdateActions = async () => { 9 | const data = await getApplicationRepository().findOne(1); 10 | 11 | if (data && data.hasBeenUpdated) { 12 | interval = setInterval(async () => { 13 | if (!getConStaus()) return null; 14 | ServerLog.info({ message: 'Application has been updated successfully', path: __filename }); 15 | 16 | await pubsub.publish('U_SUPERVISOR_MESSAGE', { 17 | message: 'Application has been updated successfully.\n\nPlease refresh this window!\n\n\n\n' 18 | }); 19 | 20 | data.hasBeenUpdated = false; 21 | data.save(); 22 | 23 | return clearInterval(interval); 24 | }, 2000); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /bin/includes/app.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include "db.h" 6 | #include "utils.h" 7 | #include "app.h" 8 | #include 9 | 10 | int App::serverport() 11 | { 12 | Utils utils; 13 | Database db; 14 | db_app app_obj; 15 | db.get_application(&app_obj); 16 | 17 | if(app_obj.webPort < 2 || app_obj.webPort > 65000) { 18 | std::cout << ">> Invalid port number " << app_obj.webPort << 19 | " Database does probably not exsist! First startup?" << '\n'; 20 | return 1; 21 | } 22 | 23 | //Change the port number in systemd. 24 | std::string sed = 25 | std::string("sudo sed -ri 's/(SERVER_PORT)(=.*)/\\1=") + std::to_string(app_obj.webPort) + 26 | std::string("\"/' /etc/systemd/system/uavcast-web.service"); 27 | 28 | utils.exec_p(sed.c_str()); 29 | std::cout << ">> WebPort changed " << app_obj.webPort << '\n'; 30 | 31 | return 0; 32 | } -------------------------------------------------------------------------------- /frontend/src/components/led.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const GreenLed = (): any => ( 4 |
5 |
6 |
7 | ); 8 | export const YellowLed = () => ( 9 |
10 |
11 |
12 | ); 13 | export const YellowLedBlink = () => ( 14 |
15 |
16 |
17 | ); 18 | export const RedLed = (): any => ( 19 |
20 |
21 |
22 | ); 23 | export const RedLedBlink = () => ( 24 |
25 |
26 |
27 | ); 28 | export const BlueLed = () => ( 29 |
30 |
31 |
32 | ); 33 | -------------------------------------------------------------------------------- /frontend/src/pages/camera/containers/customPath.tsx: -------------------------------------------------------------------------------- 1 | import { useCameraDataQuery, useUpdateCameraMutation } from '../../../graphql/generated/dist'; 2 | import { Form, Header, Input } from 'semantic-ui-react'; 3 | 4 | const CustomPath = () => { 5 | const { data: { cameraData = {} } = {} }: any = useCameraDataQuery(); 6 | const [storeData] = useUpdateCameraMutation(); 7 | 8 | const { path } = cameraData?.database || {}; 9 | return ( 10 | 11 |
16 | storeData({ variables: { properties: { path: e.target.value.trim() } } })} 18 | defaultValue={path} 19 | fluid 20 | placeholder='Example:: /dev/video0' 21 | /> 22 | 23 | ); 24 | }; 25 | 26 | export default CustomPath; 27 | -------------------------------------------------------------------------------- /frontend/src/pages/endpoints/components/apModuleActive.tsx: -------------------------------------------------------------------------------- 1 | import { useUpdateEndpointMutation } from '../../../graphql/generated/dist'; 2 | 3 | interface props { 4 | status: any; 5 | telemEnable: boolean; 6 | moduleActive: boolean; 7 | id: string; 8 | } 9 | 10 | const ModuleActive = ({ id, moduleActive }: props) => { 11 | const [updateEndpoint] = useUpdateEndpointMutation(); 12 | 13 | return ( 14 | 25 | ); 26 | }; 27 | 28 | export default ModuleActive; 29 | -------------------------------------------------------------------------------- /frontend/src/scss/bootstrap/mixins/_hover.scss: -------------------------------------------------------------------------------- 1 | // Hover mixin and `$enable-hover-media-query` are deprecated. 2 | // 3 | // Originally added during our alphas and maintained during betas, this mixin was 4 | // designed to prevent `:hover` stickiness on iOS-an issue where hover styles 5 | // would persist after initial touch. 6 | // 7 | // For backward compatibility, we've kept these mixins and updated them to 8 | // always return their regular pseudo-classes instead of a shimmed media query. 9 | // 10 | // Issue: https://github.com/twbs/bootstrap/issues/25195 11 | 12 | @mixin hover() { 13 | &:hover { @content; } 14 | } 15 | 16 | @mixin hover-focus() { 17 | &:hover, 18 | &:focus { 19 | @content; 20 | } 21 | } 22 | 23 | @mixin plain-hover-focus() { 24 | &, 25 | &:hover, 26 | &:focus { 27 | @content; 28 | } 29 | } 30 | 31 | @mixin hover-focus-active() { 32 | &:hover, 33 | &:focus, 34 | &:active { 35 | @content; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/pages/logs/containers/logger-download.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Grid, Header, Icon } from 'semantic-ui-react'; 3 | import DownloadLogModal from './logger-download-modal'; 4 | 5 | const LoggerDownload = () => { 6 | const [modal, setModal] = useState(Boolean); 7 | 8 | return ( 9 | <> 10 | {modal && setModal(!modal)} />} 11 | 12 | 13 | {/* download logfiles text */} 14 |
15 | 16 | 17 | 20 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default LoggerDownload; 27 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/containers/casting_status.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from 'semantic-ui-react'; 2 | import { RedLed, GreenLed } from '../../../components/led'; 3 | 4 | const CastStatus = ({ status = {} }: any) => ( 5 | 6 | 7 | Casting Status 8 | 9 | Telemetry 10 | {status?.mavproxy ? : } 11 | 12 | 13 | Video 14 | {status?.video ? : } 15 | 16 | 17 | VPN 18 | {status?.vpn ? : } 19 | 20 | 21 | Autostart 22 | {status?.uavcast_systemd_enabled ? : } 23 | 24 | 25 | 26 | ); 27 | export default CastStatus; 28 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "apollographql.vscode-apollo", 5 | "dbaeumer.vscode-eslint", 6 | "eamodio.gitlens", 7 | "xabikos.reactsnippets", 8 | "jawandarajbir.react-vscode-extension-pack", 9 | "graphql.vscode-graphql", 10 | "christian-kohler.npm-intellisense", 11 | "christian-kohler.path-intellisense", 12 | "gruntfuggly.todo-tree", 13 | "formulahendry.auto-rename-tag", 14 | "aaron-bond.better-comments", 15 | "ms-vscode.cpptools", 16 | "austin.code-gnu-global", 17 | "ms-azuretools.vscode-docker", 18 | "dsznajder.es7-react-js-snippets", 19 | "xabikos.javascriptsnippets", 20 | "alduncanson.react-hooks-snippets", 21 | "mtxr.sqltools", 22 | "mtxr.sqltools-driver-sqlite", 23 | "wayou.vscode-todo-highlight", 24 | "rbbit.typescript-hero", 25 | "pkief.material-icon-theme", 26 | "formulahendry.auto-close-tag", 27 | "mikestead.dotenv" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /backend/src/mavlink/v2.0/definitions/frame.ts: -------------------------------------------------------------------------------- 1 | export const frame: any = { 2 | 0: 'Generic micro air vehicle', 3 | 1: 'Fixed wing', 4 | 2: 'Quadrotor', 5 | 3: 'Coaxial helicopter', 6 | 4: 'Helicopter With Tail R', 7 | 5: 'Ground installation', 8 | 6: 'GCS', 9 | 7: 'Airship, controlled', 10 | 8: 'Free balloon', 11 | 9: 'Rocket', 12 | 10: 'Ground rover', 13 | 11: 'Surface vessel', 14 | 12: 'Submarine', 15 | 13: 'Hexarotor', 16 | 14: 'Octorotor', 17 | 15: 'Tricopter', 18 | 16: 'Flapping wing', 19 | 17: 'Kite', 20 | 18: 'Companion', 21 | 19: 'Two-rotor VTOL', 22 | 20: 'Quad-rotor VTOL', 23 | 21: 'Tiltrotor VTOL', 24 | 22: 'VTOL reserved 2', 25 | 23: 'VTOL reserved 3', 26 | 24: 'VTOL reserved 4', 27 | 25: 'VTOL reserved 5', 28 | 26: 'Gimbal', 29 | 27: 'ADSB system', 30 | 28: 'Steerable, nonrigid airfoil', 31 | 29: 'Dodecarotor', 32 | 30: 'Camera', 33 | 31: 'Charging station', 34 | 32: 'FLARM collision avoidance system', 35 | 33: 'Servo', 36 | }; 37 | -------------------------------------------------------------------------------- /frontend/src/pages/endpoints/components/epVideoPort.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Header, Input } from 'semantic-ui-react'; 2 | import { useUpdateEndpointMutation } from '../../../graphql/generated/dist'; 3 | 4 | interface props { 5 | videoPort: number; 6 | id: string; 7 | Eid: number; 8 | } 9 | 10 | const EndpointVideoPort = ({ videoPort, id, Eid }: props) => { 11 | const [updateEndpoint, { loading }] = useUpdateEndpointMutation(); 12 | 13 | return ( 14 | 15 |
16 | updateEndpoint({ variables: { endpoint: { videoPort: parseInt(e.target.value, 10), id } } })} 19 | defaultValue={videoPort} 20 | tabIndex={Eid * 10 + 3} 21 | fluid 22 | size='mini' 23 | placeholder='Port...' 24 | /> 25 | 26 | ); 27 | }; 28 | 29 | export default EndpointVideoPort; 30 | -------------------------------------------------------------------------------- /frontend/src/pages/map/container/settings.tab.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from 'semantic-ui-react'; 2 | 3 | const SettingsTab = () => { 4 | // const { socket, dispatch, db_uavnav, db_camera } = props; 5 | const handleChange = (_e: any) => { 6 | // let cfg = { ...db_uavnav, [e.target.name]: e.target.checked }; 7 | // dispatch({ type: SAVE_DRONECONFIG, db_collections: mongodb_collections, config: cfg, socket: socket }); 8 | }; 9 | 10 | return ( 11 | 12 | 13 |
14 | 15 | 16 | 19 |
20 |
21 |
22 | ); 23 | }; 24 | 25 | export default SettingsTab; 26 | -------------------------------------------------------------------------------- /frontend/src/pages/endpoints/components/epName.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Header, Input } from 'semantic-ui-react'; 2 | import { useUpdateEndpointMutation } from '../../../graphql/generated/dist'; 3 | 4 | interface props { 5 | name: string; 6 | id: string; 7 | Eid: number; 8 | } 9 | 10 | const EndpointName = ({ name, id, Eid }: props) => { 11 | const [updateEndpoint, { loading }] = useUpdateEndpointMutation(); 12 | 13 | return ( 14 | 15 |
16 | updateEndpoint({ variables: { endpoint: { name: e.target.value, id } } })} 24 | /> 25 | {/*
*/} 26 | 27 | ); 28 | }; 29 | 30 | export default EndpointName; 31 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "allowJs": true, 5 | "module": "commonjs", 6 | "lib": ["dom", "es6", "es2017", "esnext.asynciterable"], 7 | "sourceMap": true, 8 | "outDir": "./dist", 9 | "moduleResolution": "node", 10 | "removeComments": true, 11 | "noImplicitAny": true, 12 | "strictNullChecks": true, 13 | "strictFunctionTypes": true, 14 | "noImplicitThis": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "allowSyntheticDefaultImports": true, 20 | "esModuleInterop": true, 21 | "emitDecoratorMetadata": true, 22 | "experimentalDecorators": true, 23 | "resolveJsonModule": true, 24 | "baseUrl": "." 25 | // "paths": { 26 | // "@components/*": ["src/*"] 27 | // // "@seeds/*": ["src/seeds/*"] 28 | // } 29 | }, 30 | "exclude": ["node_modules"], 31 | "include": ["src/**/*.ts", "src/**/*.js"] 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/pages/backup/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card, Container, Grid, Header } from 'semantic-ui-react'; 3 | import BackupDatabase from './containers/backupDatabase'; 4 | 5 | const Backup = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | {/* Prune Files */} 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | export default Backup; 30 | -------------------------------------------------------------------------------- /frontend/src/components/slider.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import RCslider from 'rc-slider'; 3 | import 'rc-slider/assets/index.css'; 4 | 5 | const { Handle } = RCslider; 6 | 7 | const SliderHandle = (props: any) => { 8 | const { value, dragging, index, ...restProps } = props; 9 | return ( 10 | 11 |
12 |
13 | {value} 14 |
15 |
16 |
17 | ); 18 | }; 19 | 20 | const Slider = (props: any) => { 21 | const [value, setValue] = useState(); 22 | 23 | useEffect(() => { 24 | setValue(props.defaultValue); 25 | }, [props.defaultValue]); 26 | 27 | const handleSlide = (sliderValue: number) => { 28 | setValue(sliderValue); 29 | }; 30 | 31 | return ; 32 | }; 33 | 34 | export default Slider; 35 | -------------------------------------------------------------------------------- /frontend/src/pages/endpoints/components/epTelemetryPort.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Header, Input } from 'semantic-ui-react'; 2 | import { useUpdateEndpointMutation } from '../../../graphql/generated/dist'; 3 | 4 | interface props { 5 | telemetryPort: number; 6 | id: string; 7 | Eid: number; 8 | } 9 | 10 | const EndpointTelemetryPort = ({ telemetryPort, id, Eid }: props) => { 11 | const [updateEndpoint, { loading }] = useUpdateEndpointMutation(); 12 | 13 | return ( 14 | 15 |
16 | updateEndpoint({ variables: { endpoint: { telemetryPort: parseInt(e.target.value, 10), id } } })} 19 | defaultValue={telemetryPort} 20 | tabIndex={Eid * 10 + 3} 21 | fluid 22 | size='mini' 23 | placeholder='Port...' 24 | /> 25 | 26 | ); 27 | }; 28 | 29 | export default EndpointTelemetryPort; 30 | -------------------------------------------------------------------------------- /frontend/src/pages/logs/components/modal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Header, Icon, Modal } from 'semantic-ui-react'; 3 | 4 | interface IProps { 5 | loading?: boolean; 6 | close: (arg: boolean) => void; 7 | acknowledge: () => void; 8 | title: string; 9 | content?: string; 10 | } 11 | 12 | const LoggerModal = ({ close, acknowledge, title, content, loading }: IProps) => { 13 | return ( 14 | close(false)} open={true}> 15 |
16 | {content} 17 | 18 | 21 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | export default LoggerModal; 30 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: 'npm' # See documentation for possible values 9 | directory: '/frontend' # Location of package manifests 10 | schedule: 11 | interval: 'monthly' 12 | open-pull-requests-limit: 3 13 | 14 | - package-ecosystem: 'npm' # See documentation for possible values 15 | directory: '/backend' # Location of package manifests 16 | schedule: 17 | interval: 'monthly' 18 | open-pull-requests-limit: 3 19 | 20 | - package-ecosystem: 'github-actions' 21 | # Workflow files stored in the 22 | # default location of `.github/workflows` 23 | directory: '/' 24 | schedule: 25 | interval: 'monthly' 26 | open-pull-requests-limit: 3 27 | -------------------------------------------------------------------------------- /frontend/src/translations/locales/Readme.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | * Fork this project. 4 | * Download a proper text editor on your computer, example [Microsoft studio Code](https://code.visualstudio.com/) 5 | * Download **en.json** to your computer, and make a copy of that file with the language abbreviation you want to use. example "fr.json for france". 6 | * Make the changes you want use. Only change the text on right hand side of : 7 | 8 | #### Example en.json 9 | ```javascript 10 | "headerLinks":{ 11 | "community":"Community", 12 | "documentation":"Documentation" 13 | } 14 | ``` 15 | For Norwegian language, this file would have been edited like 16 | ```javascript 17 | "headerLinks":{ 18 | "community":"Samfunnet", 19 | "documentation":"Dokumentasjon" 20 | } 21 | ``` 22 | 23 | * when you have completed the file, upload it to your forked version on github. 24 | * then go to [https://github.com/UAVmatrix/uavcast-pro-translations](https://github.com/UAVmatrix/uavcast-pro-translations) and press merge request. 25 | 26 | * Or you can send the file to uavmatrix@uavmatrix.com 27 | -------------------------------------------------------------------------------- /backend/src/graphql-response-types/VpnResponse.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from 'type-graphql'; 2 | import { FieldError } from './FieldError'; 3 | import { Vpn } from '../entity/Vpn'; 4 | 5 | @ObjectType() 6 | export class VpnResponse { 7 | @Field(() => Vpn, { nullable: true }) 8 | data: Vpn; 9 | 10 | @Field(() => [FieldError], { nullable: true }) 11 | errors?: FieldError[]; 12 | } 13 | 14 | @ObjectType() 15 | export class ZerotierNetworkProperties { 16 | @Field(() => [String]) 17 | assignedAddresses: string[]; 18 | 19 | @Field(() => String) 20 | name: string; 21 | 22 | @Field(() => String) 23 | nwid: string; 24 | 25 | @Field(() => String) 26 | portDeviceName: string; 27 | 28 | @Field(() => String) 29 | status: string; 30 | 31 | @Field(() => String) 32 | type: string; 33 | } 34 | 35 | @ObjectType() 36 | export class ZerotierNetworkResponse { 37 | @Field(() => [ZerotierNetworkProperties], { nullable: true }) 38 | networks: ZerotierNetworkProperties[]; 39 | 40 | @Field(() => [FieldError], { nullable: true }) 41 | errors?: FieldError[]; 42 | } 43 | -------------------------------------------------------------------------------- /backend/src/entity/Vpn.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Check, PrimaryColumn, Column, getRepository } from 'typeorm'; 2 | import { Field, ID, ObjectType } from 'type-graphql'; 3 | 4 | @ObjectType({ isAbstract: true }) 5 | @Entity() 6 | @Check(`ensure = 1`) 7 | export class Vpn { 8 | @PrimaryColumn({ type: 'int', default: () => `1`, nullable: false }) 9 | public ensure: 1; 10 | 11 | @Field(() => ID) 12 | @Column({ type: Number, default: 1 }) 13 | id: number; 14 | 15 | @Field() 16 | @Column({ type: Boolean, default: false, nullable: false }) 17 | enableVpn: boolean; 18 | 19 | @Field() 20 | @Column({ type: String, default: '', nullable: false }) 21 | networkId: string; 22 | 23 | @Field() 24 | @Column({ type: String, default: 'zerotier', nullable: false }) 25 | serviceProvider: string; 26 | 27 | @Field() 28 | @Column({ type: String, default: '', nullable: false }) 29 | username: string; 30 | 31 | @Field() 32 | @Column({ type: String, default: '', nullable: false }) 33 | password: string; 34 | } 35 | 36 | export const getVpnRepository = () => { 37 | return getRepository(Vpn); 38 | }; 39 | -------------------------------------------------------------------------------- /frontend/src/pages/settings/components/rebootHostModal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Header, Icon, Modal } from 'semantic-ui-react'; 3 | import { useSupervisorCommandsMutation } from '../../../graphql/generated/dist'; 4 | 5 | const RebootHost = ({ open, close }: any) => { 6 | const [supervisorCommand] = useSupervisorCommandsMutation(); 7 | 8 | const reboot = () => { 9 | supervisorCommand({ variables: { type: 'RESTART_HOST' } }).then(() => close()); 10 | }; 11 | return ( 12 | close()} open={open} size='small'> 13 |
14 | 15 | Reboot host machine 16 |
17 | 18 | 19 | 22 | 25 | 26 |
27 | ); 28 | }; 29 | 30 | export default RebootHost; 31 | -------------------------------------------------------------------------------- /frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | // import i18n (needs to be bundled ;)) 3 | import App from './app/app'; 4 | // import reportWebVitals from './reportWebVitals'; 5 | import { BrowserRouter } from 'react-router-dom'; 6 | import { ApolloProvider } from '@apollo/client'; 7 | import { client } from './utils/apolloClient'; 8 | import ReactGA from 'react-ga'; 9 | 10 | import './translations/i18next'; 11 | 12 | // Import Main styles for this application 13 | import './scss/style.scss'; 14 | 15 | if (process.env.NODE_ENV === 'production') { 16 | ReactGA.initialize('UA-107582726-2', { 17 | debug: false, 18 | titleCase: true 19 | }); 20 | } 21 | 22 | ReactDOM.render( 23 | 24 | 25 | 26 | 27 | , 28 | document.getElementById('root') 29 | ); 30 | 31 | // If you want to start measuring performance in your app, pass a function 32 | // to log results (for example: reportWebVitals(console.log)) 33 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 34 | // reportWebVitals(); 35 | -------------------------------------------------------------------------------- /frontend/src/pages/camera/containers/contrast.tsx: -------------------------------------------------------------------------------- 1 | import { Grid, Header } from 'semantic-ui-react'; 2 | import Slider from '../../../components/slider'; 3 | import { useCameraDataQuery, useUpdateCameraMutation } from '../../../graphql/generated/dist'; 4 | 5 | const CameraContrast = () => { 6 | const { data: { cameraData = {} } = {}, loading: camLoading }: any = useCameraDataQuery(); 7 | const [storeData] = useUpdateCameraMutation(); 8 | 9 | const storeValue = (value: any) => { 10 | storeData({ variables: { properties: { contrast: value } } }); 11 | }; 12 | const { contrast } = cameraData?.database || {}; 13 | 14 | if (camLoading) return
; 15 | return ( 16 | 17 | 18 |
19 | 20 | 21 |
22 | 23 |
24 |
25 | 26 | ); 27 | }; 28 | 29 | export default CameraContrast; 30 | -------------------------------------------------------------------------------- /frontend/src/pages/camera/containers/framesPrSecond.tsx: -------------------------------------------------------------------------------- 1 | import { Grid, Header } from 'semantic-ui-react'; 2 | import Slider from '../../../components/slider'; 3 | import { useCameraDataQuery, useUpdateCameraMutation } from '../../../graphql/generated/dist'; 4 | 5 | const FramesPrSecond = () => { 6 | const { data: { cameraData = {} } = {}, loading: camLoading }: any = useCameraDataQuery(); 7 | const [storeData] = useUpdateCameraMutation(); 8 | 9 | const storeValue = (value: any) => { 10 | storeData({ variables: { properties: { framesPrSecond: value } } }); 11 | }; 12 | const { framesPrSecond } = cameraData?.database || {}; 13 | 14 | if (camLoading) return
; 15 | return ( 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | export default FramesPrSecond; 28 | -------------------------------------------------------------------------------- /frontend/src/pages/camera/containers/rotation.tsx: -------------------------------------------------------------------------------- 1 | import { Grid, Header } from 'semantic-ui-react'; 2 | import Slider from '../../../components/slider'; 3 | import { useCameraDataQuery, useUpdateCameraMutation } from '../../../graphql/generated/dist'; 4 | 5 | const CameraRotation = () => { 6 | const { data: { cameraData = {} } = {}, loading: camLoading }: any = useCameraDataQuery(); 7 | const [storeData] = useUpdateCameraMutation(); 8 | 9 | const storeValue = (value: any) => { 10 | storeData({ variables: { properties: { rotation: value } } }); 11 | }; 12 | const { rotation } = cameraData?.database || {}; 13 | 14 | if (camLoading) return
; 15 | return ( 16 | 17 | 18 |
19 | 20 | 21 |
22 | 23 |
24 |
25 | 26 | ); 27 | }; 28 | 29 | export default CameraRotation; 30 | -------------------------------------------------------------------------------- /backend/src/migration/intitTableValues.js: -------------------------------------------------------------------------------- 1 | const { MigrationInterface, QueryRunner, QueryBuilder, Table } = require('typeorm'); 2 | 3 | class PostRefactoring1613820162728 { 4 | async up(queryRunner) { 5 | await queryRunner.createTable( 6 | new Table({ 7 | name: 'map', 8 | columns: [ 9 | { 10 | name: 'mavCockpitDisable', 11 | type: 'Boolean', 12 | default: 0, 13 | nullable: false 14 | } 15 | ] 16 | }) 17 | ); 18 | 19 | await queryRunner.manager 20 | .createQueryBuilder() 21 | .insert() 22 | .into('map') 23 | .values([{ mavCockpitDisable: 0 }]) 24 | .execute(); 25 | } 26 | 27 | async down(queryRunner) { 28 | await queryRunner.query(`DROP TABLE "maps"`); 29 | } 30 | } 31 | module.exports = PostRefactoring1613820162728; 32 | // Run migration 33 | // typeorm migration:run -c sqlite 34 | 35 | // Revert migration 36 | // typeorm migration:revert -c sqlite 37 | 38 | // Generate Migration file 39 | // typeorm migration:generate -n PostRefactoring 40 | 41 | // npx ts-node node_modules/..../typeorm !!!! 42 | -------------------------------------------------------------------------------- /backend/src/graphql-input-types/Endpoint.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field, ArgsType } from 'type-graphql'; 2 | 3 | @InputType() 4 | export class EndpointProperties { 5 | @Field({ nullable: true }) 6 | id: string; 7 | 8 | @Field({ nullable: true }) 9 | telemEnable?: boolean; 10 | 11 | @Field({ nullable: true }) 12 | moduleActive?: boolean; 13 | 14 | @Field({ nullable: true }) 15 | name?: string; 16 | 17 | @Field({ nullable: true }) 18 | endpointIPaddress?: string; 19 | 20 | @Field({ nullable: true }) 21 | telemetryPort?: number; 22 | 23 | @Field({ nullable: true }) 24 | videoPort?: number; 25 | 26 | @Field({ nullable: true }) 27 | videoEnable?: boolean; 28 | } 29 | 30 | @InputType() 31 | @ArgsType() 32 | export class EndpointInput { 33 | @Field({ nullable: true }) 34 | endpoint: EndpointProperties; 35 | } 36 | 37 | @InputType() 38 | @ArgsType() 39 | export class RemoveEndpointInput { 40 | @Field({ nullable: true }) 41 | id: string; 42 | } 43 | 44 | @InputType() 45 | @ArgsType() 46 | export class UpdateEndpointInput { 47 | @Field({ nullable: true }) 48 | endpoint: EndpointProperties; 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/pages/camera/containers/brightness.tsx: -------------------------------------------------------------------------------- 1 | import { Grid, Header } from 'semantic-ui-react'; 2 | import Slider from '../../../components/slider'; 3 | import { useCameraDataQuery, useUpdateCameraMutation } from '../../../graphql/generated/dist'; 4 | 5 | const CameraBrightness = () => { 6 | const { data: { cameraData = {} } = {}, loading: camLoading }: any = useCameraDataQuery(); 7 | const [storeData] = useUpdateCameraMutation(); 8 | 9 | const storeValue = (value: any) => { 10 | storeData({ variables: { properties: { brightness: value } } }); 11 | }; 12 | const { brightness } = cameraData?.database || {}; 13 | 14 | if (camLoading) return
; 15 | return ( 16 | 17 | 18 |
19 | 20 | 21 |
22 | 23 |
24 |
25 | 26 | ); 27 | }; 28 | 29 | export default CameraBrightness; 30 | -------------------------------------------------------------------------------- /docker/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM sinamics/uavcast-base:latest 2 | LABEL maintainer="Bernt Christian Egeland / (aka sinamics)" 3 | 4 | WORKDIR /app/uavcast 5 | 6 | ENV NPM_CONFIG_PREFIX=/home/uavcast/.npm-global 7 | ENV PATH=$PATH:/home/uavcast/.npm-global/bin 8 | 9 | # systemctl3 10 | COPY init/systemctl3.py /bin/systemctl 11 | COPY init/systemctl3.py /bin/systemctl3.py 12 | COPY init/journalctl3.py /bin/journalctl 13 | 14 | # Copy systemd files 15 | COPY ["init/mavlink-router.service", \ 16 | "init/uavcast-camera.service", \ 17 | "init/uavcast-vpn.service", \ 18 | "init/uavcast-web.service", \ 19 | "init/uavcast.service", "/etc/systemd/system/"] 20 | 21 | RUN mkdir -p /app/uavcast/data && chown -R uavcast /app/uavcast 22 | 23 | # Create mavlink directory 24 | RUN mkdir -p /etc/mavlink-router 25 | COPY ["etc/mavlink-router-example.conf", "/etc/mavlink-router/main.conf"] 26 | 27 | RUN chmod +x /bin/systemctl \ 28 | /bin/systemctl3.py \ 29 | /bin/journalctl 30 | 31 | RUN sudo touch /var/run/docker.sock 32 | RUN sudo chmod 666 /var/run/docker.sock 33 | RUN sudo chown uavcast:docker /var/run/docker.sock 34 | 35 | ENV NODE_ENV=development -------------------------------------------------------------------------------- /frontend/src/scss/core/_slider.scss: -------------------------------------------------------------------------------- 1 | .wdc-tooltip { 2 | position: absolute; 3 | top: 0; 4 | left: 50%; 5 | transform: translate(-50%, -100%); 6 | z-index: 9999; 7 | font-size: 10px; 8 | line-height: 1.5; 9 | opacity: 0.9; 10 | user-select: none; 11 | visibility: visible; 12 | opacity: 1; 13 | transition: all ease-in-out 150ms; 14 | &.active { 15 | // visibility: visible; always visible 16 | // opacity: 1; 17 | top: -10px; 18 | } 19 | span { 20 | display: block; 21 | text-align: center; 22 | color: #fff; 23 | text-decoration: none; 24 | background-color: #373737; 25 | border-radius: 5px; 26 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.17); 27 | position: relative; 28 | font-weight: bold; 29 | user-select: none; 30 | padding: 5px 10px; 31 | &:after { 32 | content: ''; 33 | position: absolute; 34 | width: 0; 35 | height: 0; 36 | border-color: transparent; 37 | border-style: solid; 38 | border-width: 5px 5px 0; 39 | border-top-color: #373737; 40 | left: 50%; 41 | bottom: 0; 42 | transform: translate(-50%, 100%); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /frontend/src/pages/endpoints/components/epIPaddress.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Header, Input } from 'semantic-ui-react'; 2 | import { useUpdateEndpointMutation } from '../../../graphql/generated/dist'; 3 | 4 | interface props { 5 | endpointIPaddress: string; 6 | id: string; 7 | Eid: number; 8 | } 9 | 10 | const EndpointIPaddress = ({ endpointIPaddress, id, Eid }: props) => { 11 | const [updateEndpoint, { loading }] = useUpdateEndpointMutation(); 12 | 13 | return ( 14 | 15 |
21 | updateEndpoint({ variables: { endpoint: { endpointIPaddress: e.target.value, id } } })} 26 | fluid 27 | size='mini' 28 | placeholder='IP...' 29 | /> 30 | {/*
*/} 31 | 32 | ); 33 | }; 34 | 35 | export default EndpointIPaddress; 36 | -------------------------------------------------------------------------------- /frontend/src/pages/logs/index.tsx: -------------------------------------------------------------------------------- 1 | import { Card, Container, Divider, Grid, Header } from 'semantic-ui-react'; 2 | import LoggerDownload from './containers/logger-download'; 3 | import PruneLogs from './containers/logger-delete'; 4 | import DebugMode from './containers/logger-debug'; 5 | 6 | const Logs = () => { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | Download 20 | 21 | Prune Files 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | export default Logs; 32 | -------------------------------------------------------------------------------- /frontend/src/pages/backup/containers/restore-database-modal.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import axios from 'axios'; 4 | import React from 'react'; 5 | import { Button, Header, Icon, Modal } from 'semantic-ui-react'; 6 | import DatabaseDropzone from './dropzone'; 7 | 8 | function RestoreDatabaseModal({ close }: any) { 9 | return ( 10 | close(false)} open={true}> 11 |
12 | 13 |

Select backup file to restore from

14 |
15 | 16 | 17 | 18 | 19 | 22 | {/* */} 25 | 26 | 27 | ); 28 | } 29 | 30 | export default RestoreDatabaseModal; 31 | -------------------------------------------------------------------------------- /frontend/src/pages/vpn/containers/zerotier.footer.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from 'semantic-ui-react'; 2 | import { useChildProcessCmdMutation } from '../../../graphql/generated/dist'; 3 | 4 | const ZerotierFooter = () => { 5 | const [kernelCommand] = useChildProcessCmdMutation(); 6 | 7 | return ( 8 | 9 | 18 | 27 | {/* */} 28 | 37 | 38 | ); 39 | }; 40 | 41 | export default ZerotierFooter; 42 | -------------------------------------------------------------------------------- /backend/ormconfig.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const root = require('app-root-path'); 3 | 4 | // data merged in /backend/src/createTypeormCon.ts 5 | module.exports = [ 6 | { 7 | name: 'default', 8 | type: 'sqlite', 9 | synchronize: true, 10 | logging: false, 11 | entities: ['src/entity/**/*{.ts,.js}'], 12 | migrations: ['src/migration/**/*{.ts,.js}'], 13 | subscribers: ['src/subscriber/**/*{.ts,.js}'], 14 | seeds: ['src/seeds/**/*{.ts,.js}'], 15 | factories: ['src/factories/**/*{.ts,.js}'], 16 | cli: { 17 | entitiesDir: 'src/entity', 18 | migrationsDir: 'src/migration', 19 | subscribersDir: 'src/subscriber' 20 | } 21 | }, 22 | { 23 | name: 'production', 24 | type: 'sqlite', 25 | synchronize: false, // switch this to false once you have the initial tables created and use migrations instead 26 | logging: false, 27 | seeds: ['src/seeds/**/*{.ts,.js}'], 28 | factories: [`${__dirname}/dist/factories/**/*{.ts,.js}`], 29 | entities: [`${__dirname}/dist/entity/**/*.js`], 30 | migrations: [path.join(__dirname, 'dist/migration/**/*.js')], 31 | subscribers: [path.join(__dirname, 'dist/subscriber/**/*.js')] 32 | } 33 | ]; 34 | -------------------------------------------------------------------------------- /frontend/src/pages/camera/containers/bitratePrSecond.tsx: -------------------------------------------------------------------------------- 1 | import { Grid, Header } from 'semantic-ui-react'; 2 | import Slider from '../../../components/slider'; 3 | import { useCameraDataQuery, useUpdateCameraMutation } from '../../../graphql/generated/dist'; 4 | 5 | const BitratePrSecond = () => { 6 | const { data: { cameraData = {} } = {}, loading: camLoading }: any = useCameraDataQuery(); 7 | const [storeData] = useUpdateCameraMutation(); 8 | 9 | const storeValue = (value: any) => { 10 | storeData({ variables: { properties: { bitratePrSecond: value } } }); 11 | }; 12 | const { bitratePrSecond } = cameraData?.database || {}; 13 | 14 | if (camLoading) return
; 15 | 16 | return ( 17 | 18 | 19 |
20 | 21 | 22 |
23 | 24 |
25 |
26 | 27 | ); 28 | }; 29 | 30 | export default BitratePrSecond; 31 | -------------------------------------------------------------------------------- /docker/Dockerfile.mavlink: -------------------------------------------------------------------------------- 1 | ##### 2 | # 3 | # Generate mavlink binaries 4 | # !needs binfmt: 5 | # docker run --privileged --rm tonistiigi/binfmt --install all 6 | # 7 | # run as: 8 | # docker buildx build --pull --rm -f Dockerfile.mavlink --platform linux/arm,linux/arm64,linux/amd64 -t mavlink --output out . 9 | ###### 10 | 11 | FROM gcc:9.2 as gcc 12 | ARG TARGETPLATFORM 13 | ARG BUILDPLATFORM 14 | ARG TARGETARCH 15 | 16 | WORKDIR /gcc 17 | 18 | RUN git clone https://github.com/mavlink-router/mavlink-router.git 19 | RUN cd mavlink-router && git submodule update --init --recursive 20 | RUN apt-get update && apt-get install -y python3-pip 21 | RUN pip3 install meson 22 | RUN cd mavlink-router && apt-get install -y git meson ninja-build pkg-config gcc g++ systemd 23 | 24 | RUN cd mavlink-router && meson setup -D systemdsystemunitdir=/usr/lib/systemd/system build . 25 | RUN cd mavlink-router && ninja -C build 26 | RUN cd mavlink-router && ninja -C build install 27 | 28 | RUN mkdir -p mavlink 29 | RUN mv /usr/bin/mavlink-routerd ./mavlink/mavlink-routerd-${TARGETARCH} 30 | 31 | FROM scratch AS export-stage 32 | ARG TARGETARCH 33 | COPY --from=gcc /gcc/mavlink/mavlink-routerd-${TARGETARCH} . 34 | 35 | ENTRYPOINT ["tail", "-f", "/dev/null"] -------------------------------------------------------------------------------- /frontend/src/components/Footer/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | // import { AppConfig } from '../../../config.js' 3 | 4 | class Footer extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | year: new Date().getFullYear() 9 | }; 10 | } 11 | render() { 12 | return ( 13 |
14 | // 33 | ); 34 | } 35 | } 36 | 37 | export default Footer; 38 | -------------------------------------------------------------------------------- /frontend/src/graphql/subscription/mavlink.graphql: -------------------------------------------------------------------------------- 1 | subscription mavlink { 2 | mavlink { 3 | message { 4 | heartbeat { 5 | armed 6 | connected 7 | type 8 | autopilot 9 | base_mode 10 | custom_mode 11 | system_status 12 | mavlink_version 13 | firmware 14 | frame 15 | numOfGcs { 16 | type 17 | } 18 | } 19 | vfr_hud { 20 | airspeed 21 | groundspeed 22 | heading 23 | throttle 24 | alt 25 | climb 26 | } 27 | power_status { 28 | Vcc 29 | Vservo 30 | flags 31 | } 32 | failsafe { 33 | gcs { 34 | param_value 35 | } 36 | short { 37 | param_value 38 | } 39 | long { 40 | param_value 41 | } 42 | } 43 | gps_raw_int { 44 | fix_type 45 | lat 46 | lon 47 | alt 48 | vel 49 | cog 50 | satellites_visible 51 | } 52 | } 53 | } 54 | } 55 | subscription cmdAck { 56 | cmdAck { 57 | command 58 | result 59 | message 60 | errors { 61 | path 62 | message 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /backend/src/entity/Endpoint.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, getRepository } from 'typeorm'; 2 | import { Field, ID, ObjectType } from 'type-graphql'; 3 | 4 | @ObjectType({ isAbstract: true }) 5 | @Entity() 6 | export class Endpoint { 7 | @Field(() => ID) 8 | @PrimaryGeneratedColumn('uuid') 9 | id: string; 10 | 11 | @Field() 12 | @Column({ type: 'boolean', default: false, nullable: false }) 13 | telemEnable: boolean; 14 | 15 | @Field() 16 | @Column({ type: 'boolean', default: true, nullable: false }) 17 | moduleActive: boolean; 18 | 19 | @Field() 20 | @Column({ type: String, default: '', nullable: false }) 21 | name: string; 22 | 23 | @Field() 24 | @Column({ type: String, default: '192.168.1.100', nullable: false }) 25 | endpointIPaddress: string; 26 | 27 | @Field() 28 | @Column({ type: Number, default: 14550, nullable: false }) 29 | telemetryPort: number; 30 | 31 | @Field() 32 | @Column({ type: Number, default: 5600, nullable: false }) 33 | videoPort: number; 34 | 35 | @Field() 36 | @Column({ type: 'boolean', default: false, nullable: false }) 37 | videoEnable: boolean; 38 | } 39 | 40 | export const getEndpointRepository = () => { 41 | return getRepository(Endpoint); 42 | }; 43 | -------------------------------------------------------------------------------- /docker/bin/install-dev-addon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ---------------------------------------------------------------- 3 | # uavcast dev-installation file. 4 | # Author Bernt Christian Egeland 5 | # 6 | # !Dev specific addons 7 | # ---------------------------------------------------------------- 8 | 9 | APPROOT="/app/uavcast" 10 | 11 | # install global dependencies 12 | # npm i concurrently ts-node-dev typescript -g 13 | 14 | sudo ln -s /usr/include/libv4l1-videodev.h /usr/include/linux/videodev.h 15 | sudo setcap cap_net_bind_service=+ep `readlink -f \`which node\`` 16 | 17 | # fetch translations 18 | # https://github.com/UAVmatrix/uavcast-pro-translations 19 | git submodule update --init --recursive 20 | 21 | ARCH=`uname -m` 22 | if [ "$ARCH" == "x86_64" ] || [ "$ARCH" = "amd64" ]; then 23 | cp ${APPROOT}/bin/mavlink/mavlink-routerd-amd64 ${APPROOT}/bin/mavlink/mavlink-routerd 24 | elif [ "$ARCH" == "armv7l" ]; then 25 | cp ${APPROOT}/bin/mavlink/mavlink-routerd-arm ${APPROOT}/bin/mavlink/mavlink-routerd 26 | elif [ "$ARCH" == "aarch64" ]; then 27 | cp ${APPROOT}/bin/mavlink/mavlink-routerd-arm64 ${APPROOT}/bin/mavlink/mavlink-routerd 28 | fi 29 | 30 | sudo apt-get install -y usbutils 31 | 32 | # Build binaries 33 | cd /app/uavcast/bin && make -------------------------------------------------------------------------------- /license.audit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run this script from the root of the repo you want to inspect. It's assumed 3 | * that there's a `./node_modules` and `./package.json` where this script runs. 4 | */ 5 | 6 | const module = `backend`; // or frontend 7 | 8 | const packageJSON = require(`./${module}/package.json`); 9 | 10 | // Get all the top-level dependencies for the current project. 11 | const dependencies = { 12 | ...packageJSON.dependencies, 13 | ...packageJSON.devDependencies 14 | }; 15 | 16 | // Get the names of every dependency. 17 | const dependencyNames = Object.keys(dependencies); 18 | 19 | // Create a key => value of depenencyName => { license, whateverElse }; 20 | const licenses = dependencyNames.reduce((memo, dependencyName) => { 21 | const dependencyDirectory = `./${module}/${dependencyName}`; 22 | const dependencyPackageJSON = require(`${dependencyDirectory}/package.json`); 23 | const license = dependencyPackageJSON.license; 24 | 25 | return { 26 | ...memo, 27 | [dependencyName]: { 28 | dependencyName, 29 | license 30 | } 31 | }; 32 | }, {}); 33 | 34 | // Print out all the dependencies and their license info. 35 | dependencyNames.sort().forEach((dependencyName) => { 36 | console.log(`"${dependencyName}","${licenses[dependencyName].license}"`); 37 | }); 38 | -------------------------------------------------------------------------------- /frontend/src/pages/pageNotFound/style.css: -------------------------------------------------------------------------------- 1 | /* @import url("https://fonts.googleapis.com/css?family=Poppins&display=swap"); 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | font-family: "Poppins", sans-serif; 7 | } */ 8 | 9 | #not-found-container { 10 | position: absolute; 11 | width: 100%; 12 | height: 100%; 13 | display: flex; 14 | justify-content: center; 15 | align-items: center; 16 | background: url(p404.png) #151729; 17 | } 18 | .not-found-content { 19 | max-width: 600px; 20 | text-align: center; 21 | } 22 | .not-found-content h2 { 23 | font-size: 16vw !important; 24 | color: #fff; 25 | line-height: 1em; 26 | } 27 | .not-found-content h4 { 28 | position: relative; 29 | font-size: 1.5em; 30 | margin-bottom: 20px; 31 | color: #111; 32 | background: #fff; 33 | font-weight: 300; 34 | padding: 10px 20px; 35 | display: inline-block; 36 | border-radius: 3px; 37 | } 38 | 39 | .not-found-content p { 40 | color: #fff !important; 41 | font-size: 1.2em; 42 | } 43 | 44 | .not-found-content a { 45 | position: relative; 46 | display: inline-block; 47 | padding: 10px 25px; 48 | background: #ff0562; 49 | color: #fff; 50 | text-decoration: none; 51 | margin-top: 25px; 52 | border-radius: 25px; 53 | border-bottom: 4px solid #d00d56; 54 | } 55 | -------------------------------------------------------------------------------- /frontend/src/pages/logs/containers/logger-delete-ulog.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Grid, Header, Icon } from 'semantic-ui-react'; 3 | import { useRemoveAllLogfilesMutation } from '../../../graphql/generated/dist'; 4 | import LoggerModal from '../components/modal'; 5 | 6 | const PruneUlog = () => { 7 | const [modal, setModal] = useState(false); 8 | const [removeUlogFiles, { loading: ulogLoading }] = useRemoveAllLogfilesMutation({ 9 | onCompleted: () => setModal(false) 10 | }); 11 | 12 | return ( 13 | <> 14 | {modal && ( 15 | setModal(false)} 19 | acknowledge={() => removeUlogFiles()} 20 | loading={ulogLoading} 21 | /> 22 | )} 23 | 24 |
25 | 26 | 27 | 30 | 31 | 32 | ); 33 | }; 34 | 35 | export default PruneUlog; 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support_request.yml: -------------------------------------------------------------------------------- 1 | name: Support Request 2 | description: Support for uavcast setup or configuration 3 | title: '[Support]: ' 4 | labels: ['support'] 5 | assignees: [] 6 | body: 7 | - type: textarea 8 | id: description 9 | attributes: 10 | label: Describe the problem you are having 11 | validations: 12 | required: true 13 | - type: input 14 | id: version 15 | attributes: 16 | label: Version 17 | description: Visible on the Settings page in the Web UI 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: logs 22 | attributes: 23 | label: Attach Logfiles (Optional) 24 | description: Download system logs from Web UI, then Drag and drop here. 25 | - type: dropdown 26 | id: os 27 | attributes: 28 | label: Operating system 29 | options: 30 | - Ubuntu 31 | - Debian 32 | - Windows 33 | - Other 34 | validations: 35 | required: true 36 | - type: dropdown 37 | id: network 38 | attributes: 39 | label: Network connection 40 | options: 41 | - Wired 42 | - Wireless 43 | - LTE / 4G 44 | validations: 45 | required: true 46 | - type: textarea 47 | id: other 48 | attributes: 49 | label: Any other information that may be helpful 50 | -------------------------------------------------------------------------------- /backend/src/entity/Logger.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Check, PrimaryColumn, Column, getRepository } from 'typeorm'; 2 | import { Field, ID, ObjectType } from 'type-graphql'; 3 | 4 | @ObjectType({ isAbstract: true }) 5 | @Entity() 6 | @Check(`ensure = 1`) 7 | export class Logger { 8 | @PrimaryColumn({ type: 'int', default: () => `1`, nullable: false }) 9 | public ensure: 1; 10 | 11 | @Field(() => ID) 12 | @Column({ type: Number, default: 1 }) 13 | id: number; 14 | 15 | @Field() 16 | @Column({ type: Boolean, default: false, nullable: false }) 17 | debug: boolean; 18 | 19 | @Field() 20 | @Column({ type: Number, default: 1.0, nullable: false }) 21 | resolution: number; 22 | 23 | @Field() 24 | @Column({ type: Date, default: false, nullable: false }) 25 | datetime: Date = new Date(); 26 | 27 | @Field() 28 | @Column({ type: Boolean, default: true, nullable: false }) 29 | logtemperature: boolean; 30 | 31 | @Field() 32 | @Column({ type: Boolean, default: true, nullable: false }) 33 | cellSignal: boolean; 34 | 35 | @Field() 36 | @Column({ type: Boolean, default: true, nullable: false }) 37 | satellites: boolean; 38 | 39 | @Field() 40 | @Column({ type: Boolean, default: true, nullable: false }) 41 | altitude: boolean; 42 | } 43 | export const getLoggerRepository = () => { 44 | return getRepository(Logger); 45 | }; 46 | -------------------------------------------------------------------------------- /backend/src/entity/Modem.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Check, PrimaryColumn, Column, getRepository } from 'typeorm'; 2 | import { Field, ObjectType } from 'type-graphql'; 3 | 4 | @ObjectType({ isAbstract: true }) 5 | @Entity() 6 | @Check(`ensure = 1`) 7 | export class Modem { 8 | @PrimaryColumn({ type: 'int', default: () => `1`, nullable: false }) 9 | public ensure: 1; 10 | 11 | @Field() 12 | @Column({ type: Boolean, default: false, nullable: false }) 13 | enableModem: boolean; 14 | 15 | @Field() 16 | @Column({ type: String, default: 'hilink', nullable: false }) 17 | modemType: string; 18 | 19 | @Field() 20 | @Column({ type: Boolean, default: false, nullable: false }) 21 | modemInformation: boolean; 22 | 23 | @Field() 24 | @Column({ type: String, default: 'eth1' }) 25 | modemInterface: string; 26 | 27 | @Field() 28 | @Column({ type: String, default: 'cdc-wdm0', nullable: false }) 29 | internalAddress: string; 30 | 31 | @Field() 32 | @Column({ type: String, default: 0, nullable: false }) 33 | pinCode: string; 34 | 35 | @Field() 36 | @Column({ type: String, default: '', nullable: false }) 37 | username: string; 38 | 39 | @Field() 40 | @Column({ type: String, default: '', nullable: false }) 41 | password: string; 42 | } 43 | 44 | export const getModemRepository = () => { 45 | return getRepository(Modem); 46 | }; 47 | -------------------------------------------------------------------------------- /frontend/src/graphql/query/supervisor.graphql: -------------------------------------------------------------------------------- 1 | query getAvailableVersions($application: String!) { 2 | getAvailableVersions(application: $application) { 3 | count 4 | results { 5 | id 6 | name 7 | tag_status 8 | last_updated 9 | full_size 10 | } 11 | error 12 | } 13 | } 14 | query getUavcastInformation { 15 | getUavcastInformation { 16 | message { 17 | supervisor { 18 | repo 19 | isRunning 20 | remoteVersion 21 | localVersion 22 | hasLatest 23 | newVersionExsist 24 | } 25 | uavcast { 26 | repo 27 | isRunning 28 | remoteVersion 29 | localVersion 30 | hasLatest 31 | newVersionExsist 32 | } 33 | } 34 | errors { 35 | path 36 | message 37 | } 38 | } 39 | } 40 | query getSupervisorInformation { 41 | getSupervisorInformation { 42 | message { 43 | supervisor { 44 | repo 45 | isRunning 46 | remoteVersion 47 | localVersion 48 | hasLatest 49 | newVersionExsist 50 | } 51 | uavcast { 52 | repo 53 | isRunning 54 | remoteVersion 55 | localVersion 56 | hasLatest 57 | newVersionExsist 58 | } 59 | } 60 | errors { 61 | path 62 | message 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /frontend/src/pages/dashboard/containers/mavlinkStatus.tsx: -------------------------------------------------------------------------------- 1 | import { useSubscription } from '@apollo/client'; 2 | import { Card } from 'semantic-ui-react'; 3 | import { GreenLed, RedLed } from '../../../components/led'; 4 | import { MavlinkDocument } from '../../../graphql/generated/dist'; 5 | 6 | const MavlinkStatus = () => { 7 | const { data: { mavlink = {} } = {} } = useSubscription(MavlinkDocument); 8 | 9 | const { heartbeat = {} } = mavlink.message || {}; 10 | // console.log(heartbeat); 11 | return ( 12 | 13 | 14 | Mavlink Status 15 | 16 | Mavlink connected: 17 | {heartbeat?.connected ? : } 18 | 19 | 20 | Vehicle Armed: 21 | {heartbeat?.armed ? : } 22 | 23 | 24 | Firmware: 25 | {heartbeat?.firmware} 26 | 27 | 28 | Frame: Frame: 29 | {heartbeat?.frame} 30 | 31 | 32 | 33 | ); 34 | }; 35 | 36 | export default MavlinkStatus; 37 | -------------------------------------------------------------------------------- /frontend/script/analyse.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'production'; 2 | 3 | const webpack = require('webpack'); 4 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 5 | const webpackConfigProd = require('react-scripts/config/webpack.config')('production'); 6 | 7 | // this one is optional, just for better feedback on build 8 | const chalk = require('chalk'); 9 | const ProgressBarPlugin = require('progress-bar-webpack-plugin'); 10 | const green = (text) => { 11 | return chalk.green.bold(text); 12 | }; 13 | 14 | // pushing BundleAnalyzerPlugin to plugins array 15 | webpackConfigProd.plugins.push(new BundleAnalyzerPlugin()); 16 | 17 | // optional - pushing progress-bar plugin for better feedback; 18 | // it can and will work without progress-bar, 19 | // but during build time you will not see any messages for 10-60 seconds (depends on the size of the project) 20 | // and decide that compilation is kind of hang up on you; progress bar shows nice progression of webpack compilation 21 | webpackConfigProd.plugins.push( 22 | new ProgressBarPlugin({ 23 | format: `${green('analyzing...')} ${green('[:bar]')}${green('[:percent]')}${green('[:elapsed seconds]')} - :msg`, 24 | }) 25 | ); 26 | 27 | // actually running compilation and waiting for plugin to start explorer 28 | webpack(webpackConfigProd, (err, stats) => { 29 | if (err || stats.hasErrors()) { 30 | console.error(err); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /frontend/src/pages/flightController/containers/tcpPort.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Grid, Header, Input } from 'semantic-ui-react'; 3 | import { useFlightControllerQuery, useUpdateFlightControllerMutation } from '../../../graphql/generated/dist'; 4 | 5 | const TcpPort = () => { 6 | const { data: { flightController = {} } = {}, loading: fcLoading }: any = useFlightControllerQuery(); 7 | const [storeData, { loading: storeDataLoading }] = useUpdateFlightControllerMutation(); 8 | 9 | const inputHandler = (e: React.SyntheticEvent, data: any) => { 10 | storeData({ variables: { fc: { tcpPort: data.value } } }); 11 | }; 12 | 13 | const { tcpPort } = flightController?.data || {}; 14 | return ( 15 | 16 | 17 |
18 | 19 | 20 | 30 | 31 | 32 | ); 33 | }; 34 | 35 | export default TcpPort; 36 | -------------------------------------------------------------------------------- /backend/src/entity/FlightController.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Check, PrimaryColumn, Column, getRepository } from 'typeorm'; 2 | import { Field, ID, ObjectType } from 'type-graphql'; 3 | 4 | @ObjectType({ isAbstract: true }) 5 | @Entity() 6 | @Check(`ensure = 1`) 7 | export class FlightController { 8 | @PrimaryColumn({ type: 'int', default: () => `1`, nullable: false }) 9 | public ensure: 1; 10 | 11 | @Field(() => ID) 12 | @Column({ type: Number, default: 1 }) 13 | id: number; 14 | 15 | @Field() 16 | @Column({ type: String, default: 'apm', nullable: false }) 17 | controller: string; 18 | 19 | @Field() 20 | @Column({ type: String, default: 'udp', nullable: false }) 21 | protocol: string; 22 | 23 | @Field() 24 | @Column({ type: String, default: 'usb', nullable: false }) 25 | connectionType: string; 26 | 27 | @Field() 28 | @Column({ type: String, default: '/dev/ttyACM0', nullable: false }) 29 | internalAddress: string; 30 | 31 | @Field() 32 | @Column({ type: String, default: '57600', nullable: false }) 33 | baudRate: string; 34 | 35 | @Field() 36 | @Column({ type: String, default: '5790', nullable: false }) 37 | tcpPort: string; 38 | 39 | @Field() 40 | @Column({ type: Boolean, default: false, nullable: false }) 41 | binFlightLog: boolean; 42 | } 43 | 44 | export const getFlightControllerRepository = () => { 45 | return getRepository(FlightController); 46 | }; 47 | -------------------------------------------------------------------------------- /frontend/src/pages/endpoints/components/placeHolder.tsx: -------------------------------------------------------------------------------- 1 | import { Placeholder } from 'semantic-ui-react'; 2 | 3 | const GcsPlaceHolder = () => { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ); 36 | }; 37 | 38 | export default GcsPlaceHolder; 39 | -------------------------------------------------------------------------------- /frontend/src/pages/logs/containers/logger-delete-stats.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Grid, Header, Icon } from 'semantic-ui-react'; 3 | import { usePruneLogFilesMutation } from '../../../graphql/generated/dist'; 4 | import LoggerModal from '../components/modal'; 5 | 6 | const PruneStatsLogs = () => { 7 | const [modal, setModal] = useState(false); 8 | const [deleteFiles, { loading: sysLoading }] = usePruneLogFilesMutation(); 9 | 10 | const pruneFiles = (service: string) => { 11 | deleteFiles({ variables: { service } }).then(() => setModal(false)); 12 | }; 13 | return ( 14 | <> 15 | {modal && ( 16 | setModal(false)} 20 | acknowledge={() => pruneFiles('stats')} 21 | loading={sysLoading} 22 | /> 23 | )} 24 | 25 |
26 | 27 | 28 | 31 | 32 | 33 | ); 34 | }; 35 | 36 | export default PruneStatsLogs; 37 | -------------------------------------------------------------------------------- /frontend/src/graphql/query/logs.graphql: -------------------------------------------------------------------------------- 1 | # query getLoggData { 2 | # getLoggData { 3 | # logs { 4 | # datetime 5 | # value 6 | # group 7 | # } 8 | # } 9 | # } 10 | 11 | query getFileNames { 12 | getFileNames { 13 | files 14 | } 15 | } 16 | 17 | query getFileData($filename: String!) { 18 | getFileData(filename: $filename) { 19 | data 20 | } 21 | } 22 | 23 | query getTempLog($properties: LogProperties!) { 24 | getTempLog(properties: $properties) { 25 | file { 26 | message 27 | timestamp 28 | } 29 | } 30 | } 31 | 32 | query getNetworkLog($properties: LogProperties!) { 33 | getNetworkLog(properties: $properties) { 34 | file { 35 | message { 36 | iface 37 | rx_bytes 38 | tx_bytes 39 | rx_sec 40 | tx_sec 41 | } 42 | timestamp 43 | } 44 | } 45 | } 46 | query getServerLog($properties: LogProperties!) { 47 | getServerLog(properties: $properties) { 48 | file { 49 | timestamp 50 | message 51 | data 52 | level 53 | } 54 | } 55 | } 56 | query getCpuLog($properties: LogProperties!) { 57 | getCpuLog(properties: $properties) { 58 | file { 59 | timestamp 60 | message 61 | } 62 | } 63 | } 64 | query getLoggerParameters { 65 | getLoggerParameters { 66 | logs { 67 | id 68 | debug 69 | cellSignal 70 | satellites 71 | altitude 72 | resolution 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /backend/src/resolvers/ChildProcessCmd.ts: -------------------------------------------------------------------------------- 1 | import { Root, Args, Subscription, Resolver, PubSub, Mutation, Publisher } from 'type-graphql'; 2 | import { KernelInput } from '../graphql-input-types/KernelInput'; 3 | import { KernelResponse } from '../graphql-response-types/KernelResponse'; 4 | import { childProcessCmdCallback } from '../utils/childProcessCmd'; 5 | 6 | @Resolver() 7 | export class KernelResolver { 8 | @Mutation(() => KernelResponse) 9 | async childProcessCmd(@PubSub('KERNEL_MESSAGE') publish: Publisher, @Args() { cmd, path: cwd, logg }: KernelInput) { 10 | await publish({ message: 'waiting for response...\n' }); 11 | 12 | childProcessCmdCallback({ cmd, options: { cwd }, logg }, async ({ error, response, code }) => { 13 | if (error) { 14 | return await publish({ errors: [{ message: error.toString('utf8'), path: 'kernelMessage' }] }); 15 | } 16 | 17 | await publish({ message: response.toString('utf8'), data: `Return code ${code}` }); 18 | }); 19 | 20 | return true; 21 | } 22 | 23 | @Subscription(() => KernelResponse, { 24 | topics: 'KERNEL_MESSAGE' // single topic 25 | // topics: ({ args, payload, context }) => args.topic // or dynamic topic function 26 | // filter: ():any => { 27 | // console.log('object') 28 | // } 29 | }) 30 | async stdout(@Root() stdout: any): Promise { 31 | // console.log('stdout', stdout); 32 | return { message: stdout.message, errors: stdout.errors }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /init/journalctl3.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | 3 | import argparse 4 | import os 5 | import sys 6 | 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument('-u', '--unit', metavar='unit', type=str, required=True, help='Systemd unit to display') 9 | parser.add_argument('-f', '--follow', default=False, action='store_true', help='Follows the log') 10 | parser.add_argument('-n', '--lines', metavar='num', type=int, help='Num of lines to display') 11 | parser.add_argument('--no-pager', default=False, action='store_true', help='Do not pipe through a pager') 12 | parser.add_argument('--system', default=False, action='store_true', help='Show system units') 13 | parser.add_argument('--user', default=False, action='store_true', help='Show user units') 14 | parser.add_argument('--root', metavar='path', type=str, help='Use subdirectory path') 15 | parser.add_argument('-x', default=False, action='store_true', help='Switch on verbose mode') 16 | args = parser.parse_args() 17 | 18 | systemctl_py = "systemctl3.py" 19 | path = os.path.dirname(sys.argv[0]) 20 | systemctl = os.path.join(path, systemctl_py) 21 | 22 | cmd = [ systemctl, "log", args.unit ] # drops the -u 23 | if args.follow: cmd += [ "-f" ] 24 | if args.lines: cmd += [ "-n", str(args.lines) ] 25 | if args.no_pager: cmd += [ "--no-pager" ] 26 | if args.system: cmd += [ "--system" ] 27 | elif args.user: cmd += [ "--user" ] 28 | if args.root: cmd += [ "--root", start(args.root) ] 29 | if args.x: cmd += [ "-vvv" ] 30 | 31 | os.execvp(cmd[0], cmd) 32 | -------------------------------------------------------------------------------- /bin/Makefile: -------------------------------------------------------------------------------- 1 | 2 | # ***************************************************** 3 | # Variables to control Makefile operation 4 | CC = g++ 5 | CXXFLAGS = -g -Wall 6 | 7 | # LINK = -lpthread -lsqlite3 -ljsoncpp -lcurl `pkg-config --libs gstreamer-1.0 ` 8 | LINK = -lpthread -lsqlite3 -ljsoncpp -lcurl 9 | # `pkg-config --libs opencv` 10 | # **************************************************** 11 | # Targets needed to bring the executable up to date 12 | all: build_dir main status vpn versionControl 13 | main: main.o includes/camera.o includes/db.o includes/docker.o includes/modem.o includes/mavlink.o includes/utils.o includes/flight_controller.o includes/app.o includes/logger.o 14 | $(CC) $(CXXFLAGS) main.o includes/camera.o includes/db.o includes/docker.o includes/modem.o includes/mavlink.o includes/utils.o includes/flight_controller.o includes/app.o includes/logger.o -o build/uav_main $(LINK) 15 | 16 | status: status.o includes/db.o includes/modem.o includes/utils.o includes/logger.o 17 | $(CC) $(CXXFLAGS) status.o includes/db.o includes/modem.o includes/utils.o includes/logger.o -o build/uav_status $(LINK) 18 | 19 | vpn: vpn.o includes/db.o includes/utils.o 20 | $(CC) $(CXXFLAGS) vpn.o includes/db.o includes/utils.o -o build/uav_vpn $(LINK) 21 | 22 | versionControl: verctl.o includes/utils.o 23 | $(CC) -lcurl verctl.o includes/utils.o -o build/verctl $(LINK) 24 | 25 | #Clean up method 26 | clean: 27 | -rm *.o includes/*.o 28 | -rm -rf build 29 | 30 | build_dir: 31 | mkdir -p build 32 | 33 | .PHONY: -------------------------------------------------------------------------------- /frontend/src/graphql/mutation/camera.graphql: -------------------------------------------------------------------------------- 1 | mutation updateCamera($properties: CameraProperties!) { 2 | updateCamera(properties: $properties) { 3 | database { 4 | id 5 | key 6 | name 7 | path 8 | protocol 9 | resolution 10 | enableCamera 11 | customPipeline 12 | framesPrSecond 13 | bitratePrSecond 14 | contrast 15 | rotation 16 | brightness 17 | whiteBalance 18 | flipCamera 19 | } 20 | availableCams { 21 | key 22 | value 23 | text 24 | caps { 25 | value 26 | text 27 | height 28 | width 29 | format 30 | } 31 | } 32 | } 33 | } 34 | 35 | mutation resetCameraDatabase { 36 | resetCameraDatabase { 37 | database { 38 | id 39 | name 40 | key 41 | path 42 | protocol 43 | resolution 44 | enableCamera 45 | customPipeline 46 | framesPrSecond 47 | bitratePrSecond 48 | contrast 49 | rotation 50 | brightness 51 | whiteBalance 52 | flipCamera 53 | } 54 | availableCams { 55 | key 56 | value 57 | text 58 | caps { 59 | value 60 | text 61 | height 62 | width 63 | format 64 | } 65 | } 66 | } 67 | } 68 | 69 | mutation cameraActions($properties: CameraActionProperties!) { 70 | cameraActions(properties: $properties) { 71 | playStream 72 | stopStream 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /frontend/src/pages/logs/containers/logger-delete-docker.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Grid, Header, Icon } from 'semantic-ui-react'; 3 | import { usePruneLogFilesMutation } from '../../../graphql/generated/dist'; 4 | import LoggerModal from '../components/modal'; 5 | 6 | const PruneDockerLogs = () => { 7 | const [modal, setModal] = useState(false); 8 | const [deleteFiles, { loading: sysLoading }] = usePruneLogFilesMutation(); 9 | 10 | const pruneFiles = (service: string) => { 11 | deleteFiles({ variables: { service } }).then(() => setModal(false)); 12 | }; 13 | return ( 14 | <> 15 | {modal && ( 16 | setModal(false)} 20 | acknowledge={() => pruneFiles('docker')} 21 | loading={sysLoading} 22 | /> 23 | )} 24 | 25 |
26 | 27 | 28 | {/* 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default PruneDockerLogs; 38 | -------------------------------------------------------------------------------- /frontend/src/pages/flightController/containers/internalAddress.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Grid, Header, Input } from 'semantic-ui-react'; 3 | import { useFlightControllerQuery, useUpdateFlightControllerMutation } from '../../../graphql/generated/dist'; 4 | 5 | const InternalAddress = () => { 6 | const { data: { flightController = {} } = {}, loading: fcLoading }: any = useFlightControllerQuery(); 7 | const [storeData, { loading: storeDataLoading }] = useUpdateFlightControllerMutation(); 8 | 9 | const inputHandler = (e: React.SyntheticEvent, data: any) => { 10 | storeData({ variables: { fc: { internalAddress: data.value } } }); 11 | }; 12 | 13 | const { internalAddress = '' } = flightController?.data || {}; 14 | 15 | return ( 16 | 17 | 18 |
19 | 20 | 21 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default InternalAddress; 38 | -------------------------------------------------------------------------------- /frontend/src/pages/logs/containers/logger-delete-server.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Grid, Header, Icon } from 'semantic-ui-react'; 3 | import { usePruneLogFilesMutation } from '../../../graphql/generated/dist'; 4 | import LoggerModal from '../components/modal'; 5 | 6 | const PruneServerLogs = () => { 7 | const [modal, setModal] = useState(false); 8 | const [deleteFiles, { loading: sysLoading }] = usePruneLogFilesMutation(); 9 | 10 | const pruneFiles = (service: string) => { 11 | deleteFiles({ variables: { service } }).then(() => setModal(false)); 12 | }; 13 | return ( 14 | <> 15 | {modal && ( 16 | setModal(false)} 20 | acknowledge={() => pruneFiles('server')} 21 | loading={sysLoading} 22 | /> 23 | )} 24 | 25 |
26 | 27 | 28 | {/* 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default PruneServerLogs; 38 | -------------------------------------------------------------------------------- /backend/src/graphql-input-types/LogViewer.ts: -------------------------------------------------------------------------------- 1 | import { ArgsType, Field, InputType } from 'type-graphql'; 2 | 3 | @ArgsType() 4 | export class LogFileInput { 5 | // @Field({ nullable: true }) 6 | @Field() 7 | filename: string; 8 | } 9 | 10 | // Log Parameters 11 | @InputType() 12 | export class LogParameters { 13 | @Field({ nullable: true }) 14 | debug: boolean; 15 | 16 | @Field({ nullable: true }) 17 | cellSignal: boolean; 18 | 19 | @Field({ nullable: true }) 20 | satellites: boolean; 21 | 22 | @Field({ nullable: true }) 23 | altitude: boolean; 24 | 25 | @Field({ nullable: true }) 26 | resolution: number; 27 | } 28 | 29 | @InputType() 30 | @ArgsType() 31 | export class LogParametersInput { 32 | @Field() 33 | parameters: LogParameters; 34 | } 35 | 36 | @InputType() 37 | export class LogProperties { 38 | @Field() 39 | minutes: number; 40 | 41 | @Field({ nullable: true }) 42 | limit: number; 43 | } 44 | 45 | @InputType() 46 | @ArgsType() 47 | export class LogPeriodeInput { 48 | @Field() 49 | properties: LogProperties; 50 | } 51 | 52 | @InputType() 53 | export class DownloadLogProperties { 54 | @Field({ nullable: true }) 55 | from: number; 56 | 57 | @Field({ nullable: true }) 58 | until: number; 59 | 60 | @Field() 61 | periode: string; 62 | } 63 | @InputType() 64 | @ArgsType() 65 | export class DownloadLogsInput { 66 | @Field() 67 | properties: DownloadLogProperties; 68 | } 69 | 70 | @InputType() 71 | @ArgsType() 72 | export class PruneLogsInput { 73 | @Field() 74 | service: string; 75 | } 76 | -------------------------------------------------------------------------------- /frontend/src/pages/modem/containers/stick.footer.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from 'semantic-ui-react'; 2 | import { useChildProcessCmdMutation } from '../../../graphql/generated/dist'; 3 | 4 | const StickFooter = () => { 5 | const [kernelCommand] = useChildProcessCmdMutation(); 6 | 7 | return ( 8 | 9 | 19 | 20 | 21 | {/* */} 22 | 31 | {/* */} 32 | 41 | {/* */} 42 | 51 | {/* */} 52 | 53 | 54 | ); 55 | }; 56 | 57 | export default StickFooter; 58 | -------------------------------------------------------------------------------- /frontend/src/pages/settings/containers/application.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import React, { useEffect, useState } from 'react'; 3 | import { Grid, Header } from 'semantic-ui-react'; 4 | import WebPortInput from '../components/inputWebPort'; 5 | 6 | const Application = () => { 7 | const [DarkTheme, setDarkTheme] = useState(false); 8 | 9 | useEffect(() => { 10 | const theme = window.localStorage.getItem('Theme'); 11 | setDarkTheme(theme === 'dark'); 12 | }, []); 13 | 14 | const toggleTheme = (e: React.ChangeEvent) => { 15 | const theme = e.currentTarget.checked === true ? 'dark' : 'light'; 16 | 17 | document.documentElement.setAttribute('data-theme', theme); 18 | window.localStorage.setItem('Theme', theme); 19 | setDarkTheme(e.target.checked); 20 | }; 21 | 22 | const themeClass = classnames({ 23 | 'switch-label': true, 24 | 'switch-green': DarkTheme 25 | }); 26 | 27 | return ( 28 | 29 | 30 |
31 | 32 | 33 | 38 | 39 | 40 | 41 | ); 42 | }; 43 | 44 | export default Application; 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Bernt Christian Egeland. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted 4 | provided that the following conditions are met: 5 | 6 | 1. Redistributions of source 7 | code must retain the above copyright notice, this list of conditions and the 8 | following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the 11 | above copyright notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 17 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 21 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 24 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | DAMAGE. 27 | -------------------------------------------------------------------------------- /frontend/src/components/suspense1columns.tsx: -------------------------------------------------------------------------------- 1 | import { Container, Grid, Placeholder, Segment } from 'semantic-ui-react'; 2 | 3 | const Suspense1column = () => { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | export default Suspense1column; 43 | -------------------------------------------------------------------------------- /frontend/src/app/layouts.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, useEffect } from 'react'; 2 | import { withRouter } from 'react-router-dom'; 3 | import Header from '../components/Header/Header'; 4 | import SuspenseMainLoading from '../components/suspense2columns'; 5 | import ServerHasDisconnected from '../components/serverHasDisconnected'; 6 | import ReactGA from 'react-ga'; 7 | import { useGetUavcastInformationQuery } from '../graphql/generated/dist'; 8 | 9 | const Sidebar = React.lazy(() => import('../components/Sidebar/Sidebar')); 10 | 11 | export const LayoutPublic = withRouter(({ children, location }: any): any => { 12 | const { data: versionInformation, loading: uavcastLoading } = useGetUavcastInformationQuery(); 13 | const { message } = versionInformation?.getUavcastInformation || {}; 14 | 15 | useEffect(() => { 16 | if (uavcastLoading) return; 17 | if (process.env.NODE_ENV === 'production') { 18 | ReactGA.pageview(message?.uavcast?.localVersion + location.pathname); 19 | } 20 | }, [message, location, uavcastLoading]); 21 | 22 | return ( 23 |
24 |
25 |
26 | 27 |
28 | 29 | }>{children} 30 | {/* */} 31 |
32 | {/*
34 |
35 | ); 36 | }); 37 | 38 | export const LayoutAnonymous: React.FC> = (props) => { 39 | return
{props.children}
; 40 | }; 41 | -------------------------------------------------------------------------------- /frontend/src/pages/flightController/containers/flightLog.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Checkbox, Grid, Header } from 'semantic-ui-react'; 3 | import { useFlightControllerQuery, useUpdateFlightControllerMutation } from '../../../graphql/generated/dist'; 4 | 5 | const FlightLog = () => { 6 | const { data: { flightController = {} } = {} }: any = useFlightControllerQuery(); 7 | const [storeData] = useUpdateFlightControllerMutation(); 8 | 9 | const inputHandler = (e: React.SyntheticEvent, data: any) => { 10 | storeData({ variables: { fc: { binFlightLog: data.checked } } }); 11 | }; 12 | 13 | const { binFlightLog } = flightController?.data || {}; 14 | return ( 15 | 16 | 17 |
22 | 23 | 24 | 25 | {/* */} 35 | 36 | 37 | ); 38 | }; 39 | 40 | export default FlightLog; 41 | -------------------------------------------------------------------------------- /frontend/src/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/anchor-has-content */ 2 | const Header = (props: any): JSX.Element => { 3 | const sidebarToggle = (e: { preventDefault: () => void }) => { 4 | e.preventDefault(); 5 | document.body.classList.toggle('sidebar-hidden'); 6 | }; 7 | 8 | const mobileSidebarToggle = (e: { preventDefault: () => void }) => { 9 | e.preventDefault(); 10 | document.body.classList.toggle('sidebar-mobile-show'); 11 | }; 12 | 13 | return ( 14 | 42 | ); 43 | }; 44 | 45 | export default Header; 46 | -------------------------------------------------------------------------------- /bin/includes/rapidjson/internal/swap.h: -------------------------------------------------------------------------------- 1 | // Tencent is pleased to support the open source community by making RapidJSON available. 2 | // 3 | // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. 4 | // 5 | // Licensed under the MIT License (the "License"); you may not use this file except 6 | // in compliance with the License. You may obtain a copy of the License at 7 | // 8 | // http://opensource.org/licenses/MIT 9 | // 10 | // Unless required by applicable law or agreed to in writing, software distributed 11 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 13 | // specific language governing permissions and limitations under the License. 14 | 15 | #ifndef RAPIDJSON_INTERNAL_SWAP_H_ 16 | #define RAPIDJSON_INTERNAL_SWAP_H_ 17 | 18 | #include "../rapidjson.h" 19 | 20 | #if defined(__clang__) 21 | RAPIDJSON_DIAG_PUSH 22 | RAPIDJSON_DIAG_OFF(c++98-compat) 23 | #endif 24 | 25 | RAPIDJSON_NAMESPACE_BEGIN 26 | namespace internal { 27 | 28 | //! Custom swap() to avoid dependency on C++ header 29 | /*! \tparam T Type of the arguments to swap, should be instantiated with primitive C++ types only. 30 | \note This has the same semantics as std::swap(). 31 | */ 32 | template 33 | inline void Swap(T& a, T& b) RAPIDJSON_NOEXCEPT { 34 | T tmp = a; 35 | a = b; 36 | b = tmp; 37 | } 38 | 39 | } // namespace internal 40 | RAPIDJSON_NAMESPACE_END 41 | 42 | #if defined(__clang__) 43 | RAPIDJSON_DIAG_POP 44 | #endif 45 | 46 | #endif // RAPIDJSON_INTERNAL_SWAP_H_ 47 | -------------------------------------------------------------------------------- /frontend/src/pages/vpn/containers/openvpn.input.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Grid, Header, Input } from 'semantic-ui-react'; 3 | import { useStoreVpnValuesMutation, useVpnDataQuery } from '../../../graphql/generated/dist'; 4 | import Dropzone from '../containers/dropzone'; 5 | 6 | const OpenVpnInput = () => { 7 | const [storeData] = useStoreVpnValuesMutation(); 8 | const { data: { vpnData = { data: '' } } = {} }: any = useVpnDataQuery(); 9 | 10 | const inputHandler = (e: any) => { 11 | //@ts-ignore 12 | storeData({ variables: { properties: { [e.target.name]: e.target.value } } }); 13 | }; 14 | const { data } = vpnData; 15 | 16 | return ( 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | export default OpenVpnInput; 42 | -------------------------------------------------------------------------------- /backend/src/logger/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { paths } from '../config/paths'; 4 | import CpuLogger from './cpu'; 5 | import NetworkLogger from './network'; 6 | import TemperatureLogger from './temperature'; 7 | import ServerLogger from './server'; 8 | import DockerLogger from './docker'; 9 | 10 | const SystemLogger: any = { 11 | logFolder: paths.logFolder, 12 | cpuLogger: null as any, 13 | networkLogger: null as any, 14 | temperatureLogger: null as any, 15 | serverLogger: null as any, 16 | dockerLogger: null as any, 17 | 18 | initilize: function () { 19 | this.cpuLogger = new CpuLogger(); 20 | this.networkLogger = new NetworkLogger(); 21 | this.temperatureLogger = new TemperatureLogger(); 22 | this.serverLogger = new ServerLogger(); 23 | this.dockerLogger = new DockerLogger(); 24 | }, 25 | pruneLogFiles: function (service: string) { 26 | return new Promise((resolve, reject) => { 27 | const log_file_array = fs.readdirSync(this.logFolder).filter((file: any) => { 28 | if (file.indexOf(service) > -1) return file; 29 | }); 30 | 31 | for (const file in log_file_array) { 32 | fs.unlink(path.join(this.logFolder, log_file_array[file]), (err) => { 33 | if (err) { 34 | reject(err); 35 | } 36 | }); 37 | } 38 | 39 | this.initilize(); 40 | 41 | setTimeout(() => { 42 | // Fake better user experience 43 | resolve(true); 44 | }, 2000); 45 | }); 46 | }, 47 | getLogger: function () { 48 | return this; 49 | } 50 | }; 51 | 52 | export default SystemLogger; 53 | -------------------------------------------------------------------------------- /frontend/src/pages/vpn/containers/openvpn.footer.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from 'semantic-ui-react'; 2 | import { useChildProcessCmdMutation } from '../../../graphql/generated/dist'; 3 | 4 | const HiLinkFooter = () => { 5 | const [kernelCommand] = useChildProcessCmdMutation(); 6 | 7 | return ( 8 | 9 | 22 | 23 | 36 | 45 | 54 | 55 | ); 56 | }; 57 | 58 | export default HiLinkFooter; 59 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | web_lint: 9 | name: Web - Lint 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | - uses: actions/setup-node@master 14 | with: 15 | node-version: 14.x 16 | - run: npm install 17 | working-directory: ./backend 18 | - name: Lint 19 | run: npm run lint:cmd 20 | working-directory: ./backend 21 | 22 | - run: npm install 23 | working-directory: ./frontend 24 | - name: Lint 25 | run: npm run lint:cmd 26 | working-directory: ./frontend 27 | 28 | web_build: 29 | name: Web - Build 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@master 33 | - uses: actions/setup-node@master 34 | with: 35 | node-version: 14.x 36 | - run: npm install 37 | working-directory: ./backend 38 | - name: Build 39 | run: npm run build 40 | working-directory: ./backend 41 | 42 | - run: npm install 43 | working-directory: ./frontend 44 | - name: Build 45 | run: npm run build 46 | working-directory: ./frontend 47 | # web_test: 48 | # name: Web - Test 49 | # runs-on: ubuntu-latest 50 | # steps: 51 | # - uses: actions/checkout@master 52 | # - uses: actions/setup-node@master 53 | # with: 54 | # node-version: 14.x 55 | # - run: npm install 56 | # working-directory: ./web 57 | # - name: Test 58 | # run: npm run test 59 | # working-directory: ./web -------------------------------------------------------------------------------- /frontend/src/__tests__/pages/endpoints/end.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jest/no-commented-out-tests */ 2 | /* eslint-disable space-before-function-paren */ 3 | // import * as React from 'react'; 4 | // import { customRender } from '../../../utils/customRender'; 5 | // import { GetEndpointsDocument } from '../../../graphql/generated/dist'; 6 | // import { Route } from 'react-router'; 7 | // import Endpoints from '../../../pages/endpoints'; 8 | // import ModuleActive from '../../../pages/endpoints/components/apModuleActive'; 9 | import '@testing-library/jest-dom/extend-expect'; 10 | import '@apollo/client'; 11 | 12 | // const endpoints = { 13 | // // route: '/endpoint', 14 | // request: { 15 | // query: GetEndpointsDocument, 16 | // variables: {} 17 | // }, 18 | // result: { 19 | // data: { 20 | // getEndpoints: { 21 | // data: [ 22 | // { 23 | // id: 1, 24 | // telemEnable: false, 25 | // moduleActive: false, 26 | // name: 'gcs', 27 | // endpointIPaddress: '10.0.0.100', 28 | // telemetryPort: 14550, 29 | // videoPort: 5600, 30 | // videoEnable: false 31 | // } 32 | // ] 33 | // } 34 | // } 35 | // } 36 | // }; 37 | 38 | // const waitForData = () => new Promise((res) => setTimeout(res, 0)); 39 | 40 | // describe('endpoints', () => { 41 | // test('paid user', async () => { 42 | // const { getByTestId } = customRender(, [endpoints]); 43 | // await waitForData(); 44 | // const el = getByTestId('endpoint-title'); 45 | // expect(el.textContent).toContain('Ground Control Stations'); 46 | // }); 47 | // }); 48 | -------------------------------------------------------------------------------- /frontend/src/pages/vpn/containers/zerotierId.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Grid, Header, Icon, Input } from 'semantic-ui-react'; 3 | import { useChildProcessCmdMutation } from '../../../graphql/generated/dist'; 4 | 5 | const ZerotierId = () => { 6 | const [networkId, setNetworkId] = useState(''); 7 | const [kernelCommand] = useChildProcessCmdMutation(); 8 | 9 | const inputHandler = (e: React.SyntheticEvent, data: any) => { 10 | setNetworkId(data.value); 11 | }; 12 | 13 | return ( 14 | <> 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default ZerotierId; 47 | -------------------------------------------------------------------------------- /frontend/src/pages/camera/containers/customPipeline.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { Controlled as CodeMirror } from 'react-codemirror2'; 3 | import 'codemirror/lib/codemirror.css'; 4 | import 'codemirror/theme/dracula.css'; 5 | import 'codemirror/mode/javascript/javascript'; 6 | import { useCameraDataQuery, useUpdateCameraMutation } from '../../../graphql/generated/dist'; 7 | import { Form, Header } from 'semantic-ui-react'; 8 | 9 | const CustomCommands = () => { 10 | const [state, setState] = useState(); 11 | const { data: { cameraData = {} } = {}, loading: camLoading }: any = useCameraDataQuery(); 12 | const [storeData, { loading }] = useUpdateCameraMutation(); 13 | 14 | const { customPipeline } = cameraData?.database || {}; 15 | useEffect(() => { 16 | customPipeline && setState(customPipeline); 17 | }, [customPipeline]); 18 | 19 | const onSave = (e: any) => { 20 | storeData({ variables: { properties: { customPipeline: state } } }); 21 | }; 22 | 23 | if (camLoading || loading) return
; 24 | 25 | return ( 26 | 27 |
28 |
29 | setState(value)} 32 | options={{ 33 | lineNumbers: true, 34 | autoFocus: true, 35 | lineWrapping: true, 36 | readOnly: false, 37 | mode: 'javascript', 38 | theme: 'dracula' 39 | }} 40 | /> 41 |
42 | 43 | ); 44 | }; 45 | 46 | export default CustomCommands; 47 | -------------------------------------------------------------------------------- /frontend/src/pages/backup/containers/dropzone.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react'; 2 | import { useDropzone } from 'react-dropzone'; 3 | import { Grid, Label } from 'semantic-ui-react'; 4 | import { useUploadDatabaseFileMutation } from '../../../graphql/generated/dist'; 5 | 6 | const DatabaseDropzone = () => { 7 | const [error, setError] = useState(''); 8 | const [Upload, { error: uploadError }] = useUploadDatabaseFileMutation({ 9 | errorPolicy: 'all' 10 | }); 11 | const onDrop = useCallback( 12 | (acceptedFiles: any) => { 13 | if (!acceptedFiles.length) return setError('Wrong file format, please use the exact backup file'); 14 | Upload({ variables: { file: acceptedFiles[0] } }).then(() => window.location.reload()); 15 | }, 16 | [Upload] 17 | ); 18 | 19 | const { getRootProps, getInputProps } = useDropzone({ 20 | onDrop, 21 | multiple: false, 22 | maxFiles: 1, 23 | accept: '.gz' 24 | }); 25 | 26 | return ( 27 | 28 | {uploadError &&

{uploadError.message}

} 29 | {error && ( 30 | 31 | 34 | 35 | )} 36 | 37 |
38 | 39 |
40 |

Drop the uavcast-backup file here, or click to select file ...

41 | Only .tar.gz files are allowed. 42 |
43 |
44 |
45 |
46 | ); 47 | }; 48 | 49 | export default DatabaseDropzone; 50 | -------------------------------------------------------------------------------- /backend/src/resolvers/ModemResolver.ts: -------------------------------------------------------------------------------- 1 | // import { MyContext } from "src/graphql-types/MyContext"; 2 | import { getModemRepository } from '../entity/Modem'; 3 | import { Args, Query, Mutation, Resolver } from 'type-graphql'; 4 | import { ModemResponse, NicResponse } from '../graphql-response-types/ModemResponse'; 5 | import { ModemInput } from '../graphql-input-types/ModemInput'; 6 | import os from 'os'; 7 | 8 | @Resolver() 9 | export class ModemResolver { 10 | @Mutation(() => ModemResponse) 11 | async storeModemValues(@Args() { properties }: ModemInput): Promise { 12 | const modem = await getModemRepository().findOne(1); 13 | if (!modem) return await getModemRepository().save(properties); 14 | 15 | await getModemRepository().update(1, properties); 16 | return true; 17 | } 18 | 19 | @Query(() => ModemResponse) 20 | // @UseMiddleware(isAuth) 21 | async modemData(): Promise { 22 | const message = await getModemRepository().findOne(1); 23 | return { message }; 24 | } 25 | 26 | @Query(() => NicResponse) 27 | // @UseMiddleware(isAuth) 28 | async nic(): Promise { 29 | // we only want to get the modem Interfaces, usually eth0,eth1, wwan0 ect 30 | // regex to remove unwanted nics 31 | const regEx = /^([zt]|[lo]|[wlan])/; 32 | 33 | //Get all nics 34 | const allNics = Object.keys(os.networkInterfaces()); 35 | 36 | // Filter result based on the regex 37 | const filteredNics = allNics.filter((n) => !n.match(regEx)); 38 | 39 | // Notify user that no devices was found! 40 | if (!filteredNics.length) return { interfaces: ['No Device Found!'] }; 41 | 42 | // Return all devices as array 43 | return { interfaces: filteredNics.filter((n) => !n.match(regEx)) }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /frontend/src/pages/logviewer/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Menu, Segment } from 'semantic-ui-react'; 3 | import DockerLog from './containers/dockerLog'; 4 | import ServerLog from './containers/systemLog'; 5 | 6 | import UlogViewer from './containers/ulog'; 7 | 8 | const Logviewer = () => { 9 | const [activeItem, setActiveItem] = useState({ name: 'Flight Log' }); 10 | const handleClick = (e: any, { name }: any) => setActiveItem({ name }); 11 | 12 | return ( 13 | <> 14 | 15 | 22 | 29 | 36 | 37 | 38 | 39 | 40 | 41 | {activeItem.name === 'Flight Log' && } 42 | {activeItem.name === 'System Log' && } 43 | {activeItem.name === 'Docker Log' && } 44 | 45 | 46 | ); 47 | }; 48 | 49 | export default Logviewer; 50 | -------------------------------------------------------------------------------- /backend/src/graphql-input-types/CameraInput.ts: -------------------------------------------------------------------------------- 1 | import { ArgsType, InputType, Field } from 'type-graphql'; 2 | 3 | @InputType() 4 | export class CameraProperties { 5 | @Field({ nullable: true }) 6 | controller: string; 7 | 8 | @Field({ nullable: true }) 9 | protocol: string; 10 | 11 | @Field({ nullable: true }) 12 | key: string; 13 | 14 | @Field({ nullable: true }) 15 | name: string; 16 | 17 | @Field({ nullable: true }) 18 | path: string; 19 | 20 | @Field({ nullable: true }) 21 | resolution: string; 22 | 23 | @Field({ nullable: true }) 24 | enableCamera: boolean; 25 | 26 | @Field({ nullable: true }) 27 | customPipeline: string; 28 | 29 | @Field({ nullable: true }) 30 | customAddress: string; 31 | 32 | @Field({ nullable: true }) 33 | framesPrSecond: number; 34 | 35 | @Field({ nullable: true }) 36 | bitratePrSecond: number; 37 | 38 | @Field({ nullable: true }) 39 | contrast: number; 40 | 41 | @Field({ nullable: true }) 42 | rotation: number; 43 | 44 | @Field({ nullable: true }) 45 | brightness: number; 46 | 47 | @Field({ nullable: true }) 48 | whiteBalance: string; 49 | 50 | @Field({ nullable: true }) 51 | flipCamera: string; 52 | 53 | @Field({ nullable: true }) 54 | format: string; 55 | } 56 | 57 | @ArgsType() 58 | @InputType() 59 | export class CameraInput { 60 | @Field({ nullable: true }) 61 | properties: CameraProperties; 62 | } 63 | 64 | @InputType() 65 | export class CameraActionProperties { 66 | @Field({ nullable: true }) 67 | playStream: boolean; 68 | 69 | @Field({ nullable: true }) 70 | stopStream: boolean; 71 | } 72 | 73 | @ArgsType() 74 | @InputType() 75 | export class CameraActionInput { 76 | @Field({ nullable: true }) 77 | properties: CameraActionProperties; 78 | } 79 | -------------------------------------------------------------------------------- /frontend/src/pages/map/components/video/index.js: -------------------------------------------------------------------------------- 1 | // import React, { useState } from 'react'; 2 | // import { connect } from 'react-redux'; 3 | // import { Player } from '../../../camera/components/WSAvcPlayer'; 4 | // import { ResizableBox } from 'react-resizable'; 5 | // import Draggable from 'react-draggable'; 6 | // import 'react-resizable/css/styles.css'; 7 | 8 | // // resize video initial state 9 | // const initialState = { width: 264, height: 148 }; 10 | 11 | // const MavVideo = (props) => { 12 | // const [dragPos, setDragPos] = useState({ x: 0, y: 0 }); 13 | // const [resize, setResize] = useState(initialState); 14 | 15 | // return ( 16 | // setDragPos({ x: d.x, y: d.y })} allowAnyClick handle="strong"> 17 | //
18 | // setResize({ width: size.width, height: size.height })} 26 | // > 27 | // setResize(initialState)} 31 | // setDragPos={() => setDragPos({ x: 0, y: 0 })} 32 | // /> 33 | // 34 | //
35 | //
36 | // ); 37 | // }; 38 | 39 | // const mapStateToProps = (state) => { 40 | // return { 41 | // video_fetching: state.Server.video_fetching, 42 | // }; 43 | // }; 44 | // export default connect(mapStateToProps)(MavVideo); 45 | -------------------------------------------------------------------------------- /frontend/src/pages/vpn/components/help.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Card, Grid, List } from 'semantic-ui-react'; 3 | 4 | const VpnHelp = () => { 5 | const { t } = useTranslation(); 6 | return ( 7 | 8 | 9 | 10 |
11 | 12 | {/* */} 13 | 14 | 15 | 16 | 17 | 18 | 19 | {/* 20 |
21 | */} 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | {/* Semantic-Org/Semantic-UI-Docs */} 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | export default VpnHelp; 43 | -------------------------------------------------------------------------------- /frontend/src/pages/modem/components/help.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Card, Grid, List } from 'semantic-ui-react'; 3 | 4 | const ModemHelp = () => { 5 | const { t } = useTranslation(); 6 | return ( 7 | 8 | 9 | 10 |
11 | 12 | {/* */} 13 | 14 | 15 | 16 | 17 | 18 | 19 | {/* 20 |
21 | */} 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | {/* Semantic-Org/Semantic-UI-Docs */} 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | export default ModemHelp; 43 | -------------------------------------------------------------------------------- /backend/src/mavlink/v2.0/processor.js: -------------------------------------------------------------------------------- 1 | const { mavlink10: mavlink10C, MAVLink10Processor: MAVLink10ProcessorC } = require('./dialects/mavlink_common_v1.js'); 2 | const { mavlink20: mavlink20C, MAVLink20Processor: MAVLink20ProcessorC } = require('./dialects/mavlink_common_v2.js'); 3 | const { mavlink10: mavlink10A, MAVLink10Processor: MAVLink10ProcessorA } = require('./dialects/mavlink_ardupilot_v1.js'); 4 | const { mavlink20: mavlink20A, MAVLink20Processor: MAVLink20ProcessorA } = require('./dialects/mavlink_ardupilot_v2.js'); 5 | 6 | class processor { 7 | constructor(dialect, version) { 8 | this.dialect = dialect; 9 | this.version = version; 10 | this.mav = null; 11 | this.mavmsg = null; 12 | 13 | // each udp output has a mavlink processor 14 | // this ensures non-fragmented mavlink packets from the clients 15 | switch (true) { 16 | case this.version === 1 && this.dialect === 'common': 17 | this.mav = new MAVLink10ProcessorC(null, 255, 0); 18 | this.mavmsg = mavlink10C; 19 | break; 20 | case this.version === 2 && this.dialect === 'common': 21 | this.mav = new MAVLink20ProcessorC(null, 255, 0); 22 | this.mavmsg = mavlink20C; 23 | break; 24 | case this.version === 1 && this.dialect === 'ardupilot': 25 | this.mav = new MAVLink10ProcessorA(null, 255, 0); 26 | this.mavmsg = mavlink10A; 27 | break; 28 | case this.version === 2 && this.dialect === 'ardupilot': 29 | this.mav = new MAVLink20ProcessorA(null, 255, 0); 30 | this.mavmsg = mavlink20A; 31 | break; 32 | default: 33 | console.log('Error - no valid MAVLink version or dialect'); 34 | break; 35 | } 36 | } 37 | } 38 | 39 | // export default processor; 40 | module.exports = processor; 41 | -------------------------------------------------------------------------------- /frontend/src/components/serverHasDisconnected.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from 'react'; 2 | import Spinner from './spinner'; 3 | import { wsClient } from '../utils/apolloClient'; 4 | import TimeAgo from 'react-timeago'; 5 | 6 | let disconnectTimeout: any; 7 | 8 | const ServerDisconnected = () => { 9 | const [disconnected, setDisconnected] = useState(false); 10 | const date = new Date(); 11 | 12 | const statusHandel = useCallback( 13 | (status: any) => { 14 | // let disconnectTimeout: ReturnType; 15 | if (status) { 16 | if (disconnectTimeout) return; 17 | 18 | return (disconnectTimeout = setTimeout(() => { 19 | setDisconnected(status); 20 | }, 10000)); 21 | } 22 | 23 | clearTimeout(disconnectTimeout); 24 | disconnectTimeout = 0; 25 | setDisconnected(status); 26 | }, 27 | [setDisconnected] 28 | ); 29 | 30 | useEffect(() => { 31 | wsClient.onDisconnected(() => { 32 | // eslint-disable-next-line no-console 33 | console.log('disconnected from server'); 34 | 35 | statusHandel(true); 36 | }); 37 | 38 | wsClient.onReconnected(() => { 39 | // eslint-disable-next-line no-console 40 | console.log('connected to server'); 41 | statusHandel(false); 42 | }); 43 | 44 | return () => { 45 | wsClient.unsubscribeAll(); 46 | }; 47 | }, [statusHandel]); 48 | 49 | return ( 50 | 51 | {disconnected && ( 52 | 53 |
SERVER DISCONNECTED
54 |
55 | 56 |
57 |
58 | )} 59 |
60 | ); 61 | }; 62 | export default ServerDisconnected; 63 | -------------------------------------------------------------------------------- /frontend/src/pages/camera/components/help.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Card, Grid, List } from 'semantic-ui-react'; 3 | 4 | const VideoHelp = () => { 5 | const { t } = useTranslation(); 6 | return ( 7 | 8 | 9 | 10 |
11 | 12 | {/* */} 13 | 14 | 15 | 16 | 17 | 18 | 19 | {/* 20 |
21 | */} 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | {/* Semantic-Org/Semantic-UI-Docs */} 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | export default VideoHelp; 43 | -------------------------------------------------------------------------------- /frontend/src/pages/flightController/containers/baudRate.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Dropdown, Grid, Header } from 'semantic-ui-react'; 3 | import { useFlightControllerQuery, useUpdateFlightControllerMutation } from '../../../graphql/generated/dist'; 4 | 5 | const BaudRateOpt = [ 6 | { key: '9600', value: '9600', text: '9600' }, 7 | { key: '57600', value: '57600', text: '57600' }, 8 | { key: '115200', value: '115200', text: '115200' }, 9 | { key: 'custom', value: 'custom', text: 'Custom' } 10 | ]; 11 | 12 | const FcBaudRate = (props: any) => { 13 | const { data: { flightController = {} } = {}, loading: fcLoading }: any = useFlightControllerQuery(); 14 | const [storeData, { loading: storeDataLoading }] = useUpdateFlightControllerMutation(); 15 | 16 | const dropdownHandler = (e: React.SyntheticEvent, data: any) => { 17 | storeData({ variables: { fc: { baudRate: data.value } } }); 18 | }; 19 | 20 | const { baudRate } = flightController?.data || {}; 21 | return ( 22 | 23 | 24 |
25 | 26 | 27 | 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default FcBaudRate; 47 | -------------------------------------------------------------------------------- /frontend/src/pages/flightController/containers/connectionType.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Dropdown, Grid, Header } from 'semantic-ui-react'; 3 | import { useFlightControllerQuery, useUpdateFlightControllerMutation } from '../../../graphql/generated/dist'; 4 | 5 | const connectionOpt = [ 6 | { key: 'gpio', value: 'gpio', text: 'GPIO TX / RX pins' }, 7 | { key: 'usb', value: 'usb', text: 'USB' } 8 | ]; 9 | 10 | const FcConnectionType = () => { 11 | const { data: { flightController = {} } = {}, loading: fcLoading }: any = useFlightControllerQuery(); 12 | const [storeData, { loading: storeDataLoading }] = useUpdateFlightControllerMutation(); 13 | 14 | const dropdownHandler = (e: React.SyntheticEvent, data: any) => { 15 | storeData({ 16 | variables: { fc: { connectionType: data.value, internalAddress: data.value === 'usb' ? '/dev/ttyACM0' : '/dev/ttyAMA0' } } 17 | }); 18 | }; 19 | 20 | const { connectionType } = flightController?.data || {}; 21 | 22 | return ( 23 | 24 | 25 |
26 | 27 | 28 | 42 | 43 | 44 | ); 45 | }; 46 | 47 | export default FcConnectionType; 48 | --------------------------------------------------------------------------------