` to test only that specific file.
57 | ### Roadmap
58 | | Feature | Status |
59 | | ------------------------------------------------ | ------ |
60 | | Dynamically Fetch Logs From CloudWatch | ✅ |
61 | | Back-end Testing | ✅ |
62 | | Dark/Light Mode | ✅ |
63 | | Increase Testing Coverage | ⏳ |
64 | | Make Stream Content Linkable/Shareable | ⚡️ |
65 | | Add More Themes! | ⚡️ |
66 | | Improve Search Functionality | ⚡️ |
67 |
68 | - ✅ = Completed
69 | - ⏳ = In-Progress
70 | - ⚡️ = Backlog
71 |
72 | ### Contributing
73 | - Potential Iteration Ideas
74 | - Anything from the backlog
75 | - Add additional front-end tests
76 | - Add additional back-end tests
77 | - Migrate to AWS SDK 3
78 | - Implemnet Apache Solr for better search
79 | - Implement Grafana to make expanded dashboards
80 | - Improve or expand the capabilities of the JSON parser (see documentation)
81 |
82 | ## Contributors
83 |
84 |
85 |
86 |
87 |
88 |
89 | Conrad Preston
90 |
91 | 🖇️
92 | 🐙
93 | |
94 |
95 |
96 |
97 | Hoang Dang
98 |
99 | 🖇️
100 | 🐙
101 | |
102 |
103 |
104 |
105 | Luke Clarkson
106 |
107 | 🖇️
108 | 🐙
109 | |
110 |
111 |
112 |
113 | Nick C. Mason
114 |
115 | 🖇️
116 | 🐙
117 | |
118 |
119 |
120 |
121 | - 🖇️ = LinkedIn
122 | - 🐙 = Github
123 |
--------------------------------------------------------------------------------
/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | // Intentionally blank, prevents styles imports from erroring during test runs.
2 | module.exports = {};
3 |
--------------------------------------------------------------------------------
/__tests__/Console.test.js:
--------------------------------------------------------------------------------
1 | jest.mock("react-syntax-highlighter", () => {
2 | const SyntaxHighlighter = ({
3 | children,
4 | style,
5 | language,
6 | showLineNumbers,
7 | showInlineLineNumbers,
8 | lineNumberStyle,
9 | className,
10 | }) => {
11 | const content = Array.isArray(children)
12 | ? JSON.stringify(children, null, 2)
13 | : children;
14 |
15 | return (
16 |
17 | {showLineNumbers &&
Line Numbers
}
18 |
{content}
19 |
20 | );
21 | };
22 | return {
23 | Light: SyntaxHighlighter,
24 | };
25 | });
26 |
27 | jest.mock("react-syntax-highlighter/dist/esm/languages/hljs/json", () => ({}));
28 |
29 | import React from "react";
30 | import { render } from "@testing-library/react";
31 | import { BrowserRouter as Router } from "react-router-dom";
32 | import Console from "../client/components/Console";
33 |
34 | describe("", () => {
35 | it("renders without crashing", () => {
36 | const { getByTestId } = render(
37 |
38 |
39 |
40 | );
41 | expect(getByTestId("console-container")).toBeInTheDocument();
42 | });
43 |
44 | it("displays JSON data correctly", () => {
45 | const testJson = [{ message: "Test log message" }];
46 | const { getByText } = render(
47 |
48 |
49 |
50 | );
51 | expect(getByText(JSON.stringify(testJson, null, 2))).toBeInTheDocument();
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/__tests__/Credentials.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, fireEvent } from "@testing-library/react";
3 | import Credentials from "../client/components/Credentials";
4 | import { BrowserRouter as Router } from "react-router-dom";
5 |
6 | describe("", () => {
7 | const mockSetAccessKey = jest.fn();
8 | const mockSetSecretKey = jest.fn();
9 | const mockProps = {
10 | setAccessKey: mockSetAccessKey,
11 | setSecretKey: mockSetSecretKey,
12 | };
13 |
14 | it("renders without crashing", () => {
15 | const { getByTestId } = render(
16 |
17 |
18 |
19 | );
20 | expect(getByTestId("Region Options")).toBeInTheDocument();
21 | });
22 |
23 | it("updates access key on change", () => {
24 | const { getByLabelText } = render(
25 |
26 |
27 |
28 | );
29 | fireEvent.change(getByLabelText(/Access Key/i), {
30 | target: { value: "test-access-key" },
31 | });
32 | expect(mockSetAccessKey).toHaveBeenCalledWith("test-access-key");
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/__tests__/logController.test.js:
--------------------------------------------------------------------------------
1 | test('example test', () => {
2 | expect(true).toBe(true);
3 | });
4 |
--------------------------------------------------------------------------------
/__tests__/server.test.js:
--------------------------------------------------------------------------------
1 | const request = require("supertest");
2 | const server = require("../server/server.js");
3 |
4 | afterAll(() => {
5 | server.close();
6 | });
7 |
8 | jest.mock("aws-sdk", () => {
9 | return {
10 | CloudWatchLogs: jest.fn(() => ({
11 | describeLogGroups: jest.fn((params, callback) => {
12 | const mockResponse = {
13 | logGroups: [
14 | { logGroupName: "/aws/lambda/test-group1" },
15 | { logGroupName: "/aws/lambda/test-group2" },
16 | ],
17 | };
18 | callback(null, mockResponse);
19 | }),
20 | })),
21 | config: {
22 | update: jest.fn(() => {
23 | return;
24 | }),
25 | },
26 | };
27 | });
28 |
29 | describe("\n Server Route Tests \n", () => {
30 | describe("Credentials Page Route", () => {
31 | it("should respond with status 200 OK", async () => {
32 | const response = await request(server).get("/");
33 | expect(response.status).toBe(200);
34 | });
35 |
36 | it("should respond with content-type text/html", async () => {
37 | const response = await request(server).get("/");
38 | expect(response.headers["content-type"]).toContain("text/html");
39 | });
40 | });
41 |
42 | describe("Fetch Log Groups Route", () => {
43 | it("should respond with status 200 OK", async () => {
44 | const headers = {
45 | "access-Key": "test-accessKey",
46 | "secret-Key": "test-secretKey",
47 | "aws-region": "test-region",
48 | };
49 | const response = await request(server).get("/loggroups").set(headers);
50 | expect(response.status).toBe(200);
51 | });
52 |
53 | it("should respond with content-type application/json", async () => {
54 | const response = await request(server).get("/loggroups");
55 | expect(response.headers["content-type"]).toContain("application/json");
56 | });
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/client/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useState } from 'react';
3 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
4 | import Header from './Header.jsx';
5 | import Console from './Console.jsx';
6 | import '../src/styles.css';
7 | import Credentials from './Credentials/Credentials.jsx';
8 | import ConsoleNav from './ConsoleNav/ConsoleNav.jsx';
9 | import useLogGroups from '../hooks/useLogGroups';
10 | import useLogStreams from '../hooks/useLogStreams';
11 | import useLogs from '../hooks/useLogs';
12 | import useThemeButton from '../hooks/useThemeButton.js';
13 | import useSearch from '../hooks/useSearch.js';
14 | import Splash from './Splash/Splash.jsx';
15 |
16 | const App = () => {
17 |
18 | /*********************** Initialize State for User Credentials ************************************/
19 |
20 | const [accessKey, setAccessKey] = useState('');
21 | const [secretKey, setSecretKey] = useState('');
22 | const [region, setRegion] = useState('');
23 | const [authenticated, setAuthenticated] = useState(false);
24 |
25 | /*********************** Custom Hook for managing API call fetching log groups and state ***************************/
26 |
27 | const {
28 | logGroups,
29 | selectedLogGroup,
30 | setSelectedLogGroup,
31 | fetchLogGroups,
32 | emptyRegion,
33 | isLoading,
34 | } = useLogGroups(accessKey, secretKey, region, setAuthenticated);
35 |
36 | /*********************** Custom Hook for managing API call fetching log streams and state ***************************/
37 |
38 | const {
39 | logStreams,
40 | selectedLogStream,
41 | setSelectedLogStream,
42 | fetchLogStreams,
43 | } = useLogStreams(accessKey, secretKey, region, selectedLogGroup);
44 |
45 | /*********************** Custom Hook for managing API call fetching display logs and state ***********/
46 |
47 | const {
48 | logs,
49 | fetchLogs,
50 | setLogs,
51 | } = useLogs(accessKey, secretKey, region, selectedLogGroup, selectedLogStream);
52 |
53 |
54 | /* ******************** Custom hook for managing theme toggling in React components *****************/
55 |
56 | const {
57 | theme,
58 | themeButton,
59 | handleThemeButtonClick,
60 | } = useThemeButton();
61 |
62 | /* ******************** Custom hook for managing Search querying from ConsoleNav to Console *********/
63 |
64 | const {
65 | jsonString,
66 | searchQuery,
67 | setSearchQuery,
68 | handleSearchChange
69 | } = useSearch(logs);
70 |
71 |
72 | return (
73 |
74 |
75 |
76 |
78 | }
79 | />
80 |
96 | }
97 | />
98 |
102 |
118 |
119 | >
120 | }
121 | />
122 |
123 |
124 | );
125 | };
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | export default App;
136 |
137 |
138 |
--------------------------------------------------------------------------------
/client/components/Console.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
3 | import json from 'react-syntax-highlighter/dist/esm/languages/hljs/json';
4 | import styles from '../src/styles/Console.module.css';
5 |
6 | export default function Console({ theme, jsonString}) {
7 | SyntaxHighlighter.registerLanguage('json', json);
8 |
9 | return (
10 |
11 |
12 |
20 | {jsonString}
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/client/components/ConsoleNav.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import '../src/styles.css';
3 | import styles from '../src/styles/ConsoleNav.module.css';
4 | import useLogGroupOptions from '../hooks/useLogGroupOptions';
5 | import useLogStreamOptions from '../hooks/useLogStreamOptions';
6 | import useSelectStream from '../hooks/useSelectStream';
7 | import useSelectGroup from '../hooks/useSelectGroup';
8 |
9 |
10 | export default function ConsoleNav({ searchQuery, handleSearchChange, getLogGroups, handleThemeButtonClick, themeButton, logGroups, selectedLogGroup, setSelectedLogGroup, getLogStreams, logStreams, selectedLogStream, setSelectedLogStream, setLogs}) {
11 |
12 | const { logGroupOptions } = useLogGroupOptions(logGroups);
13 | const { logStreamOptions } = useLogStreamOptions(logStreams);
14 | const { handleSelectStreamChange } = useSelectStream(setSelectedLogStream);
15 | const { handleSelectChange } = useSelectGroup(setSelectedLogGroup);
16 |
17 | return (
18 |
19 |
25 |
31 |
37 |
38 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/client/components/ConsoleNav/ConsoleNav.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../../src/styles.css';
3 | import styles from '../../src/styles/ConsoleNav.module.css';
4 | import LogGroupDropdown from './LogGroupDropdown.jsx';
5 | import LogStreamDropdown from './LogStreamDropdown.jsx';
6 | import InputBox from './InputBox.jsx';
7 | import ThemeButton from './ThemeButton.jsx';
8 | import RefreshButton from './RefreshButton.jsx';
9 | import useLogGroupOptions from '../../hooks/useLogGroupOptions';
10 | import useLogStreamOptions from '../../hooks/useLogStreamOptions';
11 | import useSelectStream from '../../hooks/useSelectStream';
12 | import useSelectGroup from '../../hooks/useSelectGroup';
13 |
14 | export default function ConsoleNav({ setSearchQuery, searchQuery, handleSearchChange, getLogGroups, handleThemeButtonClick, themeButton, logGroups, selectedLogGroup, setSelectedLogGroup, getLogStreams, logStreams, selectedLogStream, setSelectedLogStream, setLogs }) {
15 |
16 | const { logGroupOptions } = useLogGroupOptions(logGroups);
17 | const { logStreamOptions } = useLogStreamOptions(logStreams);
18 | const { handleSelectStreamChange } = useSelectStream(setSelectedLogStream);
19 | const { handleSelectChange } = useSelectGroup(setSelectedLogGroup);
20 |
21 | return (
22 |
23 |
28 |
33 |
37 |
41 |
47 |
48 | );
49 | }
--------------------------------------------------------------------------------
/client/components/ConsoleNav/InputBox.jsx:
--------------------------------------------------------------------------------
1 | // InputBox.jsx
2 | import React from 'react';
3 | import '../../src/styles/ConsoleNav.module.css';
4 |
5 | function InputBox({ searchQuery, handleSearchChange }) {
6 | return (
7 |
13 | );
14 | }
15 |
16 | export default InputBox;
17 |
--------------------------------------------------------------------------------
/client/components/ConsoleNav/LogGroupDropdown.jsx:
--------------------------------------------------------------------------------
1 | // LogGroupDropdown.jsx
2 | import React from 'react';
3 | import '../../src/styles/ConsoleNav.module.css';
4 |
5 | function LogGroupDropdown({ selectedLogGroup, handleSelectChange, logGroupOptions }) {
6 | return (
7 |
13 | );
14 | }
15 |
16 | export default LogGroupDropdown;
17 |
--------------------------------------------------------------------------------
/client/components/ConsoleNav/LogStreamDropdown.jsx:
--------------------------------------------------------------------------------
1 | // LogStreamDropdown.jsx
2 | import React from 'react';
3 | import '../../src/styles/ConsoleNav.module.css';
4 |
5 | function LogStreamDropdown({ selectedLogStream, handleSelectStreamChange, logStreamOptions }) {
6 | return (
7 |
13 | );
14 | }
15 |
16 | export default LogStreamDropdown;
17 |
--------------------------------------------------------------------------------
/client/components/ConsoleNav/RefreshButton.jsx:
--------------------------------------------------------------------------------
1 | // RefreshButton.jsx
2 | import React from 'react';
3 | import '../../src/styles/ConsoleNav.module.css';
4 |
5 | function RefreshButton({ setSearchQuery, getLogGroups, getLogStreams, setLogs }) {
6 |
7 |
8 | return (
9 |
15 | );
16 | }
17 |
18 | export default RefreshButton;
19 |
--------------------------------------------------------------------------------
/client/components/ConsoleNav/ThemeButton.jsx:
--------------------------------------------------------------------------------
1 | // ThemeButton.jsx
2 | import React from 'react';
3 | import '../../src/styles/ConsoleNav.module.css';
4 |
5 | function ThemeButton({ handleThemeButtonClick, themeButton }) {
6 | return (
7 |
8 | );
9 | }
10 |
11 | export default ThemeButton;
12 |
--------------------------------------------------------------------------------
/client/components/Credentials/Credentials.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CredentialsForm from './CredentialsForm/CredentialsForm.jsx';
3 | import styles from '../../src/styles/Credentials.module.css';
4 | import useRegions from '../../hooks/useRegions';
5 |
6 |
7 | export default function Credentials({setAccessKey, setSecretKey, setRegion, accessKey, secretKey, region, getLogGroups, setAuthenticated, authenticated, emptyRegion, isLoading}) {
8 |
9 | const { regionOptions } = useRegions();
10 |
11 | return (
12 |
13 |
Enter Credentials
14 |
28 |
29 | );
30 | }
--------------------------------------------------------------------------------
/client/components/Credentials/CredentialsForm/AccessKeyInput.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function AccessKeyInput({ accessKey, setAccessKey }) {
4 | return (
5 |
15 | );
16 | }
17 |
18 | export default AccessKeyInput;
19 |
--------------------------------------------------------------------------------
/client/components/Credentials/CredentialsForm/CredentialsForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import '../../../src/styles/Credentials.module.css';
3 | import AccessKeyInput from './AccessKeyInput';
4 | import SecretKeyInput from './SecretKeyInput'; // Create this component similarly
5 | import RegionSelect from './RegionSelect';
6 | import SubmitButton from './SubmitButton';
7 |
8 | export default function CredentialsForm({
9 | accessKey,
10 | secretKey,
11 | region,
12 | setAccessKey,
13 | setSecretKey,
14 | setRegion,
15 | regionOptions,
16 | getLogGroups,
17 | setAuthenticated,
18 | authenticated,
19 | emptyRegion,
20 | isLoading,
21 | }) {
22 |
23 | const [hasClickedSubmit, setHasClickedSubmit] = useState(false);
24 |
25 | return (
26 |
27 |
28 |
29 |
34 |
44 | {isLoading && hasClickedSubmit &&
Loading...
}
45 | {!isLoading && hasClickedSubmit && !authenticated && !emptyRegion && (
46 |
Credentials Not Accepted
47 | )}
48 | {!isLoading && hasClickedSubmit && !authenticated && emptyRegion && (
49 |
No Log Groups in Region
50 | )}
51 |
52 | );
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/client/components/Credentials/CredentialsForm/RegionSelect.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function RegionSelect({ region, setRegion, regionOptions }) {
4 | return (
5 |
8 | );
9 | }
10 |
11 | export default RegionSelect;
12 |
--------------------------------------------------------------------------------
/client/components/Credentials/CredentialsForm/SecretKeyInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function SecretKeyInput({ secretKey, setSecretKey }) {
4 | return (
5 |
15 | );
16 | }
17 |
18 | export default SecretKeyInput;
19 |
--------------------------------------------------------------------------------
/client/components/Credentials/CredentialsForm/SubmitButton.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | function SubmitButton({
5 | authenticated,
6 | getLogGroups,
7 | setAuthenticated,
8 | setHasClickedSubmit,
9 | hasClickedSubmit,
10 | }) {
11 | const navigate = useNavigate();
12 |
13 | useEffect(() => {
14 | if (authenticated && hasClickedSubmit) {
15 | navigate("/console");
16 | }
17 | }, [authenticated, hasClickedSubmit, navigate]);
18 |
19 | const handleClick = async () => {
20 | setHasClickedSubmit(true);
21 | };
22 |
23 | return ;
24 | }
25 |
26 | export default SubmitButton;
27 |
--------------------------------------------------------------------------------
/client/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import styles from '../src/styles/Header.module.css';
4 |
5 | export default function Header() {
6 | return (
7 |
8 |

9 |
Lambda Logger
10 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/client/components/Splash/Contributors.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from '../../src/styles/Contributors.module.css';
3 |
4 | const contributors = [
5 | { name: 'Conrad Preston', github: 'https://github.com/Conrady82', linkedin: 'https://www.linkedin.com/in/conrad-preston-aaaa9b252/', image: '../../src/images/conrad.jpeg' },
6 | { name: 'Hoang Dang', github: 'https://github.com/hoangdang91768', linkedin: 'https://www.linkedin.com/in/hoang-dang-b884b4296/', image: '../../src/images/Hoang.jpeg' },
7 | { name: 'Luke Clarkson', github: 'https://github.com/LClarkson', linkedin: 'https://www.linkedin.com/in/ljclarkson/', image: '../../src/images/Luke.png' },
8 | { name: 'Nick C. Mason', github: 'https://github.com/nickmasonswe', linkedin: 'https://www.linkedin.com/in/nickmasonswe/', image: '../../src/images/Nick.jpeg' },
9 | // Add more contributors as needed
10 | ];
11 |
12 | const Contributors = () => {
13 | return (
14 |
15 |
16 |
Contributors
17 |
18 | {contributors.map((contributor, index) => (
19 |
20 |

21 |
{contributor.name}
22 |
30 |
31 | ))}
32 |
33 |
34 | Lambda Logger has so many exciting features to be developed!
35 |
36 |
37 |
42 |
43 |
44 |
45 | );
46 | };
47 |
48 |
49 | export default Contributors;
50 |
--------------------------------------------------------------------------------
/client/components/Splash/FeatureSection.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import styles from '../../src/styles/FeatureSection.module.css';
4 |
5 | export default function FeatureSection() {
6 | return (
7 |
8 |
9 |
Searchable and Readable
10 |
Lambda Logger gives you the power to quickly read and search
11 | any AWS Lambda log file you need straight from our seamless, secure dashboard.
12 | Lightweight and optimized so you can finally
live debug your Lambdas in peace.
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/client/components/Splash/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from '../../src/styles/Footer.module.css';
3 |
4 | const Footer = () => {
5 | return (
6 |
7 |
8 | - GitHub
9 |
10 | @ 2023 | MIT License
11 |
12 |
13 | );
14 | };
15 |
16 | export default Footer;
17 |
--------------------------------------------------------------------------------
/client/components/Splash/GetStarted.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from '../../src/styles/MainSection.module.css';
3 |
4 | const GetStarted = () => {
5 | return (
6 |
7 |
8 |
Getting Started
9 |
Create temporary access credentials in your AWS account,
10 | then use them to scrape your logs
11 | from CloudWatch and run them through our custom parser.
12 | Lambda Logger never stores your credentials, and we dont retain
13 | your logs after you leave.
Log streams are pulled directly from
14 | AWS and are ephemeral. As soon as you leave, they leave.
15 |
16 |
17 | {/* Place your high-resolution GIF here */}
18 |

19 |
20 |
21 | );
22 | };
23 |
24 | export default GetStarted;
--------------------------------------------------------------------------------
/client/components/Splash/MainSection.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from '../../src/styles/MainSection.module.css';
3 |
4 | export default function MainSection() {
5 | return (
6 |
7 |
8 |
Live Debug
9 |
Meaningful logs.
Labeled, tagged with colors,
and searchable.
10 |
11 |
12 | {/* Place your high-resolution GIF here */}
13 |

14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/client/components/Splash/Splash.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MainSection from './MainSection.jsx';
3 | import GetStarted from './GetStarted.jsx';
4 | import Contributors from './Contributors.jsx';
5 | import Footer from './Footer.jsx';
6 | import FeatureSection from './FeatureSection.jsx';
7 |
8 | function Splash() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | export default Splash;
21 |
--------------------------------------------------------------------------------
/client/hooks/useLogGroupOptions.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 |
3 | function useLogGroupOptions(logGroups) {
4 | const [logGroupOptions, setLogGroupOptions] = useState([]);
5 | useEffect(() => {
6 | if (logGroups && Array.isArray(logGroups)) {
7 | const options = logGroups.map((logGroup, index) => (
8 |
11 | ));
12 | setLogGroupOptions(options);
13 | }
14 | }, [logGroups]);
15 | return {
16 | logGroupOptions,
17 | };
18 | }
19 |
20 | export default useLogGroupOptions;
21 |
--------------------------------------------------------------------------------
/client/hooks/useLogGroups.js:
--------------------------------------------------------------------------------
1 | // hooks/useLogGroups.js
2 | import { useState, useEffect, useCallback } from 'react';
3 |
4 | function useLogGroups(accessKey, secretKey, region, setAuthenticated) {
5 | const [logGroups, setLogGroups] = useState([]);
6 | const [selectedLogGroup, setSelectedLogGroup] = useState("");
7 | const [emptyRegion, setEmptyRegion] = useState(false);
8 | const [isLoading, setIsLoading] = useState(false);
9 |
10 | const fetchLogGroups = useCallback(
11 | async (setAuthenticated) => {
12 | setIsLoading(true);
13 | setEmptyRegion(false);
14 | const url = "/credentials/loggroups";
15 |
16 | try {
17 | const response = await fetch(url, {
18 | method: "GET", // Assuming the endpoint is expecting a GET request
19 | headers: {
20 | "Content-Type": "application/json",
21 | "Access-Key": encodeURIComponent(accessKey),
22 | "Secret-Key": encodeURIComponent(secretKey),
23 | "AWS-Region": encodeURIComponent(region),
24 | },
25 | });
26 | if (!response.ok) {
27 | const errorData = await response.json(); // Parse error response
28 | setAuthenticated(false);
29 | setIsLoading(false);
30 | return;
31 | }
32 | const data = await response.json();
33 | if (!data.length) {
34 | setAuthenticated(false);
35 | setEmptyRegion(true);
36 | setIsLoading(false);
37 | return;
38 | }
39 | console.log(data);
40 | setLogGroups(data);
41 | setAuthenticated(true);
42 | setIsLoading(false);
43 | return;
44 | } catch (err) {
45 | console.error("Failed to fetch log groups:", err);
46 | }
47 | },
48 | [accessKey, secretKey, region, setAuthenticated]
49 | );
50 |
51 | useEffect(() => {
52 | if (accessKey && secretKey && region) {
53 | fetchLogGroups(setAuthenticated);
54 | }
55 | }, [accessKey, secretKey, region, fetchLogGroups]);
56 |
57 | return {
58 | logGroups,
59 | selectedLogGroup,
60 | setSelectedLogGroup,
61 | fetchLogGroups,
62 | emptyRegion,
63 | isLoading,
64 | };
65 | }
66 |
67 | export default useLogGroups;
68 |
--------------------------------------------------------------------------------
/client/hooks/useLogStreamOptions.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | function useLogStreamOptions(logStreams) {
4 | const [logStreamOptions, setLogStreamOptions] = useState([]);
5 | useEffect(() => {
6 | if (logStreams && Array.isArray(logStreams)) {
7 | const options = logStreams.map((logStream, index) => (
8 |
11 | ));
12 | setLogStreamOptions(options);
13 | }
14 | }, [logStreams]);
15 | return {
16 | logStreamOptions,
17 | };
18 | }
19 |
20 | export default useLogStreamOptions;
21 |
--------------------------------------------------------------------------------
/client/hooks/useLogStreams.js:
--------------------------------------------------------------------------------
1 | // hooks/useLogStreams.js
2 | import { useState, useEffect, useCallback } from 'react';
3 |
4 | function useLogStreams(accessKey, secretKey, region, selectedLogGroup) {
5 | const [logStreams, setLogStreams] = useState([]);
6 | const [selectedLogStream, setSelectedLogStream] = useState('');
7 |
8 | const fetchLogStreams = useCallback(async () => {
9 | const url = "/credentials/logstreams";
10 |
11 | try {
12 | const response = await fetch(url, {
13 | method: 'GET', // Assuming the endpoint is expecting a GET request
14 | headers: {
15 | 'Content-Type': 'application/json',
16 | 'Access-Key': encodeURIComponent(accessKey),
17 | 'Secret-Key': encodeURIComponent(secretKey),
18 | 'AWS-Region': encodeURIComponent(region),
19 | 'Log-Group': encodeURIComponent(selectedLogGroup),
20 | },
21 | });
22 | const data = await response.json();
23 | setLogStreams(data);
24 | } catch (error) {
25 | console.error('Failed to fetch log streams:', error);
26 | }
27 | }, [accessKey, secretKey, region, selectedLogGroup]);
28 |
29 | useEffect(() => {
30 | if (selectedLogGroup) {
31 | fetchLogStreams();
32 | }
33 | }, [selectedLogGroup, fetchLogStreams]);
34 |
35 | return {
36 | logStreams,
37 | selectedLogStream,
38 | setSelectedLogStream,
39 | fetchLogStreams,
40 | };
41 | }
42 |
43 | export default useLogStreams;
44 |
--------------------------------------------------------------------------------
/client/hooks/useLogs.js:
--------------------------------------------------------------------------------
1 | // hooks/useLogs.js
2 | import { useState, useEffect, useCallback } from 'react';
3 |
4 | function useLogs(
5 | accessKey,
6 | secretKey,
7 | region,
8 | selectedLogGroup,
9 | selectedLogStream
10 | ) {
11 | const [logs, setLogs] = useState('NO LOGS REQUESTED YET');
12 | const fetchLogs = useCallback(async () => {
13 | const url = "/credentials/logs";
14 |
15 | try {
16 | const response = await fetch(url, {
17 | method: 'GET', // Assuming the endpoint is expecting a GET request
18 | headers: {
19 | 'Content-Type': 'application/json',
20 | 'Access-Key': encodeURIComponent(accessKey),
21 | 'Secret-Key': encodeURIComponent(secretKey),
22 | 'AWS-Region': encodeURIComponent(region),
23 | 'Log-Group': encodeURIComponent(selectedLogGroup),
24 | 'Log-Stream': encodeURIComponent(selectedLogStream),
25 | },
26 | });
27 | const data = await response.json();
28 | setLogs(data);
29 | } catch (error) {
30 | console.error('Failed to fetch logs:', error);
31 | }
32 | }, [accessKey, secretKey, region, selectedLogGroup, selectedLogStream]);
33 |
34 | useEffect(() => {
35 | if (selectedLogStream) {
36 | fetchLogs();
37 | }
38 | }, [selectedLogStream, fetchLogs]);
39 |
40 | return {
41 | logs,
42 | fetchLogs,
43 | setLogs,
44 | };
45 | }
46 |
47 | export default useLogs;
48 |
--------------------------------------------------------------------------------
/client/hooks/useRegions.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function useRegions() {
4 | const regions = [
5 | 'us-east-1',
6 | 'us-east-2',
7 | 'us-west-1',
8 | 'us-west-2',
9 | 'ap-south-1',
10 | 'ap-northeast-3',
11 | 'ap-northeast-2',
12 | 'ap-northeast-1',
13 | 'ap-southeast-2',
14 | 'ap-southeast-1',
15 | 'ca-central-1',
16 | 'eu-central-1',
17 | 'eu-west-1',
18 | 'eu-west-2',
19 | 'eu-west-3',
20 | 'eu-north-1',
21 | 'sa-south-1',
22 | ];
23 |
24 | const regionOptions = [
25 | ,
28 | ...regions.map((region, index) => (
29 |
32 | )),
33 | ];
34 |
35 | return {
36 | regionOptions,
37 | };
38 | }
39 | export default useRegions;
40 |
--------------------------------------------------------------------------------
/client/hooks/useSearch.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | function useSearch(jsonObject) {
4 | const [searchQuery, setSearchQuery] = useState('');
5 |
6 | const handleSearchChange = (event) => {
7 | setSearchQuery(event.target.value);
8 | };
9 |
10 | const filteredJson = searchQuery
11 | ? jsonObject.filter((item) =>
12 | JSON.stringify(item).toLowerCase().includes(searchQuery.toLowerCase())
13 | )
14 | : jsonObject;
15 |
16 | const jsonString = JSON.stringify(filteredJson, null, 2);
17 |
18 | return {
19 | jsonString,
20 | searchQuery,
21 | setSearchQuery,
22 | handleSearchChange,
23 | };
24 | }
25 |
26 | export default useSearch;
27 |
--------------------------------------------------------------------------------
/client/hooks/useSelectGroup.js:
--------------------------------------------------------------------------------
1 | function useSelectGroup(setSelectedLogGroup) {
2 | function handleSelectChange(e) {
3 | setSelectedLogGroup(e.target.value);
4 | }
5 | return {
6 | handleSelectChange,
7 | };
8 | }
9 |
10 | export default useSelectGroup;
11 |
--------------------------------------------------------------------------------
/client/hooks/useSelectStream.js:
--------------------------------------------------------------------------------
1 | function useSelectStream(setSelectedLogStream) {
2 | function handleSelectStreamChange(e) {
3 | setSelectedLogStream(e.target.value);
4 | }
5 | return {
6 | handleSelectStreamChange,
7 | };
8 | }
9 |
10 | export default useSelectStream;
11 |
--------------------------------------------------------------------------------
/client/hooks/useThemeButton.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import {
3 | stackoverflowDark,
4 | stackoverflowLight,
5 | } from 'react-syntax-highlighter/dist/esm/styles/hljs';
6 |
7 | function useThemeButton() {
8 | const [theme, setTheme] = useState(stackoverflowDark);
9 | const [themeButton, setThemeButton] = useState('Light Mode');
10 | function handleThemeButtonClick() {
11 | theme === stackoverflowDark
12 | ? (setTheme(stackoverflowLight), setThemeButton('Dark Mode'))
13 | : (setTheme(stackoverflowDark), setThemeButton('Light Mode'));
14 | }
15 | return {
16 | theme,
17 | setTheme,
18 | themeButton,
19 | setThemeButton,
20 | handleThemeButtonClick,
21 | };
22 | }
23 |
24 | export default useThemeButton;
25 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/src/images/Hoang.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/client/src/images/Hoang.jpeg
--------------------------------------------------------------------------------
/client/src/images/LinkedIn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/client/src/images/LinkedIn.png
--------------------------------------------------------------------------------
/client/src/images/Luke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/client/src/images/Luke.png
--------------------------------------------------------------------------------
/client/src/images/Nick.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/client/src/images/Nick.jpeg
--------------------------------------------------------------------------------
/client/src/images/Placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/client/src/images/Placeholder.png
--------------------------------------------------------------------------------
/client/src/images/conrad.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/client/src/images/conrad.jpeg
--------------------------------------------------------------------------------
/client/src/images/consoleContent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/client/src/images/consoleContent.png
--------------------------------------------------------------------------------
/client/src/images/creds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/client/src/images/creds.png
--------------------------------------------------------------------------------
/client/src/images/github-mark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/client/src/images/github-mark.png
--------------------------------------------------------------------------------
/client/src/images/ll-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/client/src/images/ll-logo.png
--------------------------------------------------------------------------------
/client/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Lambda Logger
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { createRoot } from "react-dom/client";
3 | import App from "../components/App.jsx";
4 |
5 | const domNode = document.getElementById("root");
6 | const root = createRoot(domNode);
7 | root.render();
8 |
--------------------------------------------------------------------------------
/client/src/styles.css:
--------------------------------------------------------------------------------
1 | /* This does not work yet: */
2 | html {
3 | box-sizing: border-box;
4 | }
5 | *,
6 | *::before,
7 | *::after {
8 | box-sizing: inherit;
9 | }
10 |
11 | body {
12 | background-color: rgb(40, 42, 45);
13 | margin: 0;
14 | }
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/client/src/styles/Console.module.css:
--------------------------------------------------------------------------------
1 | .Console {
2 | padding-top: 115px;
3 | }
--------------------------------------------------------------------------------
/client/src/styles/ConsoleNav.module.css:
--------------------------------------------------------------------------------
1 | .ConsoleNav {
2 | display: flex;
3 | position: fixed;
4 | width: 100%;
5 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
6 | color: #bdbdbd;
7 | height:58px;
8 | margin-top: 70px;
9 | padding-left: 1em;
10 | padding-top: .5em;
11 | padding-bottom: .5em;
12 | border-bottom: 2px solid #5a5a5a;
13 |
14 | background-color: rgb(56, 56, 56);
15 | }
16 |
17 | .ConsoleNav button {
18 | width: 10em;
19 | white-space: nowrap;
20 | margin-left: 10px;
21 | background-color: #484848;
22 | color: #dadada;
23 | border: none;
24 | border-radius: 3px;
25 | cursor: pointer;
26 | transition: background-color 0.3s;
27 | box-shadow: 5px 5px 10px 1px rgba(24, 24, 24, 0.1);
28 | }
29 |
30 | .ConsoleNav button:hover {
31 | background-color: #ff711f;
32 | }
33 |
34 | .ConsoleNav select {
35 | box-sizing: border-box;
36 | margin-left: 10px;
37 | border: 1px solid #b5b5b5;
38 | border-radius: 4px;
39 | background-color: rgb(69, 69, 69);
40 | color: #b0b0b0;
41 | transition: border-color 0.3s;
42 | box-shadow: 5px 5px 10px 2px rgba(0, 0, 0, .1);
43 | }
44 |
45 | .ConsoleNav input {
46 | box-sizing: border-box;
47 | margin-left: 10px;
48 | border: 1px solid #b5b5b5;
49 | border-radius: 4px;
50 | background-color: rgb(69, 69, 69);
51 | color: #b0b0b0;
52 | transition: border-color 0.3s;
53 | box-shadow: 5px 5px 10px 2px rgba(0, 0, 0, .1);
54 | }
55 |
56 | .ConsoleNav select:focus {
57 | border-color: #ff711f;
58 | }
--------------------------------------------------------------------------------
/client/src/styles/Contributors.module.css:
--------------------------------------------------------------------------------
1 | .Contributors {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | background: linear-gradient(to right, rgba(255, 113, 31, 0.8), rgba(204, 85, 0, 0.8));
6 | padding: 20px;
7 | }
8 |
9 | .ContributorsBox {
10 | width: 80%;
11 | background-color: rgba(255, 255, 255, 0.6);
12 | padding: 20px;
13 | border-radius: 15px;
14 | text-align: center;
15 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
16 | font-weight: bold;
17 | }
18 |
19 | .ContributorsCards {
20 | display: flex;
21 | flex-wrap: wrap;
22 | justify-content: center;
23 | gap: 20px; /* Spacing between cards */
24 | }
25 |
26 | .ContributorCard {
27 | width: 200px;
28 | background-color: rgba(255, 255, 255, 0.7);
29 | padding: 10px;
30 | border-radius: 20px;
31 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
32 | text-align: center;
33 | transition: background-color 0.3s ease;
34 | }
35 |
36 | .ContributorCard:hover {
37 | background-color: rgba(255, 255, 255, 0.4); /* Increase transparency on hover */
38 | }
39 |
40 | .ContributorImage {
41 | width: 100%;
42 | height: auto;
43 | border-radius: 50%;
44 | }
45 |
46 | .ContributorName {
47 | margin-top: 10px;
48 | }
49 |
50 | .ContributorLinks {
51 | display: flex;
52 | justify-content: center;
53 | gap: 10px; /* Spacing between links */
54 | margin-top: 10px;
55 | }
56 |
57 | .Link img {
58 | width: 35px;
59 | height: 35px;
60 | transition: transform 0.3s ease;
61 | }
62 |
63 | .Link:hover img {
64 | transform: scale(1.2);
65 | }
66 |
67 | .ContributorsText {
68 | margin-top: 20px;
69 | color: #333333; /* Adjust the color as needed */
70 | /* Other styling as needed */
71 | }
72 |
73 | .ContributorsButton {
74 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
75 | background-color: rgba(255, 255, 240, 0.8);
76 | border: 2px solid rgba(255, 235, 240, 0.8);; /* Adjust border color as needed */
77 | color: #333355; /* Adjust text color as needed */
78 | padding: 10px 20px;
79 | cursor: pointer;
80 | font-weight: bold;
81 | font-size: large;
82 | transition: background-color 0.3s;
83 | box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
84 | border-radius: 20px; /* Rounded edges */
85 | }
86 |
87 | .ContributorsButton:hover {
88 | background-color: transparent;
89 | color: rgba(255, 255, 240, 0.8);;
90 | font-weight: bold;
91 | box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
92 | }
93 |
94 | .ContributorsTextWrapper,
95 | .ContributorsButtonWrapper {
96 | text-align: center; /* Center the content */
97 | width: 100%; /* Take full width */
98 | margin-top: .5em; /* 1em is the font size of the current element */
99 | }
100 |
101 | @media only screen and (max-width: 600px) {
102 |
103 | .ContributorsBox {
104 | width: 100%;
105 | background-color: rgba(255, 255, 255, 0.6);
106 | padding: 20px;
107 | border-radius: 15px;
108 | text-align: center;
109 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
110 | font-weight: bold;
111 | }
112 |
113 | .ContributorsCards {
114 | display: inline-flex;
115 | justify-content: center;
116 | gap: 30px; /* Spacing between cards */
117 | }
118 |
119 | .ContributorCard {
120 | margin-bottom: 4vh;
121 | width: 200px;
122 | height: 150px;
123 | background-color: rgba(255, 255, 255, 0.7);
124 | padding: 10px;
125 | border-radius: 20px;
126 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
127 | text-align: center;
128 | transition: background-color 0.3s ease;
129 |
130 | p {
131 | margin-top: 1px;
132 | }
133 |
134 | img {
135 | margin-bottom: 0px;
136 | }
137 | }
138 |
139 | .ContributorImage {
140 | width: 60%;
141 | height: auto;
142 | border-radius: 50%;
143 | }
144 |
145 | .ContributorName {
146 | margin-top: 0px;
147 | }
148 | }
--------------------------------------------------------------------------------
/client/src/styles/Credentials.module.css:
--------------------------------------------------------------------------------
1 | .Credentials {
2 | font-family: Arial, sans-serif;
3 | max-width: 400px;
4 | margin: 0 auto;
5 | padding: 20px;
6 | border: 1px solid #ff711f;
7 | border-radius: 8px;
8 | background-color: rgb(31, 31, 31);
9 | box-shadow: 0px 0px 2.5px 1px rgba(249, 113, 50, 0.5);
10 |
11 | /* center the box vertically and horizontally */
12 | display: flex;
13 | flex-direction: column;
14 | align-items: center;
15 | justify-content: center;
16 |
17 | /* adjustments for better spacing */
18 | height: 25em;
19 | margin-top: 15em;
20 | }
21 |
22 | .Credentials h3 {
23 | text-align: center;
24 | color: #d8d8d8;
25 | }
26 |
27 | .Credentials form {
28 | margin-bottom: 20px;
29 | }
30 |
31 | .Credentials label {
32 | display: block;
33 | margin-bottom: 8px;
34 | color: #d8d8d8;
35 | }
36 |
37 | .Credentials input,
38 | .Credentials select {
39 | width: 100%;
40 | padding: 12px;
41 | margin-bottom: 16px;
42 | box-sizing: border-box;
43 | border: 1px solid #ddd;
44 | border-radius: 4px;
45 | background-color: #d4d4d4;
46 | color: #333;
47 | transition: border-color 0.3s;
48 | }
49 |
50 | .Credentials input:focus,
51 | .Credentials select:focus {
52 | border-color: #3498db;
53 | }
54 |
55 | .Credentials button {
56 | width: 100%;
57 | padding: 12px;
58 | background-color: #3498db;
59 | color: #fff;
60 | border: none;
61 | border-radius: 4px;
62 | cursor: pointer;
63 | transition: background-color 0.3s;
64 | }
65 |
66 | .Credentials button:hover {
67 | background-color: #2980b9;
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/client/src/styles/FeatureSection.module.css:
--------------------------------------------------------------------------------
1 | .FeatureSection {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | height: 33.44vh; /* Approximately 2/3 of the previous section's height */
6 | width: 100%;
7 | background: linear-gradient(to right,
8 | rgba(255, 113, 31, 0) 0%,
9 | rgba(255, 113, 31, .8) 0%,
10 | rgba(255, 113, 31, .8) 50%,
11 | rgba(204, 85, 0, .8) 100%),
12 | rgba(255, 113, 31, 0) 0%;
13 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
14 | }
15 |
16 |
17 | .Content {
18 | text-align: center;
19 | padding: 20px;
20 | color: white; /* Adjust text color as needed */
21 | }
22 |
23 |
24 | .FeatureSection p {
25 | text-align: center;
26 | }
27 |
28 | .HoverButton {
29 |
30 | background-color: white;
31 | border: 2px solid white; /* Adjust border color as needed */
32 | color: #ff711f; /* Adjust text color as needed */
33 | padding: 10px 20px;
34 | cursor: pointer;
35 | font-weight: bold;
36 | transition: background-color 0.3s;
37 | box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
38 | border-radius: 25px; /* Rounded edges */
39 | }
40 |
41 | .HoverButton:hover {
42 | background-color: transparent;
43 | color: white;
44 | font-weight: bold;
45 | box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/client/src/styles/Footer.module.css:
--------------------------------------------------------------------------------
1 | .Footer {
2 | text-align: center;
3 | padding: 20px;
4 | background-color: #333; /* Adjust background color as needed */
5 | color: #fff;
6 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
7 | }
8 |
9 | .FooterLinks {
10 | list-style: none;
11 | padding: 0;
12 | margin-top: 2%; /* Reset default margin */
13 | }
14 |
15 | .FooterLink {
16 | color: #fff; /* Initial link color */
17 | text-decoration: none;
18 | transition: color 0.3s ease;
19 | }
20 |
21 | .FooterLink:hover {
22 | color: #ff711f; /* Color on hover */
23 | }
24 |
25 | /* New style for list items */
26 | .FooterLinks li {
27 | margin-bottom: 15px; /* Adjust this value to increase/decrease spacing */
28 | }
29 |
--------------------------------------------------------------------------------
/client/src/styles/Header.module.css:
--------------------------------------------------------------------------------
1 | .Head {
2 | display: flex;
3 | /* justify-content: flex-end;
4 | align-items: center; */
5 | position: fixed;
6 | top: 0;
7 | width: 100%;
8 | height: 70px;
9 | background-color: rgb(25, 25, 25);
10 | box-shadow: 0px 0px 15px 5px rgba(0, 0, 0, .2);
11 | border-bottom: 2px solid #3c3c3c;
12 | z-index: 1;
13 | padding: 0 20px;
14 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
15 | }
16 |
17 | .LogoTitleContainer {
18 | display: flex;
19 | align-items: center;
20 | justify-content: flex-start;
21 | }
22 |
23 | .Head h3 {
24 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
25 | color: #d8d8d8;
26 | margin: 0;
27 | padding-left: 10px; /* Adjust as needed */
28 | }
29 |
30 | .Head img {
31 | height: 100%;
32 | }
33 |
34 | .NavLinks {
35 | display: flex;
36 | justify-content: flex-end;
37 | align-items: center;
38 | }
39 |
40 | .NavLink {
41 | color: #d8d8d8;
42 | text-decoration: none;
43 | margin-left: 20px;
44 | padding: 10px;
45 | transition: color 0.3s;
46 | }
47 |
48 | .NavLink:hover {
49 | color: yellow; /* Replace with your desired hover color */
50 | }
51 |
52 | @media only screen and (max-width: 600px) {
53 |
54 | .LogoTitleContainer {
55 | display: flex;
56 | align-items: center;
57 | justify-content: flex-start;
58 |
59 |
60 | }
61 |
62 | .NavLinks {
63 | display: flex;
64 | justify-content: flex-end;
65 | align-items: center;
66 | }
67 |
68 | .NavLink {
69 | color: #d8d8d8;
70 | text-decoration: none;
71 | margin-left: 10px;
72 | padding: 10px;
73 | transition: color 0.3s;
74 | }
75 |
76 | .Head {
77 | h3 {
78 | display: none;
79 | }
80 | }
81 |
82 |
83 | }
--------------------------------------------------------------------------------
/client/src/styles/MainSection.module.css:
--------------------------------------------------------------------------------
1 | .MainSection {
2 | display: flex;
3 | justify-content: center; /* Center the boxes */
4 | align-items: center;
5 | height: 66.67vh; /* 2/3 of the viewport height */
6 | width: 100%;
7 | padding: 0 5%; /* Reduced overall padding */
8 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
9 | }
10 |
11 | .LeftBox {
12 | width: 35%; /* Slightly increased width */
13 | text-align: center;
14 | color: rgb(235, 235, 235);
15 | font-size: 1.2em;
16 | font-weight: normal; /* Larger text size */
17 | padding-right: 15%; /* Reduced space between text and image */
18 | }
19 |
20 | .RightBox {
21 | width: 45%; /* Adjusted width */
22 | height: calc(66.67vh * 1.1);
23 | display: flex;
24 | justify-content: center;
25 | align-items: center;
26 | overflow: hidden;
27 | border-radius: 30px;
28 | }
29 |
30 | .RightBox img {
31 | max-width: 100%;
32 | max-height: 100%;
33 | object-fit: contain; /* Ensures the image scales properly */
34 | border-radius: 7px;
35 | box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.1);
36 | transition: transform 0.3s ease;
37 | }
38 |
39 | .RightBox img:hover {
40 | transform: scale(1.1); /* Slightly enlarge the image on hover */
41 | }
42 |
43 | /* Media Query for Mobile Screens */
44 | @media only screen and (max-width: 600px) {
45 | .MainSection {
46 | flex-direction: column;
47 | height: auto;
48 | }
49 |
50 | .LeftBox, .RightBox {
51 | width: 100%;
52 | padding: 0;
53 | margin-top: 4vh;
54 | font-size: medium;
55 |
56 | h2 {
57 | margin-top: 8vh;
58 | }
59 |
60 | p {
61 | font-weight: lighter;
62 | }
63 | }
64 |
65 | .RightBox {
66 | height: auto;
67 | border-radius: 0;
68 | }
69 |
70 | img {
71 | margin-bottom: 6vh;
72 | }
73 | }
--------------------------------------------------------------------------------
/docs/assets/images/consoleContent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/docs/assets/images/consoleContent.png
--------------------------------------------------------------------------------
/docs/assets/images/consolePage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/docs/assets/images/consolePage.png
--------------------------------------------------------------------------------
/docs/assets/images/get-access-key.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/docs/assets/images/get-access-key.gif
--------------------------------------------------------------------------------
/docs/assets/images/homepage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/docs/assets/images/homepage.png
--------------------------------------------------------------------------------
/docs/assets/images/minLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/docs/assets/images/minLogo.png
--------------------------------------------------------------------------------
/docs/assets/images/region.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/lambda-logger/2cc6a052afa2e477775a4f31f465cb9af6381579/docs/assets/images/region.png
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: 'jsdom',
3 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$',
4 | moduleFileExtensions: ['js', 'jsx', 'json', 'node'],
5 | };
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Lambda-Logger",
3 | "version": "1.0.0",
4 | "description": "Meaningful logs. Labeled, tagged with colors, and searchable.",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node server/server.js",
8 | "dev": "nodemon server/server.js & webpack-dev-server",
9 | "build": "webpack",
10 | "test": "jest"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "aws-sdk": "^2.1490.0",
17 | "axios": "^1.5.1",
18 | "dotenv": "^16.3.1",
19 | "express": "^4.18.2",
20 | "lambda-log": "^3.1.0",
21 | "path": "^0.12.7",
22 | "pg": "^8.11.3",
23 | "pg-promise": "^10.7.5-beta.1",
24 | "postgres": "^3.4.0",
25 | "react": "^18.2.0",
26 | "react-dom": "^18.2.0",
27 | "react-router-dom": "^6.18.0",
28 | "react-syntax-highlighter": "^15.5.0"
29 | },
30 | "devDependencies": {
31 | "@babel/core": "^7.23.2",
32 | "@babel/plugin-transform-runtime": "^7.23.4",
33 | "@babel/preset-env": "^7.23.2",
34 | "@babel/preset-react": "^7.22.15",
35 | "@testing-library/jest-dom": "^6.1.4",
36 | "@testing-library/react": "^14.0.0",
37 | "babel-jest": "^29.7.0",
38 | "babel-loader": "^9.1.3",
39 | "css-loader": "^6.8.1",
40 | "eslint": "^8.53.0",
41 | "eslint-config-airbnb": "^19.0.4",
42 | "eslint-plugin-import": "^2.29.0",
43 | "eslint-plugin-jsx-a11y": "^6.8.0",
44 | "eslint-plugin-react": "^7.33.2",
45 | "eslint-plugin-react-hooks": "^4.6.0",
46 | "html-webpack-plugin": "^5.5.3",
47 | "jest": "^29.7.0",
48 | "jest-environment-jsdom": "^29.7.0",
49 | "nodemon": "^3.0.1",
50 | "react-test-renderer": "^18.2.0",
51 | "style-loader": "^3.3.3",
52 | "supertest": "^6.3.3",
53 | "webpack": "^5.89.0",
54 | "webpack-cli": "^5.1.4",
55 | "webpack-dev-server": "^4.15.1"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/server/Controllers/logController.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 | require('dotenv').config();
3 |
4 | const logController = {};
5 |
6 | /********************* FETCH LOG GROUPS ***********************************************/
7 |
8 | logController.fetchLogGroups = (req, res, next) => {
9 | const params = {
10 | limit: '50',
11 | };
12 | const AWS = res.locals.AWS;
13 | const cloudWatchLogs = new AWS.CloudWatchLogs();
14 | cloudWatchLogs.describeLogGroups(params, function (err, data) {
15 | if (err) {
16 | return next(err);
17 | } else {
18 | const groupNames = data.logGroups.map((group) => {
19 | return group.logGroupName;
20 | });
21 | const filteredGroupNames = data.logGroups
22 | .filter(
23 | (group) =>
24 | group.logGroupName && group.logGroupName.startsWith('/aws/lambda')
25 | )
26 | .map((group) => group.logGroupName);
27 | res.locals.loggroups = filteredGroupNames;
28 | return next();
29 | }
30 | });
31 | };
32 |
33 | /********************* FETCH LOG STREAMS ***********************************************/
34 |
35 | logController.fetchLogStreams = (req, res, next) => {
36 | const paramsDescribe = {
37 | logGroupName: decodeURIComponent(req.headers['log-group']),
38 | };
39 | const AWS = res.locals.AWS;
40 | const cloudWatchLogs = new AWS.CloudWatchLogs();
41 | cloudWatchLogs.describeLogStreams(paramsDescribe, function (err, data) {
42 | if (err) {
43 | return next(err); // Pass the error to the Express error handler
44 | } else {
45 | if (!data.logStreams || data.logStreams.length === 0) {
46 | return next(new Error('No log streams found')); // Handle the case where there are no log streams
47 | }
48 | const streams = data.logStreams;
49 | const streamnames = streams.map((stream) => {
50 | return stream.logStreamName;
51 | });
52 | res.locals.streams = streamnames;
53 | return next();
54 | }
55 | });
56 | };
57 | /********************* FETCH LOGS ***********************************************/
58 |
59 | logController.fetchLogs = (req, res, next) => {
60 | const cloudWatchLogs = new AWS.CloudWatchLogs();
61 | // Define parameters for filterLogEvents
62 | const params = {
63 | logGroupName: decodeURIComponent(req.headers["log-group"]),
64 | logStreamNames: [decodeURIComponent(req.headers["log-stream"])],
65 | };
66 |
67 | cloudWatchLogs.filterLogEvents(params, function (err, data) {
68 | if (err) {
69 | return next(err);
70 | } else {
71 | try {
72 | const messages = data.events.map((event) => {
73 | const messageString = event.message;
74 | const jsonRegex = /\{[\s\S]*\}/;
75 | const match = messageString.match(jsonRegex);
76 | let messageObj = null;
77 |
78 | if (match) {
79 | try {
80 | messageObj = JSON.parse(match[0]);
81 | } catch (parseErr) {
82 | console.error('Error parsing JSON', parseErr);
83 | }
84 | }
85 |
86 | const parsedLogEntry = parseLogEntry(messageString, match);
87 |
88 | // Combine the parsed log entry with the JSON object, if present
89 | if (messageObj !== null && parsedLogEntry.message) {
90 | parsedLogEntry[parsedLogEntry.message] = messageObj;
91 | }
92 | return parsedLogEntry;
93 | });
94 |
95 | res.locals.logs = messages;
96 | return next();
97 | } catch (e) {
98 | return next(e);
99 | }
100 | }
101 | });
102 |
103 | // Helper function to parse various log entry formats
104 | function parseLogEntry(logString, jsonMatch) {
105 | logString = logString.trim();
106 |
107 | if (logString.startsWith('2023')) {
108 | // Standard Log Format
109 | const parts = logString
110 | .replace(jsonMatch && jsonMatch[0], '')
111 | .split('\t')
112 | .map((part) => part.trim());
113 | return {
114 | timestamp: parts[0],
115 | id: parts[1],
116 | type: parts[2],
117 | message: parts[3],
118 | };
119 | } else if (
120 | logString.startsWith('START') ||
121 | logString.startsWith('INIT_START')
122 | ) {
123 | // START, INIT_START Formats
124 | return parseKeyValuePairs(logString);
125 | } else if (logString.startsWith('REPORT') || logString.startsWith('END')) {
126 | // REPORT, END Formats
127 | return parseKeyValuePairs(logString);
128 | } else {
129 | // Other Formats or Unrecognized Format
130 | return { raw: logString };
131 | }
132 | }
133 |
134 | function parseKeyValuePairs(logString) {
135 | const obj = {};
136 | const parts = logString.split('\t');
137 | parts.forEach((part) => {
138 | const [key, value] = part.split(':').map((s) => s.trim());
139 | if (key && value) {
140 | obj[key] = value;
141 | }
142 | });
143 | return obj;
144 | }
145 | };
146 |
147 | module.exports = logController;
148 |
--------------------------------------------------------------------------------
/server/middleware/awsConfig.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 | require('dotenv').config();
3 |
4 | // Create a function to configure AWS and return the configured AWS object
5 | const configureAWS = (req, res, next) => {
6 | // Access the headers instead of query parameters
7 | const accessKey = req.headers['access-key'];
8 | const secretKey = req.headers['secret-key'];
9 | const region = req.headers['aws-region'];
10 |
11 | // Check if all necessary credentials are provided
12 | if (!accessKey || !secretKey || !region) {
13 | return next(new Error('Missing AWS credentials in headers'));
14 | }
15 |
16 | // Update the AWS config with the credentials from the headers
17 | AWS.config.update({
18 | accessKeyId: decodeURIComponent(accessKey),
19 | secretAccessKey: decodeURIComponent(secretKey),
20 | region: decodeURIComponent(region),
21 | });
22 | // Pass AWS config to the next middleware or route handler
23 | res.locals.AWS = AWS;
24 | next();
25 | };
26 |
27 | module.exports = configureAWS;
28 |
--------------------------------------------------------------------------------
/server/routes/logRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const logController = require('../controllers/logController');
4 | const awsConfig = require('../middleware/awsConfig');
5 |
6 | router.use(awsConfig);
7 | router.get('/logs', logController.fetchLogs, (req, res) => {
8 | return res.status(200).json(res.locals.logs);
9 | });
10 | router.get('/loggroups', logController.fetchLogGroups, (req, res) => {
11 | return res.status(200).json(res.locals.loggroups);
12 | });
13 | router.get('/logstreams', logController.fetchLogStreams, (req, res) => {
14 | return res.status(200).json(res.locals.streams);
15 | });
16 |
17 | module.exports = router;
18 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 |
3 | const PORT = process.env.PORT || 3000;
4 | const express = require('express');
5 | const path = require('path');
6 | const app = express();
7 | const logRoutes = require('./routes/logRoutes');
8 |
9 | // Middleware
10 | app.use(express.json());
11 | app.use('/', express.static(path.resolve(__dirname, '../build')));
12 | app.use(express.static(path.resolve(__dirname, '../client')));
13 | app.use(express.urlencoded({ extended: true }));
14 |
15 | // Routes
16 | app.use('/credentials', logRoutes);
17 |
18 | // 404 route
19 | app.all('*', (req, res) => {
20 | res.status(404).send('The page you are looking for does not exist');
21 | });
22 |
23 | // Global error handler
24 | app.use((err, req, res, next) => {
25 | res.status(err.status || 500).json({ error: err.message });
26 | });
27 |
28 | const server = app.listen(PORT, () => console.log(`Server is listening on port ${PORT}`));
29 |
30 | module.exports = server;
31 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HTMLWebpackPlugin = require('html-webpack-plugin');
3 | module.exports = {
4 | entry: './client/src/index.js',
5 |
6 | output: {
7 | path: path.join(__dirname, '/build'),
8 | filename: 'bundle.js',
9 | },
10 |
11 | plugins: [
12 | new HTMLWebpackPlugin({
13 | template: './client/src/index.html',
14 | }),
15 | ],
16 |
17 | mode: 'development',
18 | devtool: 'eval-source-map',
19 | devServer: {
20 | static: {
21 | directory: path.resolve(__dirname, 'build'),
22 | publicPath: '/',
23 | },
24 |
25 | proxy: {
26 | '/': 'http://localhost:3000/',
27 | },
28 | },
29 |
30 | module: {
31 | rules: [
32 | {
33 | test: /.jsx?$/,
34 | exclude: '/node_modules/',
35 | use: {
36 | loader: 'babel-loader',
37 | options: {
38 | presets: ['@babel/preset-env', '@babel/preset-react'],
39 | },
40 | },
41 | },
42 | {
43 | test: /\.css$/,
44 | use: ['style-loader', 'css-loader'],
45 | },
46 | ],
47 | },
48 | };
49 |
--------------------------------------------------------------------------------