From 59939b67aa1b18b54f3be852cfc5ff4484de877d Mon Sep 17 00:00:00 2001 From: Som Shrivastava Date: Mon, 28 Oct 2024 00:39:59 -0400 Subject: [PATCH 1/5] tried recloning repo because of push error --- .DS_Store | Bin 0 -> 8196 bytes .../Argos/Components => .github}/.DS_Store | Bin 6148 -> 6148 bytes ArgosIOS/.DS_Store | Bin 6148 -> 6148 bytes ArgosIOS/Argos/.DS_Store | Bin 6148 -> 8196 bytes README.md | 47 +----- angular-client/src/app/app.module.ts | 2 + .../argos-button/argos-button.component.css | 12 ++ .../motor-info/motor-info.component.css | 6 + .../raspberry-pi-desktop.component.css | 6 + .../toast-button/toast-button.component.css | 19 +++ .../toast-button/toast-button.component.html | 3 + .../toast-button/toast-button.component.ts | 35 +++++ .../typography/typography.component.css | 8 ++ .../typography/typography.component.html | 2 +- .../typography/typography.component.ts | 3 +- .../graph-header/graph-header.component.html | 2 +- .../graph-header/graph-header.component.ts | 2 + .../graph-page/graph-page.component.html | 2 +- .../pages/graph-page/graph-page.component.ts | 75 ++++++++-- .../graph-sidebar-desktop.component.html | 5 +- .../graph-sidebar-desktop.component.ts | 13 +- .../graph-sidebar.component.html | 2 +- .../graph-sidebar/graph-sidebar.component.ts | 2 + .../sidebar-card/sidebar-card.component.css | 4 +- .../sidebar-card/sidebar-card.component.html | 8 +- .../sidebar-card/sidebar-card.component.ts | 23 +-- .../landing-page/landing-page.component.html | 2 +- argos.sh | 1 + compose/README.md | 63 ++++++++ compose/compose.brick.yml | 6 + compose/compose.calypso.yml | 12 ++ compose/compose.client-dev.yml | 17 +++ compose/compose.router.yml | 19 +++ compose/compose.scylla-dev.yml | 19 +++ compose/compose.tpu.yml | 9 ++ compose/compose.yml | 49 +++++++ scylla-server-typescript/.DS_Store | Bin 0 -> 6148 bytes scylla-server-typescript/src/.DS_Store | Bin 0 -> 6148 bytes scylla-server/.DS_Store | Bin 6148 -> 6148 bytes scylla-server/Cargo.lock | 7 +- scylla-server/Cargo.toml | 2 + scylla-server/prisma/seed.rs | 45 +++--- .../src/controllers/run_controller.rs | 26 ++-- scylla-server/src/lib.rs | 5 + scylla-server/src/main.rs | 21 ++- scylla-server/src/processors/db_handler.rs | 56 +++----- scylla-server/src/processors/mock_data.rs | 15 +- .../src/processors/mock_processor.rs | 62 +++----- scylla-server/src/processors/mod.rs | 17 ++- .../src/processors/mqtt_processor.rs | 136 +++++++++--------- scylla-server/src/proto/serverdata.proto | 10 +- scylla-server/src/services/data_service.rs | 21 +-- scylla-server/src/services/run_service.rs | 28 ++-- .../src/transformers/data_transformer.rs | 37 ++++- .../src/transformers/run_transformer.rs | 11 +- scylla-server/tests/data_service_test.rs | 29 ++-- scylla-server/tests/data_type_service_test.rs | 2 +- scylla-server/tests/driver_service_test.rs | 4 +- scylla-server/tests/location_service_test.rs | 4 +- scylla-server/tests/run_service_test.rs | 3 +- scylla-server/tests/system_service_test.rs | 9 +- siren-base/compose.siren.yml | 2 +- 62 files changed, 657 insertions(+), 373 deletions(-) create mode 100644 .DS_Store rename {ArgosIOS/Argos/Components => .github}/.DS_Store (92%) create mode 100644 angular-client/src/components/toast-button/toast-button.component.css create mode 100644 angular-client/src/components/toast-button/toast-button.component.html create mode 100644 angular-client/src/components/toast-button/toast-button.component.ts create mode 100644 angular-client/src/components/typography/typography.component.css create mode 100644 compose/README.md create mode 100644 compose/compose.brick.yml create mode 100644 compose/compose.calypso.yml create mode 100644 compose/compose.client-dev.yml create mode 100644 compose/compose.router.yml create mode 100644 compose/compose.scylla-dev.yml create mode 100644 compose/compose.tpu.yml create mode 100644 compose/compose.yml create mode 100644 scylla-server-typescript/.DS_Store create mode 100644 scylla-server-typescript/src/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1cbe64048ab0cfa2bac92a91ada9d1002704b7e8 GIT binary patch literal 8196 zcmeHMJ#Q015Ph2%V(}3rh$2!XbVRg9q@u(zMuG+kcd5X!ag1ziId)JwcN8=!fC3?r z3Lz>eDQKxFkSO^JP$49cc(c29=6pUUfP@gXt z;W&peP1dC++Kit$M`>t}Ms2mU(Q396nqeX^5Euvy1O@^Ffq{R80Y0;(WmBH}ZZ(X7 zfxy62G9c$e#4a-LW#37Aba1dG0A+^Lw()vFE+``}^IrCyw7~CBYv8# zeI)5x8V}s5A%3>8g$=Y&K{bwN=B>x0G&G*Y+u4HWWW-@OgL$fQHkK=_*S{0pM176*(z79V2iWX0d03lR9QF5v#gl!F=xbzk3)gT1r5Llz zPgln2XuNlJ^DiH|{g&{h*`1^p!*%AB;F`0UZlZ0GjpREzoz&2n&VDNlFpc=ph|;xU zc(5yYPTIY|QPg)ft}HO|K?kGg(ePVlcWAI?JIH}g;S1piVx zen!*94wgxVTV_^elq#owr0MDSYHuzN_S{&XI);OEV>od^aE3Nr49vQ4m!$r`lK%dGx=0Dp z1_q{#0h1|~iUkJkYwJY#Qfo)-H`%3$Uf)S;!a-DI-j?H(!#@mp9r?AqmwhL>!uHo+ X1jz5`F~3Fhvok#ZZEC{sA2aY1n&6{4 literal 0 HcmV?d00001 diff --git a/ArgosIOS/Argos/Components/.DS_Store b/.github/.DS_Store similarity index 92% rename from ArgosIOS/Argos/Components/.DS_Store rename to .github/.DS_Store index deeeb35dfd3f9b9db301be091f29319d76e17dc6..66f2f49d21243a88a889b149dbcd2eb6f4b7317d 100644 GIT binary patch literal 6148 zcmeHKO-sW-5Pe&FND-vR9u#u$F1>n6OL7ntgzDK+ixq51QKZ-W2!Dfr!n?oAH#P0JDel9S`4|33Nb?T#7$ zI%fX3o>xq-g#}7H;|UL#a<;(BX7;D&%%sL^J?FRVqp+N7Jy)6j>{*i^H+OLcoB?NG zFBo9W7D)~jeRKw#0cT*zfP5cPs$gbVDaKC+hu8uTn>0t^TzUzKNewf@N|7s+prJ$! z4c1}=4W~Udzs#^w)NllAK7#EWtV0QLcRoLj?g*KpkIsNIu*txI-nOOw7vJ~)n?Zi( z3^)V-ih3yt$EiIpahSv4f5wcaEc#u#I7!dpt^dHg9vyDYm34vGQf{+llL(xO delta 88 zcmZoMXffEJ!Nl}p)nqLuDK>?OlFyy;lf9T^V9XpQIi?K^llL(xO<3Qq bV~#)p6`>TDq@4UD1_lOXVWrJJ%sWH?GK(Hd diff --git a/ArgosIOS/Argos/.DS_Store b/ArgosIOS/Argos/.DS_Store index 160d696ec585f5fac2de562483e321c53541c5e2..efb8e5eea69c4867d7114caf5b41d827132cfbf1 100644 GIT binary patch literal 8196 zcmeHMv2GJV5S@j@SV1UA2q~fAN*WNUNpVSRNP$o!1EQdS#FmM$eO3|(ks{=eh>(yd z=#VJ+0jN^Y@eh=I03~mBH+Qq=JD){?h%#&Kj<}o{yomLlsa3Q~^~$6;K8K2L*6ubE%h{`)*WQ zRX`OulM3+jAwp#vdpmO0K046Y6aaPx-L~L6@&M}-c^i8>a@M-ynl5`V-qrXN!#H=0 zM+}FJy&XC0+{rk1GCs2L8H%yd;TKsrnb=uvRRL9CTmjDA=V=9b`!02Y{5|dIb(dOO zNq^{^&C_a+e$r@c_L5B?>mNQf)^{EpuT10B{tK_+3(4*_lboY9N~ljmv;)j)!1D@c zap!9pPI6y-7k@tLTJD9{>?axHDr~^hL zz(R~l4u?1Io#+%GG6uznApW%t1i<~Xffek~Yph*|_8|`MKi+kA?}ckg%heu|=l3Tm z&x$y5O{ez?=Hm|EIcm9Gh~3D~1fEN7eXsKUT!@q0_uDD=5^Sb+6*lAj|nMm{R-&8A!?*9VR;q z*|Pn}Klg!Cd~%zR9E`b2NOf|ti0EV!5fiqm%<~}GJP|bnQ)==S5f2UJT9C{!N1(l| z3?U4a34>cOxkd1$rWrAqQ14M8)JKqRJaf6&M$@ rb8rYUgZu>aJU5VV1qI5+!tczJ`DHvoLBzlW2`*3wGHj0LnZpbK;5b## diff --git a/README.md b/README.md index 2f44e247..2e5abf21 100644 --- a/README.md +++ b/README.md @@ -15,52 +15,7 @@ Once you've sucessfully setup Scylla and the Client, you can either run them sep ## Production -### Quick start shortcuts - -The `argos.sh` can be used on POSIX compliant OSes to quickly get up and running without worrying about profiles. Its syntax is `./argos.sh ` where profile could be router, tpu, scylla-dev, or client-dev and the cmd is the normal argument passed into docker compose, such as `up -d` to start the process and fork to background. **Make sure to `down` with the same profile that you used `up` with!** - -### Customizing and more info - -The base docker compose (`compose.yml`) contains some important features to note. However, it is useless standalone. Please read the profile customization selection below before using the base compose. -- It matches the number of CPUs as the router to roughly simulate router CPU (your CPU is still faster) -- It persists the database between `down` commands via a volume called `argos_db-data`. Delete it with `docker volume rm argos_db-data` to start with a new database next `up`. -- It weighs the CPU usage of siren higher, so it is prioritized in CPU starvation scenarios. -- It limits memory according to the capacity of the router. - - -#### Customizing runtime profiles of the project via docker compose - -This project uses docker compose overrides to secify configurations. Therefore there are multiple "profiles" to choose from when running in production, and there are some profiles for development testing. Also, there are fragment files for siren and client in `siren-base` and `angular-client` respectively, as they are services only used in certain cases. These profiles are specified via the command line on top of the base `compose.yml` file as follows. - -``` -docker compose -f compose.yml -f -``` - -Additionally if you need to create your own options, you can create a `compose.override.yml` file in this directory and specify what settings should be changed, which is ignored by git. If you think the override would become useful, document it here and name it `compose..yml`. Here is the current list of overrides, designed so only one is used at any given time: - -- `scylla-dev`*: Testing the client and interactions of scylla (scylla prod, siren local, client pt local) -- `client-dev`*: Testing the client development using the scylla mock data mode (scylla mock, client pt local) -- `router`: For production deployment to the base gateway node (scylla prod, siren local, client pt 192.168.100.1) -- `tpu`: Production deployment to the TPU on the car (no client, no siren, scylla pt siren external) - -***Note that since client settings are changed via rebuild, overrides with a * must be rebuilt via `docker compose -f compose.yml -f compose..yml build client`. Further, a build should be done when reverting to profiles without stars. ** - -Examples: - -Router deploy and send to background: `docker compose -f compose.yml -f compose.router.yml up -d` - -### Build and deploy - -Using the above profiles, one can `build` the app. Then, with correct permissions, you can `push` the app then `pull` it elsewhere for usage. Note that you must `push` and `pull` on the same architecture, so you cannot use for example a dell laptop to build for the router! To get `push` permissions, create a PAT [here](https://github.com/settings/tokens/new?scopes=write:packages) and copy the token into this command: - -``` -sudo docker login ghcr.io -u -p -``` - -Now you can update the image on a remote server. Note to save time you can just specify which service to upload, like `scylla-server` or `client`. -``` -sudo docker compose -f compose.yml -f compose.router.yml build && sudo docker compose -f compose.yml -f compose.router.yml push -``` +Please see [Compose Profiles](./compose/README.md) to get started with building and testing using compose. ## Codegen Protobuf Types (client only) diff --git a/angular-client/src/app/app.module.ts b/angular-client/src/app/app.module.ts index fd70e4d8..4f9b4447 100644 --- a/angular-client/src/app/app.module.ts +++ b/angular-client/src/app/app.module.ts @@ -94,6 +94,7 @@ import PackVoltageDisplay from 'src/pages/charging-page/components/pack-voltage/ import ChargingStatusComponent from 'src/pages/charging-page/components/charging-state/charging-status.component'; import { BatteryPercentageComponent } from 'src/pages/charging-page/components/battery-percentage/battery-percentage.component'; import { BatteryInfoDisplay } from 'src/pages/charging-page/components/battery-info-display/battery-info-display'; +import { ToastButtonComponent } from 'src/components/toast-button/toast-button.component'; import StartingSocTimer from 'src/pages/charging-page/components/starting-soc/starting-soc-timer.component'; import CurrentTotalTimer from 'src/components/current-total-timer/current-total-timer.component'; import BalancingStatus from 'src/pages/charging-page/components/balancing-status/balancing-status.component'; @@ -182,6 +183,7 @@ import CellTempMobile from 'src/pages/charging-page/components/cell-temp/cell-te HighLowCellGraph, PackVoltageGraph, PackVoltageDisplay, + ToastButtonComponent, ChargingStatusComponent, StartingSocTimer, CurrentTotalTimer, diff --git a/angular-client/src/components/argos-button/argos-button.component.css b/angular-client/src/components/argos-button/argos-button.component.css index 98febc89..2975c4cb 100644 --- a/angular-client/src/components/argos-button/argos-button.component.css +++ b/angular-client/src/components/argos-button/argos-button.component.css @@ -10,4 +10,16 @@ cursor: pointer; display: inline-block; font-weight: bold; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.btn:hover { + transition: ease; + transition-duration: 0.2s; + background-color: #f04346b2; } diff --git a/angular-client/src/components/motor-info/motor-info.component.css b/angular-client/src/components/motor-info/motor-info.component.css index 41d6d579..68985ec1 100644 --- a/angular-client/src/components/motor-info/motor-info.component.css +++ b/angular-client/src/components/motor-info/motor-info.component.css @@ -17,6 +17,12 @@ font-family: 'Roboto'; font-size: 12px; color: #cacaca; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } .divider-line { diff --git a/angular-client/src/components/raspberry-pi/raspberry-pi-desktop-content/raspberry-pi-desktop.component.css b/angular-client/src/components/raspberry-pi/raspberry-pi-desktop-content/raspberry-pi-desktop.component.css index 921b4f9d..39d810ce 100644 --- a/angular-client/src/components/raspberry-pi/raspberry-pi-desktop-content/raspberry-pi-desktop.component.css +++ b/angular-client/src/components/raspberry-pi/raspberry-pi-desktop-content/raspberry-pi-desktop.component.css @@ -10,6 +10,12 @@ font-size: 18px; color: gray; padding-top: 22px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } .mat-icon { diff --git a/angular-client/src/components/toast-button/toast-button.component.css b/angular-client/src/components/toast-button/toast-button.component.css new file mode 100644 index 00000000..62f28578 --- /dev/null +++ b/angular-client/src/components/toast-button/toast-button.component.css @@ -0,0 +1,19 @@ +.btn { + background-color: #f04346; + border: none; + color: white; + border-radius: 12px; + padding: 2px 0px; + text-align: center; + text-decoration: none; + font-size: 16px; + cursor: pointer; + display: inline-block; + font-weight: bold; +} + +.btn:hover { + transition: ease; + transition-duration: 0.2s; + background-color: #f04346b2; +} diff --git a/angular-client/src/components/toast-button/toast-button.component.html b/angular-client/src/components/toast-button/toast-button.component.html new file mode 100644 index 00000000..5c1a5ce7 --- /dev/null +++ b/angular-client/src/components/toast-button/toast-button.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/angular-client/src/components/toast-button/toast-button.component.ts b/angular-client/src/components/toast-button/toast-button.component.ts new file mode 100644 index 00000000..3c3d28b2 --- /dev/null +++ b/angular-client/src/components/toast-button/toast-button.component.ts @@ -0,0 +1,35 @@ +import { Component, Input } from '@angular/core'; +import { MessageService } from 'primeng/api'; + +@Component({ + selector: 'toast-button', + templateUrl: './toast-button.component.html', + styleUrls: ['./toast-button.component.css'] +}) +export class ToastButtonComponent { + @Input() label!: string; + @Input() onClick!: () => void; + @Input() additionalStyles?: string; + style!: string; + + ngOnInit(): void { + this.style = 'width: 140px; height: 45px; '; + + if (this.additionalStyles) { + this.style += this.additionalStyles; + } + } + + constructor(private messageService: MessageService) {} + + handleClick() { + this.onClick(); + setTimeout(() => { + this.messageService.add({ + severity: 'success', + summary: 'Success!', + detail: 'Your new run has started successfully.' + }); + }); + } +} diff --git a/angular-client/src/components/typography/typography.component.css b/angular-client/src/components/typography/typography.component.css new file mode 100644 index 00000000..2c8bc096 --- /dev/null +++ b/angular-client/src/components/typography/typography.component.css @@ -0,0 +1,8 @@ +.disable-text-selection { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} diff --git a/angular-client/src/components/typography/typography.component.html b/angular-client/src/components/typography/typography.component.html index 9e2d6cbf..00ba9a89 100644 --- a/angular-client/src/components/typography/typography.component.html +++ b/angular-client/src/components/typography/typography.component.html @@ -1 +1 @@ -

{{ content }}

+

{{ content }}

diff --git a/angular-client/src/components/typography/typography.component.ts b/angular-client/src/components/typography/typography.component.ts index 53c5e03d..c3f47a7b 100644 --- a/angular-client/src/components/typography/typography.component.ts +++ b/angular-client/src/components/typography/typography.component.ts @@ -13,7 +13,8 @@ import { StyleVariant } from 'src/utils/enumerations/style-variant'; */ @Component({ selector: 'typography', - templateUrl: './typography.component.html' + templateUrl: './typography.component.html', + styleUrls: ['./typography.component.css'] }) export default class Typography implements OnInit { @Input() variant!: StyleVariant; diff --git a/angular-client/src/pages/graph-page/graph-header/graph-header.component.html b/angular-client/src/pages/graph-page/graph-header/graph-header.component.html index 8655d6d2..c517aee0 100644 --- a/angular-client/src/pages/graph-page/graph-header/graph-header.component.html +++ b/angular-client/src/pages/graph-page/graph-header/graph-header.component.html @@ -4,7 +4,7 @@
diff --git a/angular-client/src/pages/graph-page/graph-header/graph-header.component.ts b/angular-client/src/pages/graph-page/graph-header/graph-header.component.ts index 120eecc8..99e417ca 100644 --- a/angular-client/src/pages/graph-page/graph-header/graph-header.component.ts +++ b/angular-client/src/pages/graph-page/graph-header/graph-header.component.ts @@ -1,4 +1,6 @@ import { Component, Input } from '@angular/core'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatButtonModule } from '@angular/material/button'; /** * Graph Header Component to display the graph page header. diff --git a/angular-client/src/pages/graph-page/graph-page.component.html b/angular-client/src/pages/graph-page/graph-page.component.html index c22f2630..e36470d6 100644 --- a/angular-client/src/pages/graph-page/graph-page.component.html +++ b/angular-client/src/pages/graph-page/graph-page.component.html @@ -10,7 +10,7 @@
- +
{ - console.log(data); this.selectedDataTypeValuesSubject.next(data.map((value) => ({ x: +value.time, y: +value.values[0] }))); this.currentValue.next(data.pop()); }); @@ -109,6 +110,10 @@ export default class GraphPage implements OnInit { }); } }; + + this.selectedDataType.subscribe((dataType: DataType) => { + this.dataTypeName = dataType.name; + }); } onRunSelected = (run: Run) => { @@ -121,15 +126,12 @@ export default class GraphPage implements OnInit { }; onSetRealtime = () => { - const currentRunId = this.storage.getCurrentRunId().value; - if (currentRunId) { - this.run = this.allRuns.find((run) => run.id === currentRunId); - this.realTime = true; - this.selectedDataTypeValuesSubject.next([]); - this.selectedDataTypeValuesIsLoading = false; - this.selectedDataTypeValuesIsError = false; - this.selectedDataTypeValuesError = undefined; - } + this.run = undefined; + this.realTime = true; + this.selectedDataTypeValuesSubject.next([]); + this.selectedDataTypeValuesIsLoading = false; + this.selectedDataTypeValuesIsError = false; + this.selectedDataTypeValuesError = undefined; }; /** @@ -156,4 +158,55 @@ export default class GraphPage implements OnInit { setSelectedDataType!: (dataType: DataType) => void; clearDataType!: () => void; + + @HostListener('document:keydown', ['$event']) + handleKeyboardEvent(event: KeyboardEvent) { + if (this.dataTypeName == undefined) { + this.setSelectedDataType(this.nodes?.at(0)?.dataTypes[0] as DataType); + } else { + const node = this.getNode(this.dataTypeName as string); + const nodeIndex = this.getNodeIndex(this.getNode(this.dataTypeName as string)); + const dataTypeIndex = this.getDataTypeIndex( + this.getNode(this.dataTypeName as string), + this.getDataType(this.getNode(this.dataTypeName as string), this.dataTypeName as string) + ); + if (event.key == 'ArrowDown') { + if (dataTypeIndex + 1 == node.dataTypes.length && nodeIndex + 1 == this.nodes?.length) { + this.setSelectedDataType(this.nodes?.at(0)?.dataTypes[0] as DataType); + } else if (dataTypeIndex + 1 == node.dataTypes.length) { + this.setSelectedDataType(this.nodes?.at(nodeIndex + 1)?.dataTypes[0] as DataType); + } else { + this.setSelectedDataType(node.dataTypes[dataTypeIndex + 1]); + } + } else if (event.key == 'ArrowUp') { + if (dataTypeIndex == 0 && nodeIndex == 0) { + const lastNode = this.nodes?.at(this.nodes.length - 1) as Node; + this.setSelectedDataType(lastNode.dataTypes[(lastNode.dataTypes.length as number) - 1] as DataType); + } else if (dataTypeIndex == 0) { + const lastNode = this.nodes?.at(nodeIndex - 1) as Node; + this.setSelectedDataType(lastNode.dataTypes[(lastNode.dataTypes.length as number) - 1] as DataType); + } else { + this.setSelectedDataType(node.dataTypes[dataTypeIndex - 1]); + } + } + } + } + + private getNode(name: string): Node { + return this.nodes?.filter( + (node: Node) => node.dataTypes.filter((dataType: DataType) => dataType.name == name)[0] + )[0] as Node; + } + + private getNodeIndex(node: Node): number { + return this.nodes?.indexOf(node) as number; + } + + private getDataType(node: Node, name: string) { + return node.dataTypes.filter((dataType: DataType) => dataType.name == name)[0]; + } + + private getDataTypeIndex(node: Node, dataType: DataType) { + return node.dataTypes.indexOf(dataType); + } } diff --git a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.html b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.html index d8eae964..707f204a 100644 --- a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.html +++ b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.html @@ -16,9 +16,9 @@ [title]="node.name" (click)="toggleDataTypeVisibility(node)" [dropDown]="true" - [open]="node.dataTypesAreVisible" + [open]="node.dataTypesAreVisible ? true : isNodeOpen(node)" /> -
+
diff --git a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.ts b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.ts index 0ba7c9da..c734c4fb 100644 --- a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.ts +++ b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.ts @@ -4,7 +4,7 @@ import { DataType, Node, NodeWithVisibilityToggle, NodeWithVisibilityToggleObser import Storage from 'src/services/storage.service'; import { decimalPipe } from 'src/utils/pipes.utils'; import { FormControl, FormGroup } from '@angular/forms'; -import { debounceTime, Observable, of, Subscription } from 'rxjs'; +import { debounceTime, Observable, of, Subject, Subscription } from 'rxjs'; /** * Sidebar component that displays the nodes and their data types. @@ -49,6 +49,7 @@ import { debounceTime, Observable, of, Subscription } from 'rxjs'; export default class GraphSidebarDesktop implements OnInit { @Input() nodes!: Node[]; @Input() selectDataType!: (dataType: DataType) => void; + @Input() selectedDataType: Subject = new Subject(); nodesWithVisibilityToggle!: Observable; filterForm: FormGroup = new FormGroup({ @@ -58,6 +59,7 @@ export default class GraphSidebarDesktop implements OnInit { searchFilter: string = ''; dataValuesMap: Map = new Map(); + dataTypeName?: string; constructor(private storage: Storage) {} /** @@ -91,6 +93,15 @@ export default class GraphSidebarDesktop implements OnInit { }); } } + + this.selectedDataType.subscribe((dataType: DataType) => { + this.dataTypeName = dataType.name; + console.log(this.dataTypeName); + }); + } + + isNodeOpen(node: NodeWithVisibilityToggle) { + return node.dataTypes.filter((dataType: DataType) => dataType.name == this.dataTypeName).length > 0; } ngOnDestroy(): void { diff --git a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar.component.html b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar.component.html index a569fedf..8e285407 100644 --- a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar.component.html +++ b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar.component.html @@ -2,5 +2,5 @@
- + diff --git a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar.component.ts b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar.component.ts index 9f75cfa0..db91e637 100644 --- a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar.component.ts +++ b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar.component.ts @@ -1,4 +1,5 @@ import { Component, HostListener, Input, OnInit } from '@angular/core'; +import { Subject } from 'rxjs'; import { DataType, Node, Run } from 'src/utils/types.utils'; /** @@ -14,6 +15,7 @@ import { DataType, Node, Run } from 'src/utils/types.utils'; export default class GraphSidebar implements OnInit { @Input() nodes!: Node[]; @Input() selectDataType!: (dataType: DataType) => void; + @Input() selectedDataType: Subject = new Subject(); @Input() onRunSelected!: (run: Run) => void; isMobile!: boolean; diff --git a/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.css b/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.css index 881fccdf..ad7e05e0 100644 --- a/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.css +++ b/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.css @@ -2,7 +2,7 @@ background-color: #101010; border-radius: 5px; margin: 8px; - height: 40px; + height: 25px; position: relative; text-align: center; transition: background-color 0.3s; @@ -12,7 +12,7 @@ cursor: pointer; } - &.selected { + &.choosed { background-color: #4f4f4f; } } diff --git a/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.html b/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.html index cd97cdb6..e2d767b0 100644 --- a/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.html +++ b/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.html @@ -1,15 +1,15 @@ -
+
- +
= new Subject(); iconId!: string; + selected?: boolean; ngOnInit(): void { this.iconId = `${this.title}-icon`; - } - /** - * Runs animation when card is selected - */ - selectCard() { - const card = document.getElementById(this.title); - if (card) { - card.classList.add('selected'); - setTimeout(() => { - card.classList.remove('selected'); - }, 250); - } - const dropDown = document.getElementById(this.iconId); - if (dropDown) { - dropDown.classList.toggle('selected'); - } + this.selectedDataType.subscribe((dataType: DataType) => { + this.selected = this.title == dataType.name; + }); } } diff --git a/angular-client/src/pages/landing-page/landing-page.component.html b/angular-client/src/pages/landing-page/landing-page.component.html index fdddfc5d..aa985013 100644 --- a/angular-client/src/pages/landing-page/landing-page.component.html +++ b/angular-client/src/pages/landing-page/landing-page.component.html @@ -14,7 +14,7 @@ - + diff --git a/argos.sh b/argos.sh index d58f5591..6ec3ad70 100755 --- a/argos.sh +++ b/argos.sh @@ -2,4 +2,5 @@ profile=$1 shift 1 +cd ./compose docker compose -f compose.yml -f "compose.$profile.yml" "$@" diff --git a/compose/README.md b/compose/README.md new file mode 100644 index 00000000..56884871 --- /dev/null +++ b/compose/README.md @@ -0,0 +1,63 @@ +# Argos Compose Profiles System + +## Quick start + +### argos.sh + +The `argos.sh` (from the root directory) can be used on `sh` OSes to quickly get up and running. +Its syntax is +``` +./argos.sh +``` + where profile could be one of the below profiles (see the table), or client-dev and the cmd is the normal argument passed into docker compose, such as `up -d` to start the process and fork to background. **Make sure to `down` with the same profile that you used `up` with!** + +### Profiles & your own setup + +Profiles: +| Name | Usecase | Scylla | Siren | Client | Database | +| ------------ | ------------------------------------------------------- | ----------------------------------------- | ----------------------- | ----------------------------------------- | -------- | +| `brick` | Device connected to base station to collect data | No rate limit | On base station | External (`router`) | Active | +| `router` | The base station collecting on a rate limit and hosting | Rate limited | Active, bridging to car | Active | Active | +| `tpu` | The on-car data collection node | Rate limited | External (TPU OS) | None | Active | +| `client-dev` | To develop the client with a working backend | Rate limited | Active | None (run your own pointing to localhost) | Active | +| `scylla-dev` | To develop scylla with a working frontend | None (run your own pointing to localhost) | Active | Active localhost (**MUST BUILD FIRST**) | Active | + + +The base docker compose (`compose.yml`) contains some important features to note. However, it is useless standalone. Please read the profile customization selection below before using the base compose. +- It persists the database between `down` commands via a volume called `argos_db-data`. Delete it with `docker volume rm argos_db-data` to start with a new database next `up`. +- It weighs the CPU usage of siren higher, so it is prioritized in CPU starvation scenarios. + + +*These profiles are non-exhuastive, there are plently of use cases these profiles do not cover. In that case, you can write your own profile to cover it.* + +#### Examples with and without profiles + +- To send some simulated data to a client you are running with `npm`: `./argos.sh client-dev up` +- To start a mqtt server with simulated data you want to send to an instance of scylla running through `cargo`: `docker compose -f ./siren-base/compose.siren.yml -f ./compose/compose.calypso.yml up` + + +#### Customizing runtime profiles of the project via docker compose + +This project uses docker compose overrides to secify configurations. Therefore there are multiple "profiles" to choose from when running in production, and there are some profiles for development testing. Also, there are fragment files for siren and client in `siren-base` and `angular-client` respectively, as they are services only used in certain cases. These profiles are specified via the command line on top of the base `compose.yml` file as follows. + +``` +docker compose -f compose.yml -f +``` + +Additionally if you need to create your own options, you can create a `compose.override.yml` file in this directory and specify what settings should be changed, which is ignored by git. If you think the override would become useful, document it here and name it `compose..yml`. + +Examples: + +Router deploy and send to background: `docker compose -f compose.yml -f compose.router.yml up -d` + +#### Build and deploy + +Using the above profiles, one can `build` the app. Then, with correct permissions, you can `push` the app then `pull` it elsewhere for usage. Note that you must `push` and `pull` on the same architecture, so you cannot use for example a dell laptop to build for the router! To get `push` permissions, create a PAT [here](https://github.com/settings/tokens/new?scopes=write:packages) and copy the token into this command: + +``` +sudo docker login ghcr.io -u -p +``` + +Now you can update the image on a remote server. Note to save time you can just specify which service to upload, like `scylla-server` or `client`. +``` +sudo docker compose -f compose.yml -f compose.router.yml build && sudo docker compose -f compose.yml -f compose.router.yml push \ No newline at end of file diff --git a/compose/compose.brick.yml b/compose/compose.brick.yml new file mode 100644 index 00000000..007ef167 --- /dev/null +++ b/compose/compose.brick.yml @@ -0,0 +1,6 @@ +services: + scylla-server: + environment: + - SCYLLA_SIREN_HOST_URL=192.168.100.11:1883 + - SCYLLA_RATE_LIMIT_MODE=none + diff --git a/compose/compose.calypso.yml b/compose/compose.calypso.yml new file mode 100644 index 00000000..dfb5a911 --- /dev/null +++ b/compose/compose.calypso.yml @@ -0,0 +1,12 @@ +services: + calypso: + container_name: calypso + image: ghcr.io/northeastern-electric-racing/calypso:develop + restart: unless-stopped + environment: + # in prod mode + #- CALYPSO_CAN_ENCODE=false + #- CALYPSO_SOCKETCAN_IFACE=vcan0 + # in sim or prod mode + - CALYPSO_SIREN_HOST_URL=siren:1883 + \ No newline at end of file diff --git a/compose/compose.client-dev.yml b/compose/compose.client-dev.yml new file mode 100644 index 00000000..1c550727 --- /dev/null +++ b/compose/compose.client-dev.yml @@ -0,0 +1,17 @@ +services: + scylla-server: + environment: + - SCYLLA_SIREN_HOST_URL=siren:1883 + - SCYLLA_PROD=true + + siren: + extends: + file: ../siren-base/compose.siren.yml + service: siren + + calypso: + extends: + file: ./compose.calypso.yml + service: calypso + depends_on: + - siren diff --git a/compose/compose.router.yml b/compose/compose.router.yml new file mode 100644 index 00000000..4f2c0e57 --- /dev/null +++ b/compose/compose.router.yml @@ -0,0 +1,19 @@ +services: + scylla-server: + depends_on: + - siren + environment: + - SCYLLA_SIREN_HOST_URL=siren:1883 + - SCYLLA_RATE_LIMIT_MODE=static + - SCYLLA_STATIC_RATE_LIMIT_VALUE=100 + init: false + + client: + extends: + file: ../angular-client/compose.client.yml + service: client + + siren: + extends: + file: ../siren-base/compose.siren.yml + service: siren diff --git a/compose/compose.scylla-dev.yml b/compose/compose.scylla-dev.yml new file mode 100644 index 00000000..7e07f329 --- /dev/null +++ b/compose/compose.scylla-dev.yml @@ -0,0 +1,19 @@ + services: + scylla-server: # do not run scylla + entrypoint: ["echo", "DISABLED"] + restart: "no" + + + client: + extends: + file: ../angular-client/compose.client.yml + service: client + build: + context: ../angular-client + args: + BACKEND_URL: http://localhost:8000 + + siren: + extends: + file: ../siren-base/compose.siren.yml + service: siren diff --git a/compose/compose.tpu.yml b/compose/compose.tpu.yml new file mode 100644 index 00000000..88ea3216 --- /dev/null +++ b/compose/compose.tpu.yml @@ -0,0 +1,9 @@ +services: + scylla-server: + environment: + - SCYLLA_SIREN_HOST_URL=host.docker.internal:1883 + - SCYLLA_RATE_LIMIT_MODE=static + - SCYLLA_STATIC_RATE_LIMIT_VALUE=100 + extra_hosts: + - "host.docker.internal:host-gateway" # for external siren + init: false # not supported on buildroot for some reason, further investigation needed diff --git a/compose/compose.yml b/compose/compose.yml new file mode 100644 index 00000000..e425d6db --- /dev/null +++ b/compose/compose.yml @@ -0,0 +1,49 @@ +services: + odyssey-timescale: + container_name: odyssey-timescale + image: timescale/timescaledb:2.13.1-pg15 + restart: unless-stopped + environment: + POSTGRES_HOST_AUTH_METHOD: trust + ports: # needs to be published for charybdis, which runs outside of docker + - 5432:5432 + expose: + - 5432 + volumes: + - db-data:/var/lib/postgresql/data + cpu_shares: 1024 + stop_grace_period: 2m + + scylla-server: + container_name: scylla-server + image: ghcr.io/northeastern-electric-racing/argos-scylla:develop + build: + context: ../scylla-server + restart: unless-stopped + ports: + - 8000:8000 + depends_on: + - odyssey-timescale + environment: + - SOURCE_DATABASE_URL=postgresql://postgres:password@odyssey-timescale:5432/postgres + # - PROD_SIREN_HOST_URL=siren:1883 + - SCYLLA_PROD=true + #- SCYLLA_SATURATE_BATCH=false + #-SCYLLA_DATA_UPLOAD_DISABLE=false + #-SCYLLA_SIREN_HOST_URL=localhost:1883 + #-SCYLLA_BATCH_UPSERT_TIME=10 + - SCYLLA_RATE_LIMIT_MODE=static + - SCYLLA_STATIC_RATE_LIMIT_VALUE=50 + #-SCYLLA_SOCKET_DISCARD_PERCENT=0 + - RUST_LOG=warn,scylla_server=debug + cpu_shares: 1024 + stop_grace_period: 2m + stop_signal: SIGINT + init: true + + +volumes: + db-data: + + + diff --git a/scylla-server-typescript/.DS_Store b/scylla-server-typescript/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..fb25e8646efe57479fc31822b940183d9e9f745b GIT binary patch literal 6148 zcmeHKJ8r^25S@uBETwUoa<9M*Hpme@Tp$4?QhGBeyfgE5wXg8(5)qH~iy=`(L?v914l?vj^5Q+a@yGzkvc_;aZ`K=)V*V!+Zb3WR z(tNe-@+VJUnRQdwvsE)gq zw^yw~96l5X1-?^2{tpQ*m>pX~{dHi_Cjd~c@oMkZ1VipR70vDwK z59(<>!jZhU_6{e#Ho+g^pN3j3$DpkksI3?iX~lP)x+LGoXUEo1>4+;G7#9K2B`g&9 H4+TB|xzi|l literal 0 HcmV?d00001 diff --git a/scylla-server-typescript/src/.DS_Store b/scylla-server-typescript/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..16c61882c30c73b5d195302d729308def853a467 GIT binary patch literal 6148 zcmeHKJx;?g82t>TL=>S842-z|L%Xts5+qnydV$g(6+}y_{tRppXJG6B5SL&>;sWek z0FD6fv)v|6n+g_$;QJ!`x%T_wmlrv%iAdEZ?Fvzuh&&X=!WOC}!Q)&OV$JsK0+qhU zkRs~g>d{?9~j9yZ~4rA6o$=S)C9wO zY1B`KXHSQ#*zIqy2N!7-TFf?7w&{jOxH`anYSKBkUB%Du(2&bbYra32&NQ#!C#t`~ zcXY1>kU~Z6?*u+~DC2IinjKH;d%T@{>fECrC7SX$jt3C{5?1{Pz37(pnL3N)$8 z9x;?jN55}*fyLOMNhf6wAIi?G>Qhg(@6yer4|Ro0oQ?b^RvwR|8f5L-%XNV z;($2tryNj)upZX%NOo^scsSl`eUt_Y2lHZsIt7*8j&*~#;yn}{81uOS3@pY5(Swjb N0@?Y5S`U1F^X7PUTYCiY_vOL1S<hXK{bP&c<4>uoL_Z7XAer z3lZ%63BK7~<7_U$P9)60?z@|r-Ff$t+s%fEOzEPzO|(Wtc{Ijc0o?@SIJb<=dCxk~ z*g4viblTgMM$+wNvelsqr~>~@0e*KEX_FdMqdsNd-`aS*o0QA-PEvK{Mt zXLFjr=Jd|wI>GA?c=W*GoCegTTDXpEo{q;QkEM-+`|GynA?D~NKRl-8ggmOuqab-u z5!!UV3yxK`H4s_HwPn){5V-X8x86$ zy>h)p8v1WZi-w}AS-E`P8;{G{?;qF0yYJ6O%pn;}%Mm=x>Q&4<7Qmy0XCKl)ut1>r z7Dgq9+lSHfi}8}d;T3dQ-|=tj2jAcLybRT7&jdL$mhi#Q-^Li8Cx0i_{zqgP>ih(e`M3i z#16Gr1yq4hfq8ek#{2)_^z(l>NbghuRbZwRFu8K2T*4#yy*2Z2yw^r(RWvrvOC8!F k==^qUFL*2F{}rqu7V!lz_Lw?^2c|y+tPI+z0)MK&7tuJ$z5oCK literal 6148 zcmeHKy-veG47QsN;YT8DFmbO?i3bQ(c!DkrNT4*9~qFl+oc#=` z3JjsFEe3~h*q!>NigmycPOQxb>&dJg3j5Qszw_?IX+Rr|0b^j5fhasCQvXla_y4Ow z_GAng0~^Hv_p?zp#4BlS?Yx}SS_i#^iilqwa2bM$FU9bcQhWgQ0=tt5FjcGr!UC}$ N0Z)Ss#=xI4@CABTQX~KX diff --git a/scylla-server/Cargo.lock b/scylla-server/Cargo.lock index b3704400..62c335f8 100755 --- a/scylla-server/Cargo.lock +++ b/scylla-server/Cargo.lock @@ -3225,6 +3225,7 @@ version = "0.0.1" dependencies = [ "axum 0.7.5", "axum-extra", + "chrono", "clap", "console-subscriber", "prisma-client-rust", @@ -3234,6 +3235,7 @@ dependencies = [ "ringbuffer", "rumqttc", "serde", + "serde_json", "socketioxide", "tokio", "tokio-util", @@ -3311,12 +3313,13 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "indexmap 2.2.6", "itoa", + "memchr", "ryu", "serde", ] diff --git a/scylla-server/Cargo.toml b/scylla-server/Cargo.toml index 4980650d..7539bd4c 100644 --- a/scylla-server/Cargo.toml +++ b/scylla-server/Cargo.toml @@ -23,6 +23,8 @@ console-subscriber = { version = "0.3.0", optional = true } ringbuffer = "0.15.0" clap = { version = "4.5.11", features = ["derive", "env"] } axum-extra = { version = "0.9.3", features = ["query"] } +chrono = { version = "0.4.38", features = ["serde"] } +serde_json = "1.0.128" [features] top = ["dep:console-subscriber"] diff --git a/scylla-server/prisma/seed.rs b/scylla-server/prisma/seed.rs index 6ed5f8f1..3129c951 100644 --- a/scylla-server/prisma/seed.rs +++ b/scylla-server/prisma/seed.rs @@ -1,6 +1,6 @@ use std::{sync::Arc, time::Duration}; -use prisma_client_rust::{chrono, QueryError}; +use prisma_client_rust::QueryError; use scylla_server::{ prisma::PrismaClient, processors::ClientData, @@ -35,8 +35,7 @@ async fn main() -> Result<(), QueryError> { client.system().delete_many(vec![]).exec().await?; - let created_run = - run_service::create_run(&client, chrono::offset::Utc::now().timestamp_millis()).await?; + let created_run = run_service::create_run(&client, chrono::offset::Utc::now()).await?; system_service::upsert_system(&client, "Data And Controls".to_string(), created_run.id).await?; driver_service::upsert_driver(&client, "Fergus".to_string(), created_run.id).await?; @@ -67,72 +66,72 @@ async fn main() -> Result<(), QueryError> { run_id: created_run.id, name: "Pack-Temp".to_string(), unit: "C".to_string(), - values: vec!["20".to_string()], - timestamp: chrono::offset::Utc::now().timestamp_millis(), + values: vec![20f32], + timestamp: chrono::offset::Utc::now(), node: "BMS".to_string(), }, ClientData { run_id: created_run.id, name: "Pack-Temp".to_string(), unit: "C".to_string(), - values: vec!["21".to_string()], - timestamp: chrono::offset::Utc::now().timestamp_millis() + 1000, + values: vec![21f32], + timestamp: chrono::offset::Utc::now() + Duration::from_millis(1000), node: "BMS".to_string(), }, ClientData { run_id: created_run.id, name: "Pack-Temp".to_string(), unit: "C".to_string(), - values: vec!["22".to_string()], - timestamp: chrono::offset::Utc::now().timestamp_millis() + 2000, + values: vec![22f32], + timestamp: chrono::offset::Utc::now() + Duration::from_millis(2000), node: "BMS".to_string(), }, ClientData { run_id: created_run.id, name: "Pack-Temp".to_string(), unit: "C".to_string(), - values: vec!["17".to_string()], - timestamp: chrono::offset::Utc::now().timestamp_millis() + 3000, + values: vec![17f32], + timestamp: chrono::offset::Utc::now() + Duration::from_millis(3000), node: "BMS".to_string(), }, ClientData { run_id: created_run.id, name: "Pack-Temp".to_string(), unit: "C".to_string(), - values: vec!["25".to_string()], - timestamp: chrono::offset::Utc::now().timestamp_millis() + 4000, + values: vec![17f32], + timestamp: chrono::offset::Utc::now() + Duration::from_millis(4000), node: "BMS".to_string(), }, ClientData { run_id: created_run.id, name: "Pack-Temp".to_string(), unit: "C".to_string(), - values: vec!["30".to_string()], - timestamp: chrono::offset::Utc::now().timestamp_millis() + 5000, + values: vec![17f32], + timestamp: chrono::offset::Utc::now() + Duration::from_millis(5000), node: "BMS".to_string(), }, ClientData { run_id: created_run.id, name: "Pack-Temp".to_string(), unit: "C".to_string(), - values: vec!["38".to_string()], - timestamp: chrono::offset::Utc::now().timestamp_millis() + 6000, + values: vec![17f32], + timestamp: chrono::offset::Utc::now() + Duration::from_millis(6000), node: "BMS".to_string(), }, ClientData { run_id: created_run.id, name: "Pack-Temp".to_string(), unit: "C".to_string(), - values: vec!["32".to_string()], - timestamp: chrono::offset::Utc::now().timestamp_millis() + 7000, + values: vec![17f32], + timestamp: chrono::offset::Utc::now() + Duration::from_millis(7000), node: "BMS".to_string(), }, ClientData { run_id: created_run.id, name: "Pack-Temp".to_string(), unit: "C".to_string(), - values: vec!["26".to_string()], - timestamp: chrono::offset::Utc::now().timestamp_millis() + 8000, + values: vec![17f32], + timestamp: chrono::offset::Utc::now() + Duration::from_millis(8000), node: "BMS".to_string(), }, ], @@ -215,8 +214,8 @@ async fn simulate_route(db: Database, curr_run: i32) -> Result<(), QueryError> { run_id: curr_run, name: "Points".to_string(), unit: "Coord".to_string(), - values: vec![inter_lat.to_string(), inter_long.to_string()], - timestamp: chrono::offset::Utc::now().timestamp_millis(), + values: vec![inter_lat as f32, inter_long as f32], + timestamp: chrono::offset::Utc::now(), node: "TPU".to_string(), }, ) diff --git a/scylla-server/src/controllers/run_controller.rs b/scylla-server/src/controllers/run_controller.rs index 31cb517c..ce6d11dd 100644 --- a/scylla-server/src/controllers/run_controller.rs +++ b/scylla-server/src/controllers/run_controller.rs @@ -1,10 +1,9 @@ +use std::sync::atomic::Ordering; + use axum::{ extract::{Path, State}, - Extension, Json, + Json, }; -use prisma_client_rust::chrono; -use tokio::sync::mpsc; -use tracing::warn; use crate::{ error::ScyllaError, services::run_service, transformers::run_transformer::PublicRun, Database, @@ -39,17 +38,14 @@ pub async fn get_run_by_id( /// create a new run with an auto-incremented ID /// note the new run must be updated so the channel passed in notifies the data processor to use the new run -pub async fn new_run( - State(db): State, - Extension(channel): Extension>, -) -> Result, ScyllaError> { - let run_data = - run_service::create_run(&db, chrono::offset::Utc::now().timestamp_millis()).await?; - - // notify the mqtt receiver a new run has been created - if let Err(err) = channel.send(run_data.clone()).await { - warn!("Could not notify system about an updated run: {}", err); - } +pub async fn new_run(State(db): State) -> Result, ScyllaError> { + let run_data = run_service::create_run(&db, chrono::offset::Utc::now()).await?; + + crate::RUN_ID.store(run_data.id, Ordering::Relaxed); + tracing::info!( + "Starting new run with ID: {}", + crate::RUN_ID.load(Ordering::Relaxed) + ); Ok(Json::from(PublicRun::from(&run_data))) } diff --git a/scylla-server/src/lib.rs b/scylla-server/src/lib.rs index ef595323..70794d70 100644 --- a/scylla-server/src/lib.rs +++ b/scylla-server/src/lib.rs @@ -1,3 +1,5 @@ +use std::sync::atomic::AtomicI32; + pub mod controllers; pub mod error; pub mod processors; @@ -23,3 +25,6 @@ pub enum RateLimitMode { #[default] None, } + +// Atomic to keep track the current run id across EVERYTHING (very scary) +pub static RUN_ID: AtomicI32 = AtomicI32::new(-1); diff --git a/scylla-server/src/main.rs b/scylla-server/src/main.rs index 5d9c8f87..002841b6 100755 --- a/scylla-server/src/main.rs +++ b/scylla-server/src/main.rs @@ -1,4 +1,7 @@ -use std::{sync::Arc, time::Duration}; +use std::{ + sync::{atomic::Ordering, Arc}, + time::Duration, +}; use axum::{ http::Method, @@ -6,8 +9,8 @@ use axum::{ Extension, Router, }; use clap::Parser; -use prisma_client_rust::chrono; use rumqttc::v5::AsyncClient; +use scylla_server::RUN_ID; use scylla_server::{ controllers::{ self, @@ -22,7 +25,7 @@ use scylla_server::{ mqtt_processor::{MqttProcessor, MqttProcessorOptions}, ClientData, }, - services::run_service::{self, public_run}, + services::run_service::{self}, Database, RateLimitMode, }; use socketioxide::{extract::SocketRef, SocketIo}; @@ -161,9 +164,6 @@ async fn main() { // TODO tune buffer size let (db_send, db_receive) = mpsc::channel::>(1000); - // channel to update the run to a new value - let (new_run_send, new_run_receive) = mpsc::channel::(5); - // the below two threads need to cancel cleanly to ensure all queued messages are sent. therefore they are part of the a task tracker group. // create a task tracker and cancellation token let task_tracker = TaskTracker::new(); @@ -196,17 +196,17 @@ async fn main() { None } else { // creates the initial run - let curr_run = run_service::create_run(&db, chrono::offset::Utc::now().timestamp_millis()) + let curr_run = run_service::create_run(&db, chrono::offset::Utc::now()) .await .expect("Could not create initial run!"); debug!("Configuring current run: {:?}", curr_run); + RUN_ID.store(curr_run.id, Ordering::Relaxed); // run prod if this isnt present // create and spawn the mqtt processor info!("Running processor in MQTT (production) mode"); let (recv, opts) = MqttProcessor::new( mqtt_send, - new_run_receive, io, token.clone(), MqttProcessorOptions { @@ -240,10 +240,7 @@ async fn main() { // RUNS .route("/runs", get(run_controller::get_all_runs)) .route("/runs/:id", get(run_controller::get_run_by_id)) - .route( - "/runs/new", - post(run_controller::new_run).layer(Extension(new_run_send)), - ) + .route("/runs/new", post(run_controller::new_run)) // SYSTEMS .route("/systems", get(system_controller::get_all_systems)) // CONFIG diff --git a/scylla-server/src/processors/db_handler.rs b/scylla-server/src/processors/db_handler.rs index dedd9c59..5a927672 100644 --- a/scylla-server/src/processors/db_handler.rs +++ b/scylla-server/src/processors/db_handler.rs @@ -18,8 +18,8 @@ use super::{ClientData, LocationData}; /// A struct defining an in progress location packet struct LocLock { location_name: Option, - points: Option<(f64, f64)>, - radius: Option, + points: Option<(f32, f32)>, + radius: Option, } impl LocLock { @@ -37,12 +37,12 @@ impl LocLock { } /// Add points to the packet - pub fn add_points(&mut self, lat: f64, long: f64) { + pub fn add_points(&mut self, lat: f32, long: f32) { self.points = Some((lat, long)); } /// Add a radius to the packet - pub fn add_radius(&mut self, radius: f64) { + pub fn add_radius(&mut self, radius: f32) { self.radius = Some(radius); } @@ -247,14 +247,12 @@ impl DbHandler { // if data has some special meanings, push them to the database immediately, notably no matter what also enter batching logic match msg.name.as_str() { + // TODO remove driver from here, as driver is not car sourced "Driver" => { debug!("Upserting driver: {:?}", msg.values); if let Err(err) = driver_service::upsert_driver( &self.db, - msg.values - .first() - .unwrap_or(&"PizzaTheHut".to_string()) - .to_string(), + (*msg.values.first().unwrap_or(&0.0f32)).to_string(), msg.run_id, ) .await @@ -262,24 +260,19 @@ impl DbHandler { warn!("Driver upsert error: {:?}", err); } } + // TODO see above "location" => { debug!("Upserting location name: {:?}", msg.values); - self.location_lock.add_loc_name( - msg.values - .first() - .unwrap_or(&"PizzaTheHut".to_string()) - .to_string(), - ); + self.location_lock + .add_loc_name((*msg.values.first().unwrap_or(&0.0f32)).to_string()); self.is_location = true; } + // TODO see above "system" => { debug!("Upserting system: {:?}", msg.values); if let Err(err) = system_service::upsert_system( &self.db, - msg.values - .first() - .unwrap_or(&"PizzaTheHut".to_string()) - .to_string(), + (*msg.values.first().unwrap_or(&0.0f32)).to_string(), msg.run_id, ) .await @@ -290,28 +283,15 @@ impl DbHandler { "GPS-Location" => { debug!("Upserting location points: {:?}", msg.values); self.location_lock.add_points( - msg.values - .first() - .unwrap_or(&"PizzaTheHut".to_string()) - .parse::() - .unwrap_or_default(), - msg.values - .get(1) - .unwrap_or(&"PizzaTheHut".to_string()) - .parse::() - .unwrap_or_default(), + *msg.values.first().unwrap_or(&0.0f32), + *msg.values.get(1).unwrap_or(&0.0f32), ); self.is_location = true; } "Radius" => { debug!("Upserting location radius: {:?}", msg.values); - self.location_lock.add_radius( - msg.values - .first() - .unwrap_or(&"PizzaTheHut".to_string()) - .parse::() - .unwrap_or_default(), - ); + self.location_lock + .add_radius(*msg.values.first().unwrap_or(&0.0f32)); self.is_location = true; } _ => {} @@ -324,9 +304,9 @@ impl DbHandler { if let Err(err) = location_service::upsert_location( &self.db, loc.location_name, - loc.lat, - loc.long, - loc.radius, + loc.lat as f64, + loc.long as f64, + loc.radius as f64, msg.run_id, ) .await diff --git a/scylla-server/src/processors/mock_data.rs b/scylla-server/src/processors/mock_data.rs index 0ca1c447..e557f3bd 100644 --- a/scylla-server/src/processors/mock_data.rs +++ b/scylla-server/src/processors/mock_data.rs @@ -1,4 +1,4 @@ -use super::mock_processor::{MockData, MockStringData}; +use super::mock_processor::MockData; pub const BASE_MOCK_DATA: [MockData; 17] = [ MockData { @@ -121,16 +121,3 @@ pub const BASE_MOCK_DATA: [MockData; 17] = [ max: 600.0, }, ]; - -pub const BASE_MOCK_STRING_DATA: [MockStringData; 2] = [ - MockStringData { - name: "Driver", - unit: "String", - vals: "Fergus", - }, - MockStringData { - name: "Location", - unit: "String", - vals: "Max", - }, -]; diff --git a/scylla-server/src/processors/mock_processor.rs b/scylla-server/src/processors/mock_processor.rs index c6f8b789..4757ba4f 100644 --- a/scylla-server/src/processors/mock_processor.rs +++ b/scylla-server/src/processors/mock_processor.rs @@ -1,47 +1,32 @@ use std::time::Duration; -use prisma_client_rust::{chrono, serde_json}; use rand::Rng; use socketioxide::SocketIo; use tracing::warn; -use super::{ - mock_data::{BASE_MOCK_DATA, BASE_MOCK_STRING_DATA}, - ClientData, -}; +use super::{mock_data::BASE_MOCK_DATA, ClientData}; #[derive(Clone, Copy)] pub struct MockData { pub name: &'static str, pub unit: &'static str, pub num_of_vals: u8, - pub min: f64, - pub max: f64, + pub min: f32, + pub max: f32, } impl MockData { - fn get_values(&self) -> Vec { - let mut val_vec: Vec = vec![]; + fn get_values(&self) -> Vec { + let mut val_vec: Vec = vec![]; // for each point, get a random number in the range for _ in 0..self.num_of_vals { - val_vec.push( - rand::thread_rng() - .gen_range((self.min)..(self.max)) - .to_string(), - ); + val_vec.push(rand::thread_rng().gen_range((self.min)..(self.max))); } val_vec } } -#[derive(Clone, Copy)] -pub struct MockStringData { - pub name: &'static str, - pub unit: &'static str, - pub vals: &'static str, -} - pub struct MockProcessor { curr_run: i32, io: SocketIo, @@ -54,33 +39,18 @@ impl MockProcessor { pub async fn generate_mock(self) { loop { - // get a random mock datapoint the first 0 to len of number mock data is for the non string and x to len of string mocks is a string mock index. - let index = rand::thread_rng() - .gen_range(0..(BASE_MOCK_DATA.len() + BASE_MOCK_STRING_DATA.len())); + // get a random mock datapoint the first 0 to len of number mock data + let index = rand::thread_rng().gen_range(0..(BASE_MOCK_DATA.len())); - // if we are doing non-string mock this loop - let client_data: ClientData = if index < BASE_MOCK_DATA.len() { - let dat = BASE_MOCK_DATA[index]; + let dat = BASE_MOCK_DATA[index]; - ClientData { - run_id: self.curr_run, - name: dat.name.to_string(), - unit: dat.unit.to_string(), - values: dat.get_values(), - timestamp: chrono::offset::Utc::now().timestamp_millis(), - node: "".to_string(), // uneeded for socket use only - } - // do a string mock - } else { - let dat = BASE_MOCK_STRING_DATA[index - BASE_MOCK_DATA.len()]; - ClientData { - run_id: self.curr_run, - name: dat.name.to_string(), - unit: dat.unit.to_string(), - values: vec![dat.vals.to_string()], - timestamp: chrono::offset::Utc::now().timestamp_millis(), - node: "".to_string(), // uneeded for socket use only - } + let client_data: ClientData = ClientData { + run_id: self.curr_run, + name: dat.name.to_string(), + unit: dat.unit.to_string(), + values: dat.get_values(), + timestamp: chrono::offset::Utc::now(), + node: "".to_string(), // uneeded for socket use only }; match self.io.emit( diff --git a/scylla-server/src/processors/mod.rs b/scylla-server/src/processors/mod.rs index afb3bbef..5c0736e3 100644 --- a/scylla-server/src/processors/mod.rs +++ b/scylla-server/src/processors/mod.rs @@ -1,3 +1,6 @@ +use chrono::serde::ts_milliseconds; +use chrono::{DateTime, Utc}; + pub mod db_handler; mod mock_data; pub mod mock_processor; @@ -10,12 +13,16 @@ pub mod mqtt_processor; /// Note: node name is only considered for database storage and convenience, it is not serialized in a socket packet #[derive(serde::Serialize, Clone, Debug)] pub struct ClientData { + #[serde(rename = "runId")] pub run_id: i32, pub name: String, pub unit: String, - pub values: Vec, - pub timestamp: i64, + pub values: Vec, + /// Client expects time in milliseconds, so serialize as such + #[serde(with = "ts_milliseconds")] + pub timestamp: DateTime, + /// client doesnt parse node #[serde(skip_serializing)] pub node: String, } @@ -25,7 +32,7 @@ pub struct ClientData { #[derive(Debug)] struct LocationData { location_name: String, - lat: f64, - long: f64, - radius: f64, + lat: f32, + long: f32, + radius: f32, } diff --git a/scylla-server/src/processors/mqtt_processor.rs b/scylla-server/src/processors/mqtt_processor.rs index 78153d98..604e876f 100644 --- a/scylla-server/src/processors/mqtt_processor.rs +++ b/scylla-server/src/processors/mqtt_processor.rs @@ -1,10 +1,11 @@ use std::{ collections::HashMap, - sync::Arc, + sync::{atomic::Ordering, Arc}, time::{Duration, SystemTime}, }; -use prisma_client_rust::{bigdecimal::ToPrimitive, chrono, serde_json}; +use chrono::TimeDelta; +use prisma_client_rust::bigdecimal::ToPrimitive; use protobuf::Message; use ringbuffer::RingBuffer; use rumqttc::v5::{ @@ -12,20 +13,15 @@ use rumqttc::v5::{ AsyncClient, Event, EventLoop, MqttOptions, }; use socketioxide::SocketIo; -use tokio::{ - sync::mpsc::{Receiver, Sender}, - time::Instant, -}; +use tokio::{sync::mpsc::Sender, time::Instant}; use tokio_util::sync::CancellationToken; use tracing::{debug, instrument, trace, warn, Level}; use crate::{ - controllers::car_command_controller::CALYPSO_BIDIR_CMD_PREFIX, serverdata, - services::run_service, RateLimitMode, + controllers::car_command_controller::CALYPSO_BIDIR_CMD_PREFIX, serverdata, RateLimitMode, }; use super::ClientData; -use std::borrow::Cow; /// The chief processor of incoming mqtt data, this handles /// - mqtt state @@ -37,8 +33,6 @@ use std::borrow::Cow; /// It also is the main form of rate limiting pub struct MqttProcessor { channel: Sender, - new_run_channel: Receiver, - curr_run: i32, io: SocketIo, cancel_token: CancellationToken, /// Upload ratio, below is not socket sent above is socket sent @@ -68,14 +62,12 @@ pub struct MqttProcessorOptions { impl MqttProcessor { /// Creates a new mqtt receiver and socketio and db sender /// * `channel` - The mpsc channel to send the database data to - /// * `new_run_channel` - The channel for new run notifications /// * `io` - The socketio layer to send the data to /// * `cancel_token` - The token which indicates cancellation of the task /// * `opts` - The mqtt processor options to use /// Returns the instance and options to create a client, which is then used in the process_mqtt loop pub fn new( channel: Sender, - new_run_channel: Receiver, io: SocketIo, cancel_token: CancellationToken, opts: MqttProcessorOptions, @@ -109,8 +101,6 @@ impl MqttProcessor { ( MqttProcessor { channel, - new_run_channel, - curr_run: opts.initial_run, io, cancel_token, upload_ratio: opts.upload_ratio, @@ -129,7 +119,7 @@ impl MqttProcessor { let mut view_interval = tokio::time::interval(Duration::from_secs(3)); let mut latency_interval = tokio::time::interval(Duration::from_millis(250)); - let mut latency_ringbuffer = ringbuffer::AllocRingBuffer::::new(20); + let mut latency_ringbuffer = ringbuffer::AllocRingBuffer::::new(20); let mut upload_counter: u8 = 0; @@ -154,7 +144,7 @@ impl MqttProcessor { Some(msg) => msg, None => continue }; - latency_ringbuffer.push(chrono::offset::Utc::now().timestamp_millis() - msg.timestamp); + latency_ringbuffer.push(chrono::offset::Utc::now() - msg.timestamp); self.send_db_msg(msg.clone()).await; self.send_socket_msg(msg, &mut upload_counter); }, @@ -168,9 +158,9 @@ impl MqttProcessor { name: "Viewers".to_string(), node: "Internal".to_string(), unit: "".to_string(), - run_id: self.curr_run, - timestamp: chrono::offset::Utc::now().timestamp_millis(), - values: vec![sockets.len().to_string()] + run_id: crate::RUN_ID.load(Ordering::Relaxed), + timestamp: chrono::offset::Utc::now(), + values: vec![sockets.len() as f32] }; self.send_socket_msg(client_data, &mut upload_counter); } else { @@ -182,24 +172,20 @@ impl MqttProcessor { let avg_latency = if latency_ringbuffer.is_empty() { 0 } else { - latency_ringbuffer.iter().sum::() / latency_ringbuffer.len().to_i64().unwrap_or_default() + latency_ringbuffer.iter().sum::().num_milliseconds() / latency_ringbuffer.len().to_i64().unwrap_or_default() }; let client_data = ClientData { name: "Latency".to_string(), node: "Internal".to_string(), unit: "ms".to_string(), - run_id: self.curr_run, - timestamp: chrono::offset::Utc::now().timestamp_millis(), - values: vec![avg_latency.to_string()] + run_id: crate::RUN_ID.load(Ordering::Relaxed), + timestamp: chrono::offset::Utc::now(), + values: vec![avg_latency as f32] }; - trace!("Latency update sending: {}", client_data.values.first().unwrap_or(&"n/a".to_string())); + trace!("Latency update sending: {}", client_data.values.first().unwrap_or(&0.0f32)); self.send_socket_msg(client_data, &mut upload_counter); } - Some(new_run) = self.new_run_channel.recv() => { - trace!("New run: {:?}", new_run); - self.curr_run = new_run.id; - } } } } @@ -254,48 +240,70 @@ impl MqttProcessor { let data_type = split.1.replace('/', "-"); - // extract the unix time, returning the current time instead if either the "ts" user property isnt present or it isnt parsable - // note the Cow magic involves the return from the map is a borrow, but the unwrap cannot as we dont own it - let unix_time = msg - .properties - .unwrap_or_default() - .user_properties - .iter() - .map(Cow::Borrowed) - .find(|f| f.0 == "ts") - .unwrap_or_else(|| { - debug!("Could not find timestamp in mqtt, using system time"); - Cow::Owned(( - "ts".to_string(), - chrono::offset::Utc::now().timestamp_millis().to_string(), - )) - }) - .1 - .parse::() - .unwrap_or_else(|err| { - warn!("Invalid timestamp in mqtt, using system time: {}", err); - chrono::offset::Utc::now().timestamp_millis() - }); - - // ts check for bad sources of time which may return 1970 - // if both system time and packet timestamp are before year 2000, the message cannot be recorded - let unix_clean = if unix_time < 963014966000 { - debug!("Timestamp before year 2000: {}", unix_time); - let sys_time = chrono::offset::Utc::now().timestamp_millis(); - if sys_time < 963014966000 { - warn!("System has no good time, discarding message!"); + // extract the unix time + // levels of time priority + // - A: The time packaged in the protobuf, to microsecond precision + // - B: The time packaged in the MQTT header, to millisecond precision (hence the * 1000 on B) + // - C: The local scylla system time + // note protobuf defaults to 0 for unfilled time, so consider it as an unset time + let unix_time = if data.time_us > 0 { + // A + let Some(unix_time) = chrono::DateTime::from_timestamp_micros(data.time_us as i64) + else { + warn!( + "Corrupted time in protobuf: {}, discarding message!", + data.time_us + ); return None; - } - sys_time - } else { + }; unix_time + } else { + // B + match match msg + .properties + .unwrap_or_default() + .user_properties + .iter() + .find(|f| f.0 == "ts") + { + Some(val) => { + let Ok(time_parsed) = val.1.parse::() else { + warn!("Corrupted time in mqtt header, discarding message!"); + return None; + }; + chrono::DateTime::from_timestamp_millis(time_parsed) + } + None => None, + } { + Some(e) => e, + None => { + // C + debug!("Could not extract time, using system time!"); + chrono::offset::Utc::now() + } + } }; + // ts check for bad sources of time which may return 1970 + // if both system time and packet timestamp are before year 2000, the message cannot be recorded + let unix_clean = + if unix_time < chrono::DateTime::from_timestamp_millis(963014966000).unwrap() { + debug!("Timestamp before year 2000: {}", unix_time.to_string()); + let sys_time = chrono::offset::Utc::now(); + if sys_time < chrono::DateTime::from_timestamp_millis(963014966000).unwrap() { + warn!("System has no good time, discarding message!"); + return None; + } + sys_time + } else { + unix_time + }; + Some(ClientData { - run_id: self.curr_run, + run_id: crate::RUN_ID.load(Ordering::Relaxed), name: data_type, unit: data.unit, - values: data.value, + values: data.values, timestamp: unix_clean, node: node.to_string(), }) diff --git a/scylla-server/src/proto/serverdata.proto b/scylla-server/src/proto/serverdata.proto index 98f812f0..2f41ccb8 100644 --- a/scylla-server/src/proto/serverdata.proto +++ b/scylla-server/src/proto/serverdata.proto @@ -1,8 +1,14 @@ syntax = "proto3"; -package serverdata.v1; +package serverdata.v2; message ServerData { - repeated string value = 1; + // ensure old type is reserved + reserved 1; + reserved "value"; + string unit = 2; + // time since unix epoch in MICROSECONDS + uint64 time_us = 3; + repeated float values = 4; } diff --git a/scylla-server/src/services/data_service.rs b/scylla-server/src/services/data_service.rs index dd5c212e..1cfb5387 100644 --- a/scylla-server/src/services/data_service.rs +++ b/scylla-server/src/services/data_service.rs @@ -1,4 +1,4 @@ -use prisma_client_rust::{chrono::DateTime, QueryError}; +use prisma_client_rust::QueryError; use crate::{prisma, processors::ClientData, Database}; @@ -43,16 +43,10 @@ pub async fn add_data( db.data() .create( prisma::data_type::name::equals(client_data.name), - DateTime::from_timestamp_millis(client_data.timestamp) - .expect("Could not parse timestamp") - .fixed_offset(), + client_data.timestamp.fixed_offset(), prisma::run::id::equals(client_data.run_id), vec![prisma::data::values::set( - client_data - .values - .iter() - .map(|f| f.parse::().unwrap_or_default()) - .collect(), + client_data.values.iter().map(|f| *f as f64).collect(), )], ) .select(public_data::select()) @@ -72,15 +66,10 @@ pub async fn add_many(db: &Database, client_data: Vec) -> Result().unwrap_or_default()) - .collect(), + f.values.iter().map(|f| *f as f64).collect(), )], ) }) diff --git a/scylla-server/src/services/run_service.rs b/scylla-server/src/services/run_service.rs index 5520851e..b775e36b 100644 --- a/scylla-server/src/services/run_service.rs +++ b/scylla-server/src/services/run_service.rs @@ -1,6 +1,7 @@ use std::vec; -use prisma_client_rust::{chrono::DateTime, QueryError}; +use chrono::{DateTime, Utc}; +use prisma_client_rust::QueryError; use crate::{ prisma::{self}, @@ -43,16 +44,14 @@ pub async fn get_run_by_id( /// Creates a run /// * `db` - The prisma client to make the call to -/// * `timestamp` - The unix time since epoch in miliseconds when the run starts +/// * `timestamp` - time when the run starts /// returns: A result containing the data or the QueryError propogated by the db -pub async fn create_run(db: &Database, timestamp: i64) -> Result { +pub async fn create_run( + db: &Database, + timestamp: DateTime, +) -> Result { db.run() - .create( - DateTime::from_timestamp_millis(timestamp) - .expect("Could not parse timestamp") - .fixed_offset(), - vec![], - ) + .create(timestamp.fixed_offset(), vec![]) .select(public_run::select()) .exec() .await @@ -60,21 +59,16 @@ pub async fn create_run(db: &Database, timestamp: i64) -> Result, run_id: i32, ) -> Result { db.run() - .create( - DateTime::from_timestamp_millis(timestamp) - .expect("Could not parse timestamp") - .fixed_offset(), - vec![prisma::run::id::set(run_id)], - ) + .create(timestamp.fixed_offset(), vec![prisma::run::id::set(run_id)]) .select(public_run::select()) .exec() .await diff --git a/scylla-server/src/transformers/data_transformer.rs b/scylla-server/src/transformers/data_transformer.rs index 6fd20b3d..6decadfe 100644 --- a/scylla-server/src/transformers/data_transformer.rs +++ b/scylla-server/src/transformers/data_transformer.rs @@ -1,20 +1,43 @@ +use std::cmp::Ordering; + use serde::Serialize; use crate::{processors::ClientData, services::data_service}; /// The struct defining the data format sent to the client -#[derive(Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Serialize, Debug)] pub struct PublicData { - pub time: i64, - pub values: Vec, + #[serde(rename = "time")] + pub time_ms: i64, + pub values: Vec, +} +// custom impls to avoid comparing values fields +impl Ord for PublicData { + fn cmp(&self, other: &Self) -> Ordering { + self.time_ms.cmp(&other.time_ms) + } +} + +impl PartialOrd for PublicData { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } } +impl PartialEq for PublicData { + fn eq(&self, other: &Self) -> bool { + self.time_ms == other.time_ms + } +} + +impl Eq for PublicData {} + /// convert the prisma type to the client type for JSON encoding impl From<&data_service::public_data::Data> for PublicData { fn from(value: &data_service::public_data::Data) -> Self { PublicData { - values: value.values.iter().map(f64::to_string).collect(), - time: value.time.timestamp_millis(), + values: value.values.clone(), + time_ms: value.time.timestamp_millis(), } } } @@ -23,8 +46,8 @@ impl From<&data_service::public_data::Data> for PublicData { impl From for PublicData { fn from(value: ClientData) -> Self { PublicData { - time: value.timestamp, - values: value.values, + time_ms: value.timestamp.timestamp_millis(), + values: value.values.iter().map(|f| *f as f64).collect(), } } } diff --git a/scylla-server/src/transformers/run_transformer.rs b/scylla-server/src/transformers/run_transformer.rs index b1e18b6b..a589e5a9 100644 --- a/scylla-server/src/transformers/run_transformer.rs +++ b/scylla-server/src/transformers/run_transformer.rs @@ -15,7 +15,8 @@ pub struct PublicRun { pub driver_name: String, #[serde(rename = "systemName")] pub system_name: String, - pub time: i64, + #[serde(rename = "time")] + pub time_ms: i64, } impl From<&public_run::Data> for PublicRun { @@ -25,7 +26,7 @@ impl From<&public_run::Data> for PublicRun { location_name: value.location_name.clone().unwrap_or_default(), driver_name: value.driver_name.clone().unwrap_or_default(), system_name: value.system_name.clone().unwrap_or_default(), - time: value.time.timestamp_millis(), + time_ms: value.time.timestamp_millis(), } } } @@ -39,7 +40,7 @@ impl From<&public_driver::runs::Data> for PublicRun { location_name: value.location_name.clone().unwrap_or_default(), driver_name: value.driver_name.clone().unwrap_or_default(), system_name: value.system_name.clone().unwrap_or_default(), - time: value.time.timestamp_millis(), + time_ms: value.time.timestamp_millis(), } } } @@ -51,7 +52,7 @@ impl From<&public_location::runs::Data> for PublicRun { location_name: value.location_name.clone().unwrap_or_default(), driver_name: value.driver_name.clone().unwrap_or_default(), system_name: value.system_name.clone().unwrap_or_default(), - time: value.time.timestamp_millis(), + time_ms: value.time.timestamp_millis(), } } } @@ -63,7 +64,7 @@ impl From<&public_system::runs::Data> for PublicRun { location_name: value.location_name.clone().unwrap_or_default(), driver_name: value.driver_name.clone().unwrap_or_default(), system_name: value.system_name.clone().unwrap_or_default(), - time: value.time.timestamp_millis(), + time_ms: value.time.timestamp_millis(), } } } diff --git a/scylla-server/tests/data_service_test.rs b/scylla-server/tests/data_service_test.rs index 68c027fe..bd53a4de 100644 --- a/scylla-server/tests/data_service_test.rs +++ b/scylla-server/tests/data_service_test.rs @@ -15,7 +15,8 @@ const TEST_KEYWORD: &str = "test"; async fn test_data_service() -> Result<(), QueryError> { let db = cleanup_and_prepare().await?; - run_service::create_run_with_id(&db, 0, 0).await?; + run_service::create_run_with_id(&db, chrono::DateTime::from_timestamp_millis(0).unwrap(), 0) + .await?; node_service::upsert_node(&db, TEST_KEYWORD.to_owned()).await?; data_type_service::upsert_data_type( &db, @@ -41,16 +42,17 @@ async fn test_data_add() -> Result<(), QueryError> { TEST_KEYWORD.to_owned(), ) .await?; - let run_data = run_service::create_run(&db, 999).await?; + let run_data = + run_service::create_run(&db, chrono::DateTime::from_timestamp_millis(999).unwrap()).await?; let data = data_service::add_data( &db, ClientData { - values: vec!["0".to_owned()], + values: vec![0f32], unit: "A".to_owned(), run_id: run_data.id, name: TEST_KEYWORD.to_owned(), - timestamp: 1000, + timestamp: chrono::DateTime::from_timestamp_millis(1000).unwrap(), node: "Irrelevant".to_string(), }, ) @@ -59,8 +61,8 @@ async fn test_data_add() -> Result<(), QueryError> { assert_eq!( PublicData::from(&data), PublicData { - time: 1000, - values: vec!["0".to_owned()] + time_ms: 1000, + values: vec![0f64] } ); @@ -87,11 +89,11 @@ async fn test_data_no_prereqs() -> Result<(), QueryError> { data_service::add_data( &db, ClientData { - values: vec!["0".to_owned()], + values: vec![0f32], unit: "A".to_owned(), run_id: 0, name: TEST_KEYWORD.to_owned(), - timestamp: 1000, + timestamp: chrono::DateTime::from_timestamp_millis(1000).unwrap(), node: "Irrelevant".to_string(), }, ) @@ -107,17 +109,22 @@ async fn test_data_no_prereqs() -> Result<(), QueryError> { TEST_KEYWORD.to_owned(), ) .await?; - run_service::create_run_with_id(&db, 1000, 0).await?; + run_service::create_run_with_id( + &db, + chrono::DateTime::from_timestamp_millis(1000).unwrap(), + 0, + ) + .await?; // now shouldnt fail as it and node does exist data_service::add_data( &db, ClientData { - values: vec!["0".to_owned()], + values: vec![0f32], unit: "A".to_owned(), run_id: 0, name: TEST_KEYWORD.to_owned(), - timestamp: 1000, + timestamp: chrono::DateTime::from_timestamp_millis(1000).unwrap(), node: "Irrelevant".to_string(), }, ) diff --git a/scylla-server/tests/data_type_service_test.rs b/scylla-server/tests/data_type_service_test.rs index 1f5007e0..0a0cb2c4 100644 --- a/scylla-server/tests/data_type_service_test.rs +++ b/scylla-server/tests/data_type_service_test.rs @@ -68,7 +68,7 @@ async fn test_datatype_create() -> Result<(), QueryError> { PublicDataType::from(&data), PublicDataType { name: data_type_name, - unit: unit + unit } ); diff --git a/scylla-server/tests/driver_service_test.rs b/scylla-server/tests/driver_service_test.rs index f5233c72..c15ed83f 100644 --- a/scylla-server/tests/driver_service_test.rs +++ b/scylla-server/tests/driver_service_test.rs @@ -24,7 +24,9 @@ async fn test_create_driver() -> Result<(), QueryError> { driver_service::upsert_driver( &db, TEST_KEYWORD.to_owned(), - run_service::create_run(&db, 10001).await?.id, + run_service::create_run(&db, chrono::DateTime::from_timestamp_millis(1001).unwrap()) + .await? + .id, ) .await?; diff --git a/scylla-server/tests/location_service_test.rs b/scylla-server/tests/location_service_test.rs index 72f476aa..ebd90931 100644 --- a/scylla-server/tests/location_service_test.rs +++ b/scylla-server/tests/location_service_test.rs @@ -20,7 +20,9 @@ async fn test_get_all_locations_and_upsert() -> Result<(), QueryError> { 100.0, 200.0, 300.0, - run_service::create_run(&db, 10001).await?.id, + run_service::create_run(&db, chrono::DateTime::from_timestamp_millis(1001).unwrap()) + .await? + .id, ) .await?; diff --git a/scylla-server/tests/run_service_test.rs b/scylla-server/tests/run_service_test.rs index 1f4aeba5..039a519a 100644 --- a/scylla-server/tests/run_service_test.rs +++ b/scylla-server/tests/run_service_test.rs @@ -20,7 +20,8 @@ async fn test_get_run_by_id() -> Result<(), QueryError> { let db = cleanup_and_prepare().await?; // add a run - let run_c = run_service::create_run(&db, 1).await?; + let run_c = + run_service::create_run(&db, chrono::DateTime::from_timestamp_millis(1).unwrap()).await?; // get that run let run = run_service::get_run_by_id(&db, run_c.id) diff --git a/scylla-server/tests/system_service_test.rs b/scylla-server/tests/system_service_test.rs index 2bf2ee1b..c483853f 100644 --- a/scylla-server/tests/system_service_test.rs +++ b/scylla-server/tests/system_service_test.rs @@ -17,7 +17,8 @@ const TEST_KEYWORD: &str = "test"; async fn test_upsert_system_create() -> Result<(), QueryError> { let db = cleanup_and_prepare().await?; - let run = run_service::create_run(&db, 101).await?; + let run = + run_service::create_run(&db, chrono::DateTime::from_timestamp_millis(101).unwrap()).await?; let _ = system_service::upsert_system(&db, TEST_KEYWORD.to_owned(), run.id).await?; @@ -49,14 +50,16 @@ async fn test_get_upsert_system() -> Result<(), QueryError> { system_service::upsert_system( &db, TEST_KEYWORD.to_owned(), - run_service::create_run(&db, 101).await?.id, + run_service::create_run(&db, chrono::DateTime::from_timestamp_millis(101).unwrap()) + .await? + .id, ) .await?; let sys = system_service::get_all_systems(&db).await?; sys.iter() - .find(|&f| f.name == TEST_KEYWORD.to_owned()) + .find(|&f| f.name == *TEST_KEYWORD) .expect("System of the added name should exist in the list of systems"); Ok(()) diff --git a/siren-base/compose.siren.yml b/siren-base/compose.siren.yml index 69013f70..3e4e4c68 100644 --- a/siren-base/compose.siren.yml +++ b/siren-base/compose.siren.yml @@ -5,7 +5,7 @@ services: image: eclipse-mosquitto:latest ports: - 1883:1883 - - 9001:9001 # why? + - 9002:9001 # win conflict on 9001 expose: - 1883 volumes: From 2eea24871e53b49973c2ab380ab750a4fac37153 Mon Sep 17 00:00:00 2001 From: Som Shrivastava Date: Mon, 28 Oct 2024 00:47:37 -0400 Subject: [PATCH 2/5] got rid of a console.log --- .../graph-sidebar-desktop/graph-sidebar-desktop.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.ts b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.ts index c734c4fb..ed737154 100644 --- a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.ts +++ b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.ts @@ -96,7 +96,6 @@ export default class GraphSidebarDesktop implements OnInit { this.selectedDataType.subscribe((dataType: DataType) => { this.dataTypeName = dataType.name; - console.log(this.dataTypeName); }); } From 204d5e87d4df17079ae109f737d5f4af52a8b582 Mon Sep 17 00:00:00 2001 From: Som Shrivastava Date: Mon, 28 Oct 2024 01:06:43 -0400 Subject: [PATCH 3/5] linting and prettier errors --- .../graph-header/graph-header.component.ts | 2 -- .../pages/graph-page/graph-page.component.html | 7 ++++++- .../pages/graph-page/graph-page.component.ts | 18 +++++++++--------- .../graph-sidebar-desktop.component.ts | 2 +- .../graph-sidebar/graph-sidebar.component.html | 2 +- .../sidebar-card/sidebar-card.component.ts | 2 +- 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/angular-client/src/pages/graph-page/graph-header/graph-header.component.ts b/angular-client/src/pages/graph-page/graph-header/graph-header.component.ts index 99e417ca..120eecc8 100644 --- a/angular-client/src/pages/graph-page/graph-header/graph-header.component.ts +++ b/angular-client/src/pages/graph-page/graph-header/graph-header.component.ts @@ -1,6 +1,4 @@ import { Component, Input } from '@angular/core'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { MatButtonModule } from '@angular/material/button'; /** * Graph Header Component to display the graph page header. diff --git a/angular-client/src/pages/graph-page/graph-page.component.html b/angular-client/src/pages/graph-page/graph-page.component.html index e36470d6..e79e3fb1 100644 --- a/angular-client/src/pages/graph-page/graph-page.component.html +++ b/angular-client/src/pages/graph-page/graph-page.component.html @@ -10,7 +10,12 @@
- +
node.dataTypes.filter((dataType: DataType) => dataType.name == name)[0] + (node: Node) => node.dataTypes.filter((dataType: DataType) => dataType.name === name)[0] )[0] as Node; } @@ -203,7 +203,7 @@ export default class GraphPage implements OnInit { } private getDataType(node: Node, name: string) { - return node.dataTypes.filter((dataType: DataType) => dataType.name == name)[0]; + return node.dataTypes.filter((dataType: DataType) => dataType.name === name)[0]; } private getDataTypeIndex(node: Node, dataType: DataType) { diff --git a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.ts b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.ts index ed737154..febd51ae 100644 --- a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.ts +++ b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.ts @@ -100,7 +100,7 @@ export default class GraphSidebarDesktop implements OnInit { } isNodeOpen(node: NodeWithVisibilityToggle) { - return node.dataTypes.filter((dataType: DataType) => dataType.name == this.dataTypeName).length > 0; + return node.dataTypes.filter((dataType: DataType) => dataType.name === this.dataTypeName).length > 0; } ngOnDestroy(): void { diff --git a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar.component.html b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar.component.html index 8e285407..ca561f3a 100644 --- a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar.component.html +++ b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar.component.html @@ -2,5 +2,5 @@
- + diff --git a/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.ts b/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.ts index a7aa37ee..a9d0737e 100644 --- a/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.ts +++ b/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.ts @@ -24,7 +24,7 @@ export default class SidebarCard implements OnInit { ngOnInit(): void { this.iconId = `${this.title}-icon`; this.selectedDataType.subscribe((dataType: DataType) => { - this.selected = this.title == dataType.name; + this.selected = this.title === dataType.name; }); } } From 2eb468690af45463f47a468b96b41da23e51cfc8 Mon Sep 17 00:00:00 2001 From: RChandler234 <29521172+RChandler234@users.noreply.github.com> Date: Fri, 1 Nov 2024 01:09:15 -0400 Subject: [PATCH 4/5] #1 fix arrow down new node bug --- .../graph-sidebar-desktop.component.html | 2 +- .../graph-sidebar/sidebar-card/sidebar-card.component.ts | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.html b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.html index 707f204a..b88bc65a 100644 --- a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.html +++ b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.html @@ -27,7 +27,7 @@ style="width: 95%; margin-left: 30px" [title]="dataType.name" [dataValue]="dataValuesMap.get(dataType.name)" - [selectedDataType]="selectedDataType" + [selected]="dataType.name === dataTypeName" (click)="selectDataType(dataType)" />
diff --git a/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.ts b/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.ts index a9d0737e..b5b7ca12 100644 --- a/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.ts +++ b/angular-client/src/pages/graph-page/graph-sidebar/sidebar-card/sidebar-card.component.ts @@ -17,14 +17,10 @@ export default class SidebarCard implements OnInit { @Input() dropDown?: boolean; @Input() open?: boolean; @Input() dataValue?: string; - @Input() selectedDataType: Subject = new Subject(); + @Input() selected?: boolean; iconId!: string; - selected?: boolean; ngOnInit(): void { this.iconId = `${this.title}-icon`; - this.selectedDataType.subscribe((dataType: DataType) => { - this.selected = this.title === dataType.name; - }); } } From 5e9d842cce75bc619c4202373badd6960b520a20 Mon Sep 17 00:00:00 2001 From: Som Shrivastava Date: Thu, 7 Nov 2024 13:42:37 -0500 Subject: [PATCH 5/5] Quick change in graph sidebar desktop --- .../graph-sidebar-desktop.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.html b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.html index b88bc65a..97b4b64b 100644 --- a/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.html +++ b/angular-client/src/pages/graph-page/graph-sidebar/graph-sidebar-desktop/graph-sidebar-desktop.component.html @@ -16,9 +16,9 @@ [title]="node.name" (click)="toggleDataTypeVisibility(node)" [dropDown]="true" - [open]="node.dataTypesAreVisible ? true : isNodeOpen(node)" + [open]="node.dataTypesAreVisible || isNodeOpen(node)" /> -
+