Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions packages/amplify/amplify/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,10 @@ const TelemetryBackendSecretsManagerMQTTCredentials = new secretsmanager.Secret(
},
);

const TelemetryBackendSecretsDatabaseCredentials = new secretsmanager.Secret(
const TimescaleConnectionString = secretsmanager.Secret.fromSecretNameV2(
TelemetryBackendStack,
"HeliosTelemetryDBCredentials",
{
secretName: "HeliosTelemetryDBCredentials" + backend.stack.stackName,
secretObjectValue: {
POSTGRES_PASSWORD: cdk.SecretValue.unsafePlainText(""),
POSTGRES_USERNAME: cdk.SecretValue.unsafePlainText(""),
},
},
"TimescaleConnectionString",
"TimescaleConnectionString",
);
Comment on lines +55 to 59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file structure to understand the repository layout
git ls-files | head -20

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 443


🏁 Script executed:

# Search for references to the TimescaleConnectionString secret
rg "TimescaleConnectionString" -A 5 -B 2

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 2747


🏁 Script executed:

# Search for the specific database environment variable keys mentioned
rg "DATABASE_HOST|DATABASE_PASSWORD|DATABASE_PORT|DATABASE_USERNAME" -A 2 -B 2

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 2760


🏁 Script executed:

# Look for any setup or deployment documentation
fd -e md -e txt | xargs grep -l -i "secret\|timescale\|database" 2>/dev/null | head -10

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 168


🏁 Script executed:

# Check the ECS task definition around lines 354-369
sed -n '350,375p' packages/amplify/amplify/backend.ts

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 956


🏁 Script executed:

# Search for any AWS secrets configuration or deployment scripts
fd -e sh -e py -e ts -e js | xargs grep -l "Secrets Manager\|secrets\|DATABASE" 2>/dev/null | grep -E "(setup|deploy|init|config)" | head -5

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 57


🏁 Script executed:

# Check the AMPLIFY documentation for secret setup instructions
cat docs/AMPLIFY.md

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 7620


🏁 Script executed:

# Check the database package documentation
cat packages/db/README.md

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 243


🏁 Script executed:

# Check for any other deployment or setup documentation
cat docs/SERVER.md | head -100

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 5131


🏁 Script executed:

# Search for any CloudFormation templates or IaC that might provision the secret
find . -name "*.yml" -o -name "*.yaml" -o -name "*.json" | grep -E "(cloudformation|template|infrastructure|secrets)" | head -10

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 57


🏁 Script executed:

# Check if there's any reference to secret provisioning in the codebase
rg "TimescaleConnectionString" -B 5 -A 5 --type-add 'config:*.yml' --type config --type-add 'template:*.json' --type template

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 57


🏁 Script executed:

# Look for any environment setup or deployment instructions in README files
find . -maxdepth 2 -name "README*" -type f | xargs cat 2>/dev/null

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 6068


Ensure the TimescaleConnectionString secret is pre-provisioned in AWS Secrets Manager before deployment.

The code references a TimescaleConnectionString secret and expects it to contain JSON keys: DATABASE_HOST, DATABASE_PASSWORD, DATABASE_PORT, and DATABASE_USERNAME. The ECS task will fail to start if this secret does not exist or if these keys are missing. Create this secret in Secrets Manager with the correct structure before deploying.

🤖 Prompt for AI Agents
In `@packages/amplify/amplify/backend.ts` around lines 55 - 59, The stack
references a pre-existing Secrets Manager secret via
secretsmanager.Secret.fromSecretNameV2(TelemetryBackendStack,
"TimescaleConnectionString", "TimescaleConnectionString"), so create a Secrets
Manager secret named exactly "TimescaleConnectionString" before deployment and
populate it with JSON keys DATABASE_HOST, DATABASE_PASSWORD, DATABASE_PORT, and
DATABASE_USERNAME (with appropriate values); verify the secret name matches the
string passed to fromSecretNameV2 and that the JSON structure is correct so the
ECS task can read each key at runtime.


const TelemetryBackendImageRepository = new ecr.Repository(
Expand Down Expand Up @@ -326,9 +320,6 @@ const TelemetryBackendVPC = new ec2.Vpc(

TelemetryECSTaskDefinition.addContainer("TheContainer", {
environment: {
// DB_HOST: dbInstance.instancePrivateIp,
DB_NAME: "postgres",
DB_PORT: "5432",
DRIVER_TABLE_NAME: driverDataTable.tableName,
GPS_CALCULATED_LAP_DATA_TABLE: gpsCalculatedLapDataTable.tableName,
LAP_TABLE_NAME: lapDataTable.tableName,
Expand Down Expand Up @@ -360,6 +351,22 @@ TelemetryECSTaskDefinition.addContainer("TheContainer", {
TelemetryBackendSecretsManagerCertificate,
),
CHAIN: ecs.Secret.fromSecretsManager(TelemetryBackendSecretsManagerChain),
DATABASE_HOST: ecs.Secret.fromSecretsManager(
TimescaleConnectionString,
"DATABASE_HOST",
),
DATABASE_PASSWORD: ecs.Secret.fromSecretsManager(
TimescaleConnectionString,
"DATABASE_PASSWORD",
),
DATABASE_PORT: ecs.Secret.fromSecretsManager(
TimescaleConnectionString,
"DATABASE_PORT",
),
DATABASE_USERNAME: ecs.Secret.fromSecretsManager(
TimescaleConnectionString,
"DATABASE_USERNAME",
),
MQTT_PASSWORD: ecs.Secret.fromSecretsManager(
TelemetryBackendSecretsManagerMQTTCredentials,
"password",
Expand All @@ -368,21 +375,13 @@ TelemetryECSTaskDefinition.addContainer("TheContainer", {
TelemetryBackendSecretsManagerMQTTCredentials,
"username",
),
POSTGRES_PASSWORD: ecs.Secret.fromSecretsManager(
TelemetryBackendSecretsDatabaseCredentials,
"POSTGRES_PASSWORD",
), // pass DB password
POSTGRES_USERNAME: ecs.Secret.fromSecretsManager(
TelemetryBackendSecretsDatabaseCredentials,
"POSTGRES_USERNAME",
), // pass DB username,
PRIVATE_KEY: ecs.Secret.fromSecretsManager(
TelemetryBackendSecretsManagerPrivKey,
),
},
});

// Allow ECS Task to read the Secrets Manager Store
// Allow ECS Task to read the TimescaleDB connection string
TelemetryBackendSecretsManagerPrivKey.grantRead(
TelemetryECSTaskDefinition.taskRole,
);
Expand All @@ -395,6 +394,7 @@ TelemetryBackendSecretsManagerCertificate.grantRead(
TelemetryBackendSecretsManagerMQTTCredentials.grantRead(
TelemetryECSTaskDefinition.taskRole,
);
TimescaleConnectionString.grantRead(TelemetryECSTaskDefinition.taskRole);

const TelemetryBackendVPCSecurityGroup = ec2.SecurityGroup.fromSecurityGroupId(
TelemetryBackendStack,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { useAppState } from "@/stores/useAppState";

function BottomInformationContainer() {
const { currentAppState, setCurrentAppState } = useAppState();
const { battery, motor, mppt } = usePIS();
const dataArray = [battery, motor, mppt].filter(
const { battery, mbms, motor, mppt } = usePIS();
const dataArray = [battery, motor, mppt, mbms].filter(
(data) => data !== undefined,
) as I_PIS[];
const lookupTable = useFavouriteLookupTable(dataArray);
Expand All @@ -28,6 +28,18 @@ function BottomInformationContainer() {
[currentAppState, favourites],
);

const formatPathLabel = (path: string) =>
path
.split(".")
.map(
(segment) =>
segment
.replace(/([a-z])([A-Z])/g, "$1 $2") // lowerUpper → lower Upper
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2") // ABCDef → ABC Def
.replace(/([a-zA-Z])(\d)/g, "$1 $2"), // Unit3 → Unit 3
)
.join(" "); // use " " or " →" or " / " if preferred

return (
<div className="align-middle">
<div className="flex h-full flex-row flex-wrap justify-evenly gap-4 pt-1 text-center text-base md:gap-2 2xl:text-xl">
Expand All @@ -37,7 +49,7 @@ function BottomInformationContainer() {
className="text-xs hover:cursor-pointer 2xl:text-sm"
onClick={() => handleRemoveFavourite(favourite)}
>
{favourite}
{formatPathLabel(favourite)}
</div>
<div className="text-helios">
{/*Search for the value associated with the favourite name string in the lookupTable */
Expand Down
65 changes: 40 additions & 25 deletions packages/client/src/components/transformers/PISTransformer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,33 +88,31 @@ function FieldDataFormatter(props: FieldDataFormatterProps): JSX.Element {

type FieldPrinterProps = {
field: I_PISField;
fieldPath: string;
};

function FieldPrinter(props: FieldPrinterProps): JSX.Element {
const { setCurrentAppState } = useAppState();
const { field } = props;
const { field, fieldPath } = props;

const handleAddToFavourites = useCallback(() => {
const storedFavourites = localStorage.getItem("favourites");
const parsedFavourites: string[] = storedFavourites
? (JSON.parse(storedFavourites) as string[])
: [];

if (
!parsedFavourites.some((fav) => fav === field.name) &&
typeof field.name === "string"
) {
if (parsedFavourites.length === 8 && typeof field.name === "string") {
if (!parsedFavourites.includes(fieldPath)) {
if (parsedFavourites.length === 8) {
parsedFavourites.shift();
}
parsedFavourites.push(field.name);
parsedFavourites.push(fieldPath);
setCurrentAppState((prev) => ({
...prev,
favourites: parsedFavourites,
}));
localStorage.setItem("favourites", JSON.stringify(parsedFavourites));
}
}, [field.name, setCurrentAppState]);
}, [fieldPath, setCurrentAppState]);

if (
field.fstring !== undefined &&
Expand All @@ -128,17 +126,15 @@ function FieldPrinter(props: FieldPrinterProps): JSX.Element {

return (
<div className="group mt-1 flex items-center justify-between text-xs">
{!field.isFault && (
<span
className="mt-1 hidden cursor-pointer items-center whitespace-nowrap text-xs font-bold text-helios group-hover:flex"
onClick={handleAddToFavourites}
>
Add to Favourites
</span>
)}
<span
className="mt-1 hidden cursor-pointer items-center whitespace-nowrap text-xs font-bold text-helios group-hover:flex"
onClick={handleAddToFavourites}
>
Add to Favourites
</span>
<div className="flex w-full items-center justify-between gap-2">
<p
className={`mt-1 flex items-center justify-between text-xs ${field.isFault ? "" : "group-hover:hidden"}`}
className={`mt-1 flex items-center justify-between text-xs group-hover:hidden`}
>
{field.name}
</p>
Expand All @@ -151,10 +147,11 @@ function FieldPrinter(props: FieldPrinterProps): JSX.Element {
type FieldsPrinterProps = {
fields: I_PISField[];
depth?: number;
basePath: string; // NEW
};

function FieldsPrinter(props: FieldsPrinterProps): JSX.Element {
const { depth = 0, fields } = props;
const { basePath, depth = 0, fields } = props;
const isFullscreen = useFullscreen();

// get the max height class based on depth, but only do this if not in fullscreen
Expand All @@ -170,30 +167,39 @@ function FieldsPrinter(props: FieldsPrinterProps): JSX.Element {
className={`block overflow-x-hidden md:grid md:grid-cols-3 md:gap-x-2 lg:block lg:overflow-x-hidden ${getMaxHeightClass()}`}
>
{fields.map((field, index) => (
<FieldPrinter field={field} key={index} />
<FieldPrinter
field={field}
fieldPath={`${basePath}.${field.name}`}
key={index}
/>
))}
</div>
);
}

type PIStransformerProps = {
root: I_PIS;
depth?: number;
path?: string[];
};

function PISTransformer(props: PIStransformerProps): JSX.Element {
const { depth = 0, root } = props;
const { depth = 0, path = [], root } = props;

const formatKey = (key: string): string => {
return key
.replace(/([a-z])([A-Z])/g, "$1 $2") // Add space between lowercase and uppercase
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2") // Add space between consecutive uppercase followed by lowercase
.replace(/([a-zA-Z])(\d)/g, "$1 $2"); // Add space between letters and numbers
.replace(/([a-z])([A-Z])/g, "$1 $2")
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
.replace(/([a-zA-Z])(\d)/g, "$1 $2");
};

return (
root && (
<div className="flex size-full flex-col gap-x-2 lg:h-[375px] lg:flex-wrap xl:h-[326px]">
{Object.keys(root).map((key, index) => {
const value = root[key];
const newPath = [...path, key]; // Track path

return (
<div className={`flex flex-col`} id={key} key={index}>
<div className="flex w-full items-center justify-evenly border-b-2 border-helios">
Expand All @@ -205,10 +211,19 @@ function PISTransformer(props: PIStransformerProps): JSX.Element {
{formatKey(key)}
</p>
</div>

{Array.isArray(value) ? (
<FieldsPrinter depth={depth + 1} fields={value} />
<FieldsPrinter
basePath={newPath.join(".")}
depth={depth + 1}
fields={value}
/>
) : (
<PISTransformer depth={depth + 1} root={value as I_PIS} />
<PISTransformer
depth={depth + 1}
path={newPath}
root={value as I_PIS}
/>
)}
</div>
);
Expand Down
Loading