Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial commit for cloudwatch alarm chat example #5

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions cloudwatch-alarm-chat/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
45 changes: 45 additions & 0 deletions cloudwatch-alarm-chat/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
**/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# amplify
.amplify
amplify_outputs*
amplifyconfiguration*
75 changes: 75 additions & 0 deletions cloudwatch-alarm-chat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# CloudWatch Alarms Chat: AI-Powered Monitoring Assistant

CloudWatch Alarms Chat is a Next.js application that provides an AI-powered conversation interface for interacting with AWS CloudWatch alarms.

The application allows users to query alarm information, describe specific alarms, and retrieve metric data points through a chat interface.

This example uses:

- AWS Amplify Gen2
- NextJS App router
- Mantine UI components

Key Files:
- `amplify/backend.ts`: Defines the Amplify backend configuration
- `amplify/data/resource.ts`: Contains the data schema with conversations and generation
- `src/app/page.tsx`: Main page component for the application
- `src/components/Chat.tsx`: Chat component for the AI conversation interface

## Usage Instructions

### Installation

Prerequisites:
- Node.js 18+ installed
- AWS account that has been set up for AWS Amplify and has access to Anthropic Claude models in Amazon Bedrock

Steps:
1. Clone the repository and `cd` into the `claudwatch-alarms-chat` directory
2. Install dependencies:
```
npm install
```
3. Run Amplify sandbox to spin up a sandbox cloud backend
```
npx ampx sandbox
```
You can optionally add `--stream-function-logs` to see the logs stream in your CLI
```
npx ampx sandbox --stream-function-logs
```
Once deployment is complete, Amplify sandbox enters watch mode. Any changes to tracked files will trigger a new deployment. You can exit if you don't plan to make further modifications.
4. Start the development server:
```
npm run dev
```
5. Open your browser and navigate to `http://localhost:3000` (port may be different)

### Common Use Cases

1. Querying CloudWatch Alarms:
- In the chat interface, ask: "Show me all CloudWatch alarms in oregon"

2. Describing a Specific Alarm:
- Ask: "Describe the alarm named 'High CPU Utilization'"

3. Retrieving Metric Data:
- Request: "Show me the datapoints related to this alarm"

## Infrastructure

The project uses AWS Amplify to define and manage its infrastructure. Key resources include:

Amplify Functions:
- `getAlarms`: Retrieves CloudWatch alarms
- `describeAlarm`: Provides detailed information about a specific alarm
- `getMetricDataPoints`: Fetches metric data points for CloudWatch metrics

IAM Policies:
- Policies are attached to functions to allow:
- Invoking Bedrock models
- Describing CloudWatch alarms
- Retrieving CloudWatch metric data

Auth:
- Uses Amazon Cognito for user authentication
11 changes: 11 additions & 0 deletions cloudwatch-alarm-chat/amplify/auth/resource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineAuth } from '@aws-amplify/backend';

/**
* Define and configure your auth resource
* @see https://docs.amplify.aws/gen2/build-a-backend/auth
*/
export const auth = defineAuth({
loginWith: {
email: true,
},
});
43 changes: 43 additions & 0 deletions cloudwatch-alarm-chat/amplify/backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data, conversationHandler, getAlarms, describeAlarm, getMetricDataPoints} from './data/resource';
import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam';

const backend = defineBackend({
getAlarms,
describeAlarm,
getMetricDataPoints,
data,
auth,
conversationHandler,
});

backend.conversationHandler.resources.lambda.addToRolePolicy(
new PolicyStatement({
resources: [
'arn:aws:bedrock:*::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0',
'arn:aws:bedrock:*:*:inference-profile/us.anthropic.claude-3-5-sonnet-20241022-v2:0'
],
actions: [
'bedrock:InvokeModelWithResponseStream'
],
})
);

backend.getAlarms.resources.lambda.addToRolePolicy(new PolicyStatement({
actions: ['cloudwatch:DescribeAlarms'],
resources: [`arn:aws:cloudwatch:*:${process.env.CDK_DEFAULT_ACCOUNT}:*`],
effect: Effect.ALLOW,
}))

backend.describeAlarm.resources.lambda.addToRolePolicy(new PolicyStatement({
actions: ['cloudwatch:DescribeAlarms'],
resources: [`arn:aws:cloudwatch:*:${process.env.CDK_DEFAULT_ACCOUNT}:*`],
effect: Effect.ALLOW,
}))

backend.getMetricDataPoints.resources.lambda.addToRolePolicy(new PolicyStatement({
actions: ['cloudwatch:GetMetricData'],
resources: [`*`],
effect: Effect.ALLOW,
}))
3 changes: 3 additions & 0 deletions cloudwatch-alarm-chat/amplify/data/conversationHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { handleConversationTurnEvent } from '@aws-amplify/ai-constructs/conversation/runtime';

export const handler = handleConversationTurnEvent;
54 changes: 54 additions & 0 deletions cloudwatch-alarm-chat/amplify/data/describeAlarm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { CloudWatch } from '@aws-sdk/client-cloudwatch';
import { Schema } from './resource';

const cloudwatch = new CloudWatch();

export const handler: Schema["describeAlarm"]["functionHandler"] = async (event) => {
try {
const {alarmName = ""} = event.arguments;

if (!alarmName) {
return {
statusCode: 400,
error: 'Alarm name is required'
};
}

const params = {
AlarmNames: [alarmName]
};

const response = await cloudwatch.describeAlarms(params);

if (!response.MetricAlarms || response.MetricAlarms.length === 0) {
return {
statusCode: 404,
error: 'Alarm not found',
};
}
const alarmDetails = response.MetricAlarms[0];

const returnObject = {
name: alarmDetails?.AlarmName,
description: alarmDetails?.AlarmDescription,
state: alarmDetails?.StateValue,
threshold: alarmDetails?.Threshold,
lastUpdate: alarmDetails?.StateUpdatedTimestamp ? new Date(alarmDetails.StateUpdatedTimestamp).toISOString() : null,
namespace: alarmDetails?.Namespace,
dimensions: alarmDetails?.Dimensions,
deeplink: alarmDetails?.AlarmArn ? `https://${alarmDetails.AlarmArn.split(':')[3]}.console.aws.amazon.com/cloudwatch/home?region=${alarmDetails.AlarmArn.split(':')[3]}#alarmsV2:alarm/${alarmDetails.AlarmName}` : null
}

return {
statusCode: 200,
alarmDetails: JSON.stringify(returnObject)
}

} catch (error) {
console.error('Error describing alarm:', error);
return {
statusCode: 500,
error: 'Internal server error'
};
}
};
32 changes: 32 additions & 0 deletions cloudwatch-alarm-chat/amplify/data/getAlarms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { CloudWatch } from '@aws-sdk/client-cloudwatch';
import { Schema } from './resource';

const cloudwatch = new CloudWatch();

export const handler: Schema["getAlarms"]["functionHandler"] = async (event) => {
const { prefix = '' } = event.arguments;
try {
const response = await cloudwatch.describeAlarms({
AlarmNamePrefix: prefix || undefined
});

return {
statusCode: 200,
body: JSON.stringify({
message: 'Successfully retrieved CloudWatch alarms',
alarms: response.MetricAlarms
})
};

} catch (error) {
console.error('Error retrieving CloudWatch alarms:', error);

return {
statusCode: 500,
body: JSON.stringify({
message: 'Error retrieving CloudWatch alarms',
error: error instanceof Error ? error.message : 'Unknown error'
})
};
}
};
67 changes: 67 additions & 0 deletions cloudwatch-alarm-chat/amplify/data/getMetricDataPoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { CloudWatch } from '@aws-sdk/client-cloudwatch';

const cloudwatch = new CloudWatch();

export const handler = async (event: any) => {
try {
const { namespace, metricName, dimensionName, dimensionValue } = event.arguments;

// Calculate time range for last hour
const endTime = new Date();
const startTime = new Date(endTime.getTime() - (60 * 60 * 1000)); // 1 hour ago

const params = {
MetricDataQueries: [
{
Id: 'm1',
MetricStat: {
Metric: {
Namespace: namespace,
MetricName: metricName,
Dimensions: [
{
Name: dimensionName,
Value: dimensionValue
}
]
},
Period: process.env.METRIC_PERIOD ? parseInt(process.env.METRIC_PERIOD) : 300, // Period in seconds from env var
Stat: 'Average'
}
}
],
StartTime: startTime,
EndTime: endTime
};

const data = await cloudwatch.getMetricData(params);
if (!data.MetricDataResults || data.MetricDataResults.length === 0) {
return {
statusCode: 404,
error: 'No metric data found'
}
}

const label = data.MetricDataResults[0].Label
const datapoints = data.MetricDataResults[0].Timestamps?.map((timestamp, index) => {
return {
timestamp: timestamp,
value: data.MetricDataResults![0].Values![index]
}
});

const returnObject = {
statusCode: 200,
label: data.MetricDataResults[0].Label,
datapoints: datapoints,
};
return returnObject;

} catch (error) {
console.log('Error getting datapoints:', error)
return {
statusCode: 500,
error: 'Internal server error'
};
}
};
Loading