├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── images
├── create_a_custom_widget_1.png
└── create_a_custom_widget_2.png
├── samples
├── athenaQuery
│ ├── athenaQuery.js
│ ├── dashboard.json
│ ├── permissions.yaml
│ └── tags
├── awsCall
│ ├── awsCall.js
│ ├── awsCall.py
│ ├── dashboard.json
│ ├── permissions.yaml
│ └── tags
├── cloudWatchBitmapGraph
│ ├── cloudWatchBitmapGraph.js
│ ├── cloudWatchBitmapGraph.py
│ ├── dashboard.json
│ ├── permissions.yaml
│ └── tags
├── cloudWatchMetricDataTable
│ ├── cloudWatchMetricDataTable.js
│ ├── dashboard.json
│ ├── permissions.yaml
│ └── tags
├── codeDeploy
│ ├── codeDeploy.js
│ ├── dashboard.json
│ ├── permissions.yaml
│ └── tags
├── costExplorerReport
│ ├── costExplorerReport.js
│ ├── dashboard.json
│ ├── permissions.yaml
│ └── tags
├── debugger
│ ├── dashboard.json
│ ├── debugger.js
│ ├── debugger.py
│ ├── permissions.yaml
│ └── tags
├── ec2Table
│ ├── dashboard.json
│ ├── ec2Table.js
│ ├── permissions.yaml
│ └── tags
├── echo
│ ├── dashboard.json
│ ├── echo.js
│ ├── echo.py
│ ├── permissions.yaml
│ └── tags
├── emailDashboardSnapshot
│ ├── dashboard.json
│ ├── emailDashboardSnapshot.py
│ ├── permissions.yaml
│ └── tags
├── fetchURL
│ ├── dashboard.json
│ ├── fetchURL.js
│ ├── fetchURL.py
│ ├── permissions.yaml
│ └── tags
├── helloWorld
│ ├── dashboard.json
│ ├── helloWorld.js
│ ├── helloWorld.py
│ ├── permissions.yaml
│ └── tags
├── includeTextWidget
│ ├── dashboard.json
│ ├── includeTextWidget.js
│ ├── includeTextWidget.py
│ ├── permissions.yaml
│ └── tags
├── logsInsightsQuery
│ ├── dashboard.json
│ ├── logsInsightsQuery.js
│ ├── permissions.yaml
│ └── tags
├── rssReader
│ ├── dashboard.json
│ ├── permissions.yaml
│ ├── rssReader.py
│ └── tags
├── s3GetObject
│ ├── dashboard.json
│ ├── permissions.yaml
│ ├── s3GetObject.js
│ ├── s3GetObject.py
│ └── tags
├── simplePie
│ ├── dashboard.json
│ ├── permissions.yaml
│ ├── simplePie.js
│ ├── simplePie.py
│ └── tags
└── snapshotDashboardToS3
│ ├── dashboard.json
│ ├── permissions.yaml
│ ├── snapshotDashboardToS3.py
│ └── tags
└── scripts
├── build-assets
└── lib
└── bashFunctions
/.gitignore:
--------------------------------------------------------------------------------
1 | # build artifacts
2 | build
3 |
4 | # macOS
5 | .DS_Store
6 |
7 | # IDE
8 | .idea
9 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *main* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 |
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CloudWatch Custom Widgets Samples
2 |
3 | [](LICENSE)
4 | [](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/WhatIsCloudWatch.html)
5 |
6 | The samples in this project demonstrate several uses of Custom Widgets within a CloudWatch Dashboard.
7 |
8 |
60 |
61 | A **Custom Widget** is a [CloudWatch Dashboards](https://aws.amazon.com/blogs/aws/cloudwatch-dashboards-create-use-customized-metrics-views/) widget that can display virtually anything you want. Custom Widgets enables you to add custom visualizations, display information from multiple sources or add custom controls like buttons to take actions directly in a CloudWatch Dashboard. Custom Widgets are powered by custom Lambda functions, enabling complete control over the content, layout, and interactions. You can add custom widgets programmatically using the AWS SDK, CLI and CloudFormation.
62 |
63 |
Why would I use Custom Widgets?
64 |
65 | Custom Widgets is a simple way to build a custom data view or tool on a dashboard. There's no complicated web framework to learn, it's completely serverless. If you can write code in Lambda and create HTML then you can create a useful custom widget.
66 |
67 |
Sounds great, how do I sign up?
68 |
69 |
Option A: Using the CloudWatch Console
70 |
71 | The samples are already available in the CloudWatch Console, from a CloudWatch dashboard you can click the **Add widget** button and then select **Custom widget**. The samples found within this repository are available for a 1-click quick creation:
72 |
73 |
82 |
83 | 1. Clone the repo
84 | 2. Run the build script: `scripts/build-assets`
85 | 3. YAML templates will be generated inside the `build` folder e.g.:
86 | ```
87 | build
88 | └── cfn
89 | ├── customWidgetSample1-js.yaml
90 | ├── customWidgetSample2-py.yaml
91 | ├── customWidgetSample3-js.yaml
92 | ├── customWidgetSample4-py.yaml
93 | ├── ...
94 | └── customWidgetSampleN-py.yaml
95 | ```
96 | 4. The templates can be deployed with the [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html) web console (or the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html))
97 | 5. Modify them or adjust them to suit your own use case and needs!
98 |
99 |
Option C: Manually from a CloudWatch Dashboard
100 |
101 | Alternatively, add this entry into the **widgets** array of any CloudWatch Dashboard - go to **Actions -> View/edit source:**
102 |
103 | ``` json
104 | {
105 | "type": "custom"
106 | }
107 | ```
108 |
109 |
Even quicker - 2-click samples
110 |
111 | The following links launch a sample Custom Widget into your AWS account, along with a sample dashboard showing how the widget works. To launch:
112 | * if you're not happy with default region of **us-east-1 (N. Virginia)** switch region at top right of AWS Console
113 | * change the function name if you want to use it as a starter widget of your own
114 | * tick the **I acknowledge that AWS CloudFormation might create IAM resources.** checkbox
115 | * click orange **Create stack** button
116 |
117 | CloudFormation will create the custom widget Lambda function and sample CloudWatch Dashboard that uses it for you within a minute. Once the stack is created check the **Outputs** tab for links to the Lambda function code and sample dashboard. Edit the widget code directly in Lambda console and test changes directly in CloudWatch dashboard.
118 |
119 |
JavaScript samples
120 |
121 | * [Hello world](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetHelloWorld-js&template=customWidgets/customWidgetHelloWorld-js.yaml¶m_DoCreateExampleDashboard=Yes): a very simple starter widget
122 | * [Custom widget debugger](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetDebugger-js&template=customWidgets/customWidgetDebugger-js.yaml¶m_DoCreateExampleDashboard=Yes): a helpful debugger widget that displays useful information about the Lambda runtime environment
123 | * [Echo](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetEcho-js&template=customWidgets/customWidgetEcho-js.yaml¶m_DoCreateExampleDashboard=Yes): a simple echoer. Test how HTML will appear in a widget without writing a widget
124 | * [Run Athena queries](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetAthenaQuery-js&template=customWidgets/customWidgetAthenaQuery-js.yaml¶m_DoCreateExampleDashboard=Yes): run and edit queries against [Amazon Athena](https://aws.amazon.com/athena/)
125 | * [Call AWS API](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetAwsCall-js&template=customWidgets/customWidgetAwsCall-js.yaml¶m_DoCreateExampleDashboard=Yes): call any read-only AWS API and display results as JSON
126 | * [EC2 Table](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetEc2Table-js&template=customWidgets/customWidgetEc2Table-js.yaml¶m_DoCreateExampleDashboard=Yes): display top EC2 instances by CPU, plus a Reboot button (disabled by default as IAM role created only has read-only permissions needed to read EC2 instances and metric data)
127 | * [Fast CloudWatch bitmap graphs](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetCloudWatchBitmapGraph-js&template=customWidgets/customWidgetCloudWatchBitmapGraph-js.yaml¶m_DoCreateExampleDashboard=Yes): render CloudWatch graphs server-side, for faster display
128 | * [CloudWatch metric data as table](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetCloudWatchMetricDataTable-js&template=customWidgets/customWidgetCloudWatchMetricDataTable-js.yaml¶m_DoCreateExampleDashboard=Yes): display raw CloudWatch metric data in table
129 | * [Code Deployments](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetCodeDeploy-js&template=customWidgets/customWidgetCodeDeploy-js.yaml¶m_DoCreateExampleDashboard=Yes): display [CodeDeploy](https://aws.amazon.com/codedeploy/) deployments
130 | * [Cost explorer report](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetCostExplorerReport-js&template=customWidgets/customWidgetCostExplorerReport-js.yaml¶m_DoCreateExampleDashboard=Yes): displays a report on cost of each AWS service for a selected time range
131 | * [Display content of external URL](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetFetchURL-js&template=customWidgets/customWidgetFetchURL-js.yaml¶m_DoCreateExampleDashboard=Yes): display externally accessible HTML content
132 | * [Include text widget from dashboard](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetIncludeTextWidget-js&template=customWidgets/customWidgetIncludeTextWidget-js.yaml¶m_DoCreateExampleDashboard=Yes): displays the first text widget from a specified CloudWatch Dashboard
133 | * [Run Logs Insights queries](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetLogsInsightsQuery-js&template=customWidgets/customWidgetLogsInsightsQuery-js.yaml¶m_DoCreateExampleDashboard=Yes): run and edit queries against [CloudWatch Logs Insights](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AnalyzingLogData.html)
134 | * [Display S3 object](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetS3GetObject-js&template=customWidgets/customWidgetS3GetObject-js.yaml¶m_DoCreateExampleDashboard=Yes): display object from S3 bucket in your account
135 | * [Simple SVG pie chart](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetSimplePie-js&template=customWidgets/customWidgetSimplePie-js.yaml¶m_DoCreateExampleDashboard=Yes): simple example of graphical SVG-based widget
136 |
137 |
Python samples
138 |
139 | * [Hello world](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetHelloWorld-py&template=customWidgets/customWidgetHelloWorld-py.yaml¶m_DoCreateExampleDashboard=Yes): a very simple starter widget
140 | * [Custom widget debugger](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetDebugger-py&template=customWidgets/customWidgetDebugger-py.yaml¶m_DoCreateExampleDashboard=Yes): a helpful debugger widget that displays useful information about the Lambda runtime environment
141 | * [Echo](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetEcho-py&template=customWidgets/customWidgetEcho-py.yaml¶m_DoCreateExampleDashboard=Yes): a simple echoer. Test how HTML will appear in a widget without writing a widget
142 | * [Call AWS API](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetAwsCall-py&template=customWidgets/customWidgetAwsCall-py.yaml¶m_DoCreateExampleDashboard=Yes): call any read-only AWS API and display results as JSON
143 | * [Fast CloudWatch bitmap graphs](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetCloudWatchBitmapGraph-py&template=customWidgets/customWidgetCloudWatchBitmapGraph-py.yaml¶m_DoCreateExampleDashboard=Yes): render CloudWatch graphs server-side, for faster display
144 | * [Send dashboard snapshot by email](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetEmailDashboardSnapshot-py&template=customWidgets/customWidgetEmailDashboardSnapshot-py.yaml¶m_DoCreateExampleDashboard=Yes): take a snapshot of the current dashboard and send it to email recipient
145 | * [Display content of an external URL](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetFetchURL-py&template=customWidgets/customWidgetFetchURL-py.yaml¶m_DoCreateExampleDashboard=Yes): display externally accessible HTML content
146 | * [Include text widget from dashboard](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetIncludeTextWidget-py&template=customWidgets/customWidgetIncludeTextWidget-py.yaml¶m_DoCreateExampleDashboard=Yes): displays the first text widget from a specified CloudWatch Dashboard
147 | * [RSS Reader](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetRssReader-py&template=customWidgets/customWidgetRssReader-py.yaml¶m_DoCreateExampleDashboard=Yes): display RSS feeds
148 | * [Display S3 object](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetS3GetObject-py&template=customWidgets/customWidgetS3GetObject-py.yaml¶m_DoCreateExampleDashboard=Yes): display object from S3 bucket in your account
149 | * [Simple SVG pie chart](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetSimplePie-py&template=customWidgets/customWidgetSimplePie-py.yaml¶m_DoCreateExampleDashboard=Yes): simple example of graphical SVG-based widget
150 | * [Snapshot dashboard to S3](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetSnapshotDashboardToS3-py&template=customWidgets/customWidgetSnapshotDashboardToS3-py.yaml¶m_DoCreateExampleDashboard=Yes): take a snapshot of the current dashboard and store it in S3
151 |
152 |
How does a widget work?
153 |
154 | Your CloudWatch Dashboard:
155 | 1. calls the Lambda function containing the widget code. The function is passed any custom parameters defined in the widget
156 | 2. the Lambda function is expected to return a string of HTML
157 | 3. the CloudWatch Dashboard displays the HTML
158 | 4. if the response is JSON it is displayed as formatted JSON
159 |
160 |
Can the returned HTML contain any HTML tags?
161 |
162 | Almost all HTML tags are supported. CSS styles and SVGs can be used for building sophisticated and graphically rich views.
163 |
164 | However, for security reasons _JavaScript_ is not allowed in the returned HTML. If the HTML string from the Lambda function contains any Javascript it will be "cleaned" from the HTML before rendering. Also, the **<iframe>** tag is forbidden.
165 |
166 | Removing Javascript is done to prevent privilege escalation issues, where the writer of the Lambda function can inject code that would run with the possibly higher privileges of the user viewing the widget in the CloudWatch Console.
167 |
168 |
Is interactivity possible in the returned HTML?
169 |
170 | Yes. Even though JavaScript is not allowed, there are a number of avenues that allow interactivity with the returned HTML:
171 |
172 | * Any element in the returned HTML can be tagged with special configuration in a **<cwdb-action>** tag that trigger display information in popups, ask for confirmation on clicks can call any Lambda function when that element is clicked. This allows for example the definition of buttons that will call any AWS API via a Lambda function. The returned HTML can be set to either replace the existing Lambda widget's content, or display inside a modal.
173 | * HTML can include links that open new consoles, other customer pages or load other dashboards
174 | * HTML can include the 'title' attribute for an element, that gives additional information if the user hovers over that element
175 | * HTML can include CSS selectors such as :hover which can trigger animations or different CSS effects
176 |
177 |
What can I do with the cwdb-action tag?
178 |
179 | Example of how to create a button that reboots an EC2 instance via a Lambda function call, displaying the success or failure of the call in a popup:
180 |
181 | ``` html
182 | Reboot Instance
183 |
184 | { "instanceId": "i-342389adbfef" }
185 |
186 | ```
187 |
188 |
cwdb-action: Definition and usage
189 |
190 | The **<cwdb-action>** element defines a behavior on the previous element. The content of the **<cwdb-action>** is either HTML to display or JSON of parameters to pass to the call to a Lambda function.
191 |
192 | **Attributes**
193 | General definition:
194 | ``` html
195 |
201 |
202 | html | params in JSON
203 |
204 |
205 | ```
206 |
207 | | Attribute | Value | Description |
208 | | --- | --- | --- |
209 | | **action** | call | html | Two actions are supported, _call_ a Lambda function or (default) display _html_ contained within **<cwdb-action>** |
210 | | **confirmation** | _message_ | Displays a confirmation message that needs to be acknowledged before the action is taken (allowing customer to cancel) |
211 | | **display** | popup | widget | Where should action result be displayed. Can be either in a _popup_ or (default) replace the content of the _widget_ itself |
212 | | **endpoint** | _arn of lambda function_ | The ARN of the Lambda function to call. Required if **action** is set to **call** |
213 | | **event** | click | dblclick | mouseenter | The event on the previous element which triggers the action. The _mouseenter_ event can be used only in combination with the _html_ action. The default is _click_ |
214 |
215 |
cwdb-action: Examples
216 |
217 | A link which displays more information in a popup:
218 | ``` html
219 | Click me for more info in popup
220 |
221 |
Big title
222 | More info about something important.
223 |
224 | ```
225 | A Next button (primary) which replaces content of widget with call to a Lambda:
226 | ``` html
227 | Next
228 |
229 | { "pageNum": 2 }
230 |
231 | ```
232 |
233 |
Can I call Custom Widget functions in other accounts?
234 |
235 | Yes. In order to help with sharing of functionality between multiple accounts owned by a customer, a Custom Widget function can be defined in one account and called from the dashboards of other accounts, as long as the correct permissions have been defined to allow access from other accounts. Follow [CloudWatch Cross-Account Cross-Region setup](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Cross-Account-Cross-Region.html) and make sure you allow the current account to invoke the custom widget Lambda function in the shared account(s).
236 |
237 | The CloudWatch Dashboard facilitates this by allowing the customer to pick a Lambda function by pasting a raw ARN to it into the Dashboard definition.
238 |
239 |
Can a Lambda function call a customer's internal services?
240 |
241 | If those services are accessible within an AWS VPC then the Lambda function can run within that VPC, thus allowing access to the customer's internal services/data.
242 |
243 |
Can access to Custom Widgets be restricted?
244 |
245 | Yes. Normal IAM policies can be applied so that IAM users can be allowlisted or blocklisted for Lambda execute permissions on all or particular Lambda functions.
246 |
247 | This allows customers to share a single dashboard with multiple users but lock down the view of particular Lambda widgets to users with higher privileges.
248 |
249 |
What is the 'Hello world' Custom Widget example?
250 |
251 | Below is the Javascript code for the [Hello world](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetHelloWorld-js&template=customWidgets/customWidgetHelloWorld-js.yaml¶m_DoCreateExampleDashboard=Yes) example:
252 |
253 | ``` javascript
254 | exports.handler = async (event) => {
255 | const name = event.name || 'friend';
256 | return `
Hello ${name}
`;
257 | };
258 | ```
259 |
260 | And here is the Python version of [Hello world](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetHelloWorld-py&template=customWidgets/customWidgetHelloWorld-py.yaml¶m_DoCreateExampleDashboard=Yes):
261 |
262 | ``` python
263 | def lambda_handler(event, context):
264 | name = event.get('name', 'friend')
265 | return f'
Hello {name}
'
266 | ```
267 |
268 |
Example of returning data from a call to an AWS function?
269 |
270 | Below is the [JavaScript code](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetS3GetObject-js&template=customWidgets/customWidgetS3GetObject-js.yaml¶m_DoCreateExampleDashboard=Yes) for displaying the HTML content of any file in an S3 bucket:
271 |
272 | ``` javascript
273 | const aws = require('aws-sdk');
274 |
275 | exports.handler = async (event) => {
276 | const region = event.region || process.env.AWS_REGION;
277 | const params = {
278 | Bucket: event.bucket,
279 | Key: event.key
280 | };
281 | const s3 = new aws.S3({ region });
282 | const result = await s3.getObject(params).promise();
283 |
284 | return result.Body.toString();
285 | };
286 |
287 | ```
288 |
289 | And the Python equivalent:
290 |
291 | ``` python
292 | def lambda_handler(event, context):
293 | region = event.get('region', os.environ['AWS_REGION'])
294 | s3 = boto3.client('s3', region_name=region)
295 | result = s3.get_object(Bucket=event['bucket'], Key=event['key'])
296 |
297 | return result['Body'].read().decode('utf-8')
298 | ```
299 |
300 | Another one, that can [call any AWS API](https://console.aws.amazon.com/cloudwatch/cfn.js?region=us-east-1&action=create&stackName=customWidgetAwsCall-js&template=customWidgets/customWidgetAwsCall-js.yaml¶m_DoCreateExampleDashboard=Yes) and display the results in pretty-fied JSON:
301 |
302 | ``` javascript
303 | const aws = require('aws-sdk');
304 |
305 | exports.handler = async (event) => {
306 | const service = event.service || 'CloudWatch';
307 | const api = event.api || 'listDashboards';
308 | const region = event.region || process.env.AWS_REGION;
309 | const params = event.params || {};
310 |
311 | if (!aws[service]) {
312 | throw `Unknown AWS service ${service}`;
313 | }
314 |
315 | const client = new aws[service]({ region });
316 |
317 | if (!client[api]) {
318 | throw `Unknown API ${api} for AWS service ${service}`;
319 | }
320 |
321 | return await client[api](params).promise();
322 | };
323 | ```
324 |
325 |
Can the user of a Custom Widget customize its behavior?
326 |
327 | Yes. All Lambda functions for HTML widgets can receive custom parameters from the dashboard, defined by the user on a per-widget basis. It is up to the Lambda function writer to decide what parameters will be accepted.
328 |
329 | When creating/modifying a custom widget a customer can:
330 |
331 | - select the Lambda function to call from any region in the account via a dropdown
332 | - enter a [Version or Alias](http://docs.aws.amazon.com/lambda/latest/dg/versioning-aliases.html) of the Lambda function to run a specific version of the function
333 | - enter a specific ARN for a Lambda function, which could be in a different account
334 | - list the custom parameters to be sent to the Lambda function, in either JSON or YAML form
335 | - set the title for the widget
336 | - choose when the widget should be updated (i.e. when the Lambda function should be called again). This can be on **refresh** when the dashboard auto-refreshes and/or when the widget is **resized** and/or when the dashboard **time range** is adjusted (including when graphs are zoomed into)
337 |
338 |
Does Custom Widgets refresh and auto-refresh?
339 |
340 | Yes. The refresh button will call all Lambda functions and re-render all Lambda widgets along with the CloudWatch metric graphs. The auto-refresh however will re-render as long as the **Refresh** option is selected (see previous question).
341 |
342 |
Can Custom Widgets be resized and moved around the dashboard?
343 |
344 | Yes. The Custom Widgets can be resized and moved around the dashboards as easily as the existing Text and Metric Graph widgets.
345 |
346 |
Can Custom Widgets react to the time range of the dashboard?
347 |
348 | Yes. The parameters passed to every call to a Custom Widget function will include the time range of the dashboard.
349 |
350 | They will also include the dimensions of the viewable area of the widget box, helping writers of the function design their HTML to fit the size of the Lambda widget's area as they see fit.
351 |
352 |
Is the Custom Widget passed any information by default?
353 |
354 | Yes. Every call to Lambda includes a parameter called **widgetContext** which has the following structure/contents:
355 |
356 | ``` json
357 | {
358 | "widgetContext": {
359 | "dashboardName": "Name-of-current-dashboard",
360 | "widgetId": "widget-16",
361 | "accountId": "XXXXXXXXXXXX",
362 | "locale": "en",
363 | "timezone": {
364 | "label": "UTC",
365 | "offsetISO": "+00:00",
366 | "offsetInMinutes": 0
367 | },
368 | "period": 300,
369 | "isAutoPeriod": true,
370 | "timeRange": {
371 | "mode": "relative",
372 | "start": 1512556923228,
373 | "end": 1512600123228,
374 | "relativeStart": 43200000,
375 | "zoom": {
376 | "start": 1627276030434,
377 | "end": 1627282956521
378 | }
379 | },
380 | "theme": "light",
381 | "linkCharts": true,
382 | "title": "Tweets for Amazon website problem",
383 | "forms": {
384 | "all": {}
385 | },
386 | "params": {
387 | "original": "param-to-widget"
388 | },
389 | "width": 588,
390 | "height": 369
391 | }
392 | }
393 | ```
394 |
395 | This gives the Lambda developer important widget context information such as:
396 |
397 | - current dashboard settings such as time range, zoom range, timezone, period settings, if charts are "linked" together and language setting of the console
398 | - width and height of visible area of the widget
399 | - widget title and unique id
400 | - content of all form fields within the widget – allowing widget to take input and send it to another Lambda call via **<cwdb-action>** tag
401 | - the calling account id
402 | - the original parameters configured for the widget, so that a button that calls the Lambda function again does not have to copy/paste original parameters
403 |
404 |
Is Lambda the only API that these widgets can call?
405 |
406 | Yes.
407 |
408 |
What is the default style of Custom Widget HTML elements?
409 |
410 | The default style of HTML elements such as links and tables will follow the styling of the CloudWatch Console and Dashboard.
411 |
412 |
Can I customize the style of the HTML elements?
413 |
414 | Yes. Styles of HTML elements can be set either via inline styles or including a stylesheet within the returned HTML (include **<style></style>** anywhere within the HTML).
415 |
416 |
Can the default styles be disabled?
417 |
418 | Yes. In order not to force customers to override and fight with the default CSS we also allow the default CSS to be disabled, under control of the returned HTML. Simply include a single HTML element that has a class of **cwdb-no-default-styles** , e.g.
419 |
420 | ```html
421 | I have default styling
422 | ```
423 |
424 |
Can I use Custom Widgets in my own website?
425 |
426 | Yes. [CloudWatch Dashboarding Sharing](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-dashboard-sharing.html) allows you to display a CloudWatch Dashboard outside the AWS console and embed it into other websites via the [iframe](https://www.w3schools.com/html/html_iframe.asp) tag.
427 |
428 | [CloudWatch Dashboarding Sharing](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-dashboard-sharing.html) supports sharing dashboards by:
429 | * share single dashboard with group of email addresses, using login with passwords
430 | * share single dashboard via a public (obscure) URL, login not required
431 | * Share all dashboards in an account and via third-party single sign-on (SSO) provider
432 |
433 | So add your Custom Widgets to a dashboard and share it. Once it is shared you will have a URL for the dashboard, in this form:
434 | ```
435 | https://cloudwatch.amazonaws.com/dashboard.html?dashboard=&context=
436 | ```
437 |
438 | This can then be embedded into a website with HTML similar to this:
439 |
440 | ```
441 |
442 | ```
443 |
444 |
Contributing
445 |
446 | Contributions are very welcome please checkout out the [contributing guidelines](CONTRIBUTING.md).
447 |
448 |
`;
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/samples/includeTextWidget/includeTextWidget.py:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # CloudWatch Custom Widget sample: display content of a text widget from other dashboard
5 | import base64
6 | import boto3
7 | import datetime
8 | import json
9 |
10 | DOCS = """
11 | ## Include Text Widget from CloudWatch Dashboard
12 | This widget displays the first text widget from a specified CloudWatch Dashboard.
13 |
14 | This is useful for embedding the same text context in multiple dashboards and update it from a central place. An example would be a menu of links between dashboards.
15 |
16 | ### Widget parameters
17 | Param | Description
18 | ---|---
19 | **dashboardName** | The name of the dashboard from which to load the text widget. The first text widget on that dashboard is loaded.
20 |
21 | ### Example parameters
22 | ``` yaml
23 | dashboardName: sharedMenu
24 | ```"""
25 |
26 | CSS = """"""
31 |
32 | def lambda_handler(event, context):
33 | if 'describe' in event:
34 | return DOCS
35 |
36 | cloudwatch = boto3.client('cloudwatch')
37 | dashboardName = event['dashboardName'] if 'dashboardName' in event else ''
38 |
39 | if dashboardName == '':
40 | return f"""{CSS}
'
69 | for index, field in enumerate(fields):
70 | split = field.split(".")
71 | if len(split) == 2:
72 | value = entry.find(split[0])
73 | if value is not None:
74 | attr = value.get(split[1])
75 | if attr is not None:
76 | html += f"{split[0]}: {attr} "
77 | else:
78 | value = entry.find(field)
79 | if value is not None and value.text is not None:
80 | # first field is the title
81 | if index == 0:
82 | html += f"
{value.text}
"
83 | else:
84 | html += f"{value.text} "
85 | html += '
'
86 |
87 | return CSS + html
--------------------------------------------------------------------------------
/samples/rssReader/tags:
--------------------------------------------------------------------------------
1 | - Key: cw-custom-widget
2 | Value: describe:readOnly
3 |
--------------------------------------------------------------------------------
/samples/s3GetObject/dashboard.json:
--------------------------------------------------------------------------------
1 | {
2 | "widgets": [
3 | {
4 | "type": "custom",
5 | "width": 24,
6 | "height": 12,
7 | "properties": {
8 | "endpoint": "${lambdaFunction.Arn}",
9 | "params": {
10 | "bucket": "custom-widget-demo-bucket",
11 | "key": "sample-report.html"
12 | },
13 | "title": "Sample S3 Report - Power stats"
14 | }
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/samples/s3GetObject/permissions.yaml:
--------------------------------------------------------------------------------
1 | - PolicyDocument:
2 | Version: 2012-10-17
3 | Statement:
4 | - Action:
5 | - s3:GetObject
6 | Effect: Allow
7 | Resource:
8 | - "*"
9 | PolicyName: s3GetObject
10 |
--------------------------------------------------------------------------------
/samples/s3GetObject/s3GetObject.js:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // SPDX-License-Identifier: MIT-0
3 |
4 | // CloudWatch Custom Widget sample: display an object from S3 bucket
5 | const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
6 |
7 | const DOCS = `
8 | ## S3 Get Object
9 | Displays the content of a file (usually HTML) stored in S3 in the current account.
10 |
11 | This is useful for displaying dynamic content stored and updated separately from a dashboard, such as the results of a daily or hourly report.
12 |
13 | ### Widget parameters
14 | Param | Description
15 | ---|---
16 | **bucket** | The name of the S3 bucket owned by this account
17 | **key** | The key of the S3 object
18 |
19 | ### Example parameters
20 | \`\`\` yaml
21 | bucket: custom-widget-demo-bucket
22 | key: sample-report.html
23 | \`\`\`
24 | `;
25 |
26 | exports.handler = async (event) => {
27 | if (event.describe) {
28 | return DOCS;
29 | }
30 |
31 | const region = event.region || process.env.AWS_REGION;
32 | const params = {
33 | Bucket: event.bucket,
34 | Key: event.key
35 | };
36 |
37 | const s3Client = new S3Client({ region });
38 | const command = new GetObjectCommand(params);
39 |
40 | const response = await s3Client.send(command);
41 | try {
42 | const bodyContents = await response.Body.transformToString();
43 | return bodyContents;
44 | } catch (e) {
45 | console.error(e);
46 | return `Error fetching S3 object: ${e.message}`;
47 | }
48 | };
49 |
--------------------------------------------------------------------------------
/samples/s3GetObject/s3GetObject.py:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # CloudWatch Custom Widget sample: display an object from S3 bucket
5 | DOCS = """
6 | ## S3 Get Object
7 | Displays the content of a file (usually HTML) stored in S3 in the current account.
8 |
9 | This is useful for displaying dynamic content stored and updated separately from a dashboard, such as the results of a daily or hourly report.
10 |
11 | ### Widget parameters
12 | Param | Description
13 | ---|---
14 | **bucket** | The name of the S3 bucket owned by this account
15 | **key** | The key of the S3 object
16 |
17 | ### Example parameters
18 | ``` yaml
19 | bucket: custom-widget-demo-bucket
20 | key: sample-report.html
21 | ```"""
22 |
23 | import boto3
24 | import json
25 | import os
26 |
27 | def lambda_handler(event, context):
28 | if 'describe' in event:
29 | return DOCS
30 |
31 | region = event.get('region', os.environ['AWS_REGION'])
32 | s3 = boto3.client('s3', region_name=region)
33 | result = s3.get_object(Bucket=event['bucket'], Key=event['key'])
34 |
35 | return result['Body'].read().decode('utf-8')
36 |
--------------------------------------------------------------------------------
/samples/s3GetObject/tags:
--------------------------------------------------------------------------------
1 | - Key: cw-custom-widget
2 | Value: describe:readOnly
3 |
--------------------------------------------------------------------------------
/samples/simplePie/dashboard.json:
--------------------------------------------------------------------------------
1 | {
2 | "widgets": [
3 | {
4 | "type": "custom",
5 | "width": 12,
6 | "height": 6,
7 | "properties": {
8 | "endpoint": "${lambdaFunction.Arn}",
9 | "updateOn": {
10 | "refresh": true
11 | },
12 | "params": {
13 | "slices": [
14 | {
15 | "label": "Sky",
16 | "value": 135,
17 | "color": "blue"
18 | },
19 | {
20 | "label": "Shady",
21 | "value": 22.5,
22 | "color": "#6E4435"
23 | },
24 | {
25 | "label": "Sunny",
26 | "value": 45,
27 | "color": "#DDAF59"
28 | },
29 | {
30 | "label": "Sky",
31 | "value": 135,
32 | "color": "blue"
33 | }
34 | ]
35 | },
36 | "title": "Pyramid"
37 | }
38 | },
39 | {
40 | "type": "custom",
41 | "width": 12,
42 | "height": 6,
43 | "properties": {
44 | "endpoint": "${lambdaFunction.Arn}",
45 | "updateOn": {
46 | "refresh": true
47 | },
48 | "params": {
49 | "slices": [
50 | {
51 | "value": 100,
52 | "label": "Right wall"
53 | },
54 | {
55 | "value": 130,
56 | "label": "Floor"
57 | },
58 | {
59 | "value": 130,
60 | "label": "Left wall"
61 | }
62 | ]
63 | },
64 | "title": "My living room corner"
65 | }
66 | },
67 | {
68 | "type": "custom",
69 | "width": 12,
70 | "height": 9,
71 | "properties": {
72 | "endpoint": "${lambdaFunction.Arn}",
73 | "updateOn": {
74 | "refresh": true
75 | },
76 | "params": {
77 | "legendHeight": 75,
78 | "slices": [
79 | {
80 | "value": 16,
81 | "label": "Give you up"
82 | },
83 | {
84 | "value": 16,
85 | "label": "Let you down"
86 | },
87 | {
88 | "value": 16,
89 | "label": "Run around and desert you"
90 | },
91 | {
92 | "value": 16,
93 | "label": "Make you cry"
94 | },
95 | {
96 | "value": 16,
97 | "label": "Say goodbye"
98 | },
99 | {
100 | "value": 16,
101 | "label": "Tell a lie and hurt you"
102 | },
103 | {
104 | "value": 5,
105 | "label": "Give, never gonna give"
106 | }
107 | ]
108 | },
109 | "title": "Rick Astley is \"never gonna ...\""
110 | }
111 | },
112 | {
113 | "type": "custom",
114 | "width": 12,
115 | "height": 9,
116 | "properties": {
117 | "endpoint": "${lambdaFunction.Arn}",
118 | "updateOn": {
119 | "refresh": true
120 | },
121 | "params": {
122 | "slices": [
123 | {
124 | "label": "Remaining",
125 | "value": 90
126 | },
127 | {
128 | "label": "Eaten",
129 | "color": "white",
130 | "value": 10
131 | }
132 | ]
133 | },
134 | "title": "Pizza consumption"
135 | }
136 | }
137 | ]
138 | }
139 |
--------------------------------------------------------------------------------
/samples/simplePie/permissions.yaml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/cloudwatch-custom-widgets-samples/e337b8de4d0f5edf906d7ac2b17d609248301cc8/samples/simplePie/permissions.yaml
--------------------------------------------------------------------------------
/samples/simplePie/simplePie.js:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // SPDX-License-Identifier: MIT-0
3 |
4 | // CloudWatch Custom Widget sample: draw a simple SVG pie chart
5 | const DOCS = `
6 | ## Simple Pie, using SVG
7 | This pie chart is a simple example of how SVG can be used to display graphical content in a custom widget.
8 |
9 | ### Widget parameters
10 | Param | Description
11 | ---|---
12 | **slices** | Array containing slice data. Each entry must contain **value** (numerical value for size of slice) and **label** (the slice's label)
13 | **legendHeight** | The height of the legend in pixels (optional, defaults to 25)
14 |
15 | ### Example parameters
16 | \`\`\` yaml
17 | ---
18 | legendHeight: 75
19 | slices:
20 | - value: 16
21 | label: Give you up
22 | - value: 16
23 | label: Let you down
24 | - value: 16
25 | label: Run around and desert you
26 | - value: 16
27 | label: Make you cry
28 | - value: 16
29 | label: Say goodbye
30 | - value: 16
31 | label: Tell a lie and hurt you
32 | - value: 5
33 | label: Give, never gonna give
34 | \`\`\`
35 | `;
36 |
37 | const PALETTE = [ '#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf', '#aec7e8', '#ffbb78', '#98df8a', '#ff9896', '#c5b0d5', '#c49c94', '#f7b6d2', '#c7c7c7', '#dbdb8d', '#9edae5' ];
38 | const SVG_RADIUS = 200;
39 | const css = margin => {
40 | return ``;
63 | }
64 |
65 | const getCoordinatesForAngle = (angle) => {
66 | const x = parseInt(Math.round(SVG_RADIUS + (SVG_RADIUS - 5) * Math.cos(Math.PI * angle / 180)));
67 | const y = parseInt(Math.round(SVG_RADIUS + (SVG_RADIUS - 5) * Math.sin(Math.PI * angle / 180)));
68 | return [x, y];
69 | };
70 |
71 | exports.handler = async (event) => {
72 | if (event.describe) {
73 | return DOCS;
74 | }
75 |
76 | const slices = event.slices;
77 | const total = slices.reduce((total, slice) => total + slice.value, 0);
78 | let startAngle, endAngle = -90;
79 |
80 | const slicesHtml = slices.map((slice, i) => {
81 | const angle = 360 * slice.value / total;
82 | startAngle = endAngle;
83 | endAngle = startAngle + angle;
84 | const [x1, y1] = getCoordinatesForAngle(startAngle);
85 | const [x2, y2] = getCoordinatesForAngle(endAngle);
86 |
87 | var d = `M200,200 L${x1},${y1} A195,195 0 ${((endAngle-startAngle > 180) ? 1 : 0)},1 ${x2},${y2} z`;
88 | const color = slice.color || PALETTE[i % PALETTE.length];
89 |
90 | // create a and append it to the