diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..b5348c468 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Local Terraform state +*.tfstate +*.tfstate.* + +# .terraform directory +.terraform/ + +# Crash log files +crash.log + +# Ignore any .tfvars files, which are used to store sensitive variables. +*.tfvars diff --git a/AWS-Tf-Handson/Day18/Terraform/main.tf b/AWS-Tf-Handson/Day18/Terraform/main.tf new file mode 100644 index 000000000..12b0efccf --- /dev/null +++ b/AWS-Tf-Handson/Day18/Terraform/main.tf @@ -0,0 +1,196 @@ +resource "random_id" "name" { + byte_length = 4 +} + +locals { + bucket_prefix = "${var.project_name}-${var.environment}" + upload_bucket_name = "${local.bucket_prefix}-${random_id.name.hex}" + processed_bucket_name = "${local.bucket_prefix}-processed-${random_id.name.hex}" + lambda_function_name = "${var.project_name}-${var.environment}-processor" +} + +##################################### +# S3 Buckets +##################################### + +## Upload Bucket (SOURCE BUCKET) +resource "aws_s3_bucket" "upload_bucket" { + bucket = local.upload_bucket_name +} + +resource "aws_s3_bucket_versioning" "upload_bucket_versioning" { + bucket = aws_s3_bucket.upload_bucket.id + + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "upload_bucket_encryption" { + bucket = aws_s3_bucket.upload_bucket.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} + +resource "aws_s3_bucket_public_access_block" "upload_bucket_public_access_block" { + bucket = aws_s3_bucket.upload_bucket.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + + +## Processed Bucket (DESTINATION BUCKET) +resource "aws_s3_bucket" "processed_bucket" { + bucket = local.processed_bucket_name +} + +resource "aws_s3_bucket_versioning" "processed_bucket_versioning" { + bucket = aws_s3_bucket.processed_bucket.id + + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "processed_bucket_encryption" { + bucket = aws_s3_bucket.processed_bucket.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} + +resource "aws_s3_bucket_public_access_block" "processed_bucket_public_access_block" { + + bucket = aws_s3_bucket.processed_bucket.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + + +###################################### +# S3 Bucket Notification for Lambda Function +###################################### +resource "aws_s3_bucket_notification" "bucket_notification" { + bucket = aws_s3_bucket.upload_bucket.id + + lambda_function { + lambda_function_arn = aws_lambda_function.lambda_function.arn + events = ["s3:ObjectCreated:*"] + } + + depends_on = [aws_lambda_permission.allow_bucket] +} + +###################################### +# IAM Role and Policy for Lambda Function +###################################### +resource "aws_iam_role" "lambda_exec_role" { + name = "${local.lambda_function_name}-exec-role" + + assume_role_policy = jsonencode({ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] + }) +} + +resource "aws_iam_role_policy" "lambda_exec_policy" { + name = "${local.lambda_function_name}-exec-policy" + role = aws_iam_role.lambda_exec_role.id + + policy = jsonencode({ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": "arn:aws:logs:${var.region}:*:*:*" + }, + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:GetObjectVersion", + ], + "Resource": "{aws_s3_bucket.upload_bucket.arn}/*" + }, + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Resource": "{aws_s3_bucket.processed_bucket.arn}/*" + } + ] + }) +} + + +#################################### +# Lambda Layer for dependencies +#################################### +resource "aws_lambda_layer_version" "lambda_layer" { + filename = "pillow_layer.zip" + layer_name = "${local.lambda_function_name}-layer" + compatible_runtimes = ["python3.9"] + description = "Lambda layer for image processing dependencies" +} + +###################################### +# Lambda Function +###################################### +data "archive_file" "lambda_function_zip" { + type = "zip" + source_file = "lambda_function.py" + output_path = "lambda_function.zip" +} + + +resource "aws_lambda_function" "lambda_function" { + + filename = "lambda_function.zip" + function_name = local.lambda_function_name + role = aws_iam_role.lambda_exec_role.arn + handler = "index.handler" + runtime = "python3.9" + + layers = [aws_lambda_layer_version.lambda_layer.arn] + + environment { + variables = { + PROCESSED_BUCKET = aws_s3_bucket.processed_bucket.bucket + } + } +} + +resource "aws_lambda_permission" "allow_bucket" { + function_name = aws_lambda_function.lambda_function.function_name + action = "lambda:InvokeFunction" + principal = "s3.amazonaws.com" + source_arn = aws_s3_bucket.upload_bucket.arn +} diff --git a/AWS-Tf-Handson/Day18/Terraform/outputs.tf b/AWS-Tf-Handson/Day18/Terraform/outputs.tf new file mode 100644 index 000000000..6ce963cd4 --- /dev/null +++ b/AWS-Tf-Handson/Day18/Terraform/outputs.tf @@ -0,0 +1,15 @@ +output "upload_bucket_name" { + value = aws_s3_bucket.upload_bucket.bucket +} + +output "processed_bucket_name" { + value = aws_s3_bucket.processed_bucket.bucket +} + +output "lambda_function_name" { + value = aws_lambda_function.lambda_function.function_name +} + +output "lambda_function_arn" { + value = aws_lambda_function.lambda_function.arn +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day18/Terraform/variables.tf b/AWS-Tf-Handson/Day18/Terraform/variables.tf new file mode 100644 index 000000000..782049bd2 --- /dev/null +++ b/AWS-Tf-Handson/Day18/Terraform/variables.tf @@ -0,0 +1,11 @@ +variable "project_name" { + default = "S3-Lambda-Project" +} + +variable "environment" { + default = "dev" +} + +variable "region" { + default = "us-east-1" +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day18/lambda/lambada_function.py b/AWS-Tf-Handson/Day18/lambda/lambada_function.py new file mode 100644 index 000000000..9d970eba9 --- /dev/null +++ b/AWS-Tf-Handson/Day18/lambda/lambada_function.py @@ -0,0 +1,194 @@ +import json +import boto3 +import os +import logging +from urllib.parse import unquote_plus +from io import BytesIO +from PIL import Image +import uuid + +# Configure logging +logger = logging.getLogger() +logger.setLevel(os.environ.get('LOG_LEVEL', 'INFO')) + +s3_client = boto3.client('s3') + +# Supported formats +SUPPORTED_FORMATS = ['JPEG', 'PNG', 'WEBP', 'BMP', 'TIFF'] +DEFAULT_QUALITY = 85 +MAX_DIMENSION = 4096 + +def lambda_handler(event, context): + """ + Lambda function to process images uploaded to S3. + Supports compression and format conversion. + """ + try: + logger.info(f"Received event: {json.dumps(event)}") + + # Get the S3 event details + for record in event['Records']: + bucket = record['s3']['bucket']['name'] + key = unquote_plus(record['s3']['object']['key']) + + logger.info(f"Processing image: {key} from bucket: {bucket}") + + # Download the image from S3 + response = s3_client.get_object(Bucket=bucket, Key=key) + image_data = response['Body'].read() + + # Process the image + processed_images = process_image(image_data, key) + + # Upload processed images to the processed bucket + processed_bucket = os.environ['PROCESSED_BUCKET'] + + for processed_image in processed_images: + output_key = processed_image['key'] + output_data = processed_image['data'] + content_type = processed_image['content_type'] + + logger.info(f"Uploading processed image: {output_key}") + + s3_client.put_object( + Bucket=processed_bucket, + Key=output_key, + Body=output_data, + ContentType=content_type, + Metadata={ + 'original-key': key, + 'processed-by': 'lambda-image-processor' + } + ) + + logger.info(f"Successfully processed {len(processed_images)} variants of {key}") + + return { + 'statusCode': 200, + 'body': json.dumps({ + 'message': 'Image processed successfully', + 'processed_images': len(processed_images) + }) + } + + except Exception as e: + logger.error(f"Error processing image: {str(e)}", exc_info=True) + return { + 'statusCode': 500, + 'body': json.dumps({ + 'error': str(e) + }) + } + + +def process_image(image_data, original_key): + """ + Process the image: create compressed versions and convert formats. + + Args: + image_data: Raw image data + original_key: Original S3 key + + Returns: + List of processed image dictionaries + """ + processed_images = [] + + try: + # Open the image + image = Image.open(BytesIO(image_data)) + + # Convert RGBA to RGB for JPEG compatibility + if image.mode in ('RGBA', 'LA', 'P'): + background = Image.new('RGB', image.size, (255, 255, 255)) + if image.mode == 'P': + image = image.convert('RGBA') + background.paste(image, mask=image.split()[-1] if image.mode in ('RGBA', 'LA') else None) + image = background + elif image.mode != 'RGB': + image = image.convert('RGB') + + # Get original format and dimensions + original_format = image.format or 'JPEG' + width, height = image.size + + logger.info(f"Original image: {width}x{height}, format: {original_format}") + + # Resize if image is too large + if width > MAX_DIMENSION or height > MAX_DIMENSION: + ratio = min(MAX_DIMENSION / width, MAX_DIMENSION / height) + new_width = int(width * ratio) + new_height = int(height * ratio) + image = image.resize((new_width, new_height), Image.Resampling.LANCZOS) + logger.info(f"Resized to: {new_width}x{new_height}") + + # Generate base filename + base_name = os.path.splitext(original_key)[0] + unique_id = str(uuid.uuid4())[:8] + + # Create multiple variants + variants = [ + {'format': 'JPEG', 'quality': 85, 'suffix': 'compressed'}, + {'format': 'JPEG', 'quality': 60, 'suffix': 'low'}, + {'format': 'WEBP', 'quality': 85, 'suffix': 'webp'}, + {'format': 'PNG', 'quality': None, 'suffix': 'png'} + ] + + for variant in variants: + output = BytesIO() + save_format = variant['format'] + + if variant['quality']: + image.save(output, format=save_format, quality=variant['quality'], optimize=True) + else: + image.save(output, format=save_format, optimize=True) + + output.seek(0) + + # Generate output key + extension = save_format.lower() + if extension == 'jpeg': + extension = 'jpg' + + output_key = f"{base_name}_{variant['suffix']}_{unique_id}.{extension}" + + # Determine content type + content_type_map = { + 'JPEG': 'image/jpeg', + 'PNG': 'image/png', + 'WEBP': 'image/webp' + } + content_type = content_type_map.get(save_format, 'image/jpeg') + + processed_images.append({ + 'key': output_key, + 'data': output.getvalue(), + 'content_type': content_type, + 'format': save_format, + 'quality': variant['quality'] + }) + + logger.info(f"Created variant: {output_key} ({save_format}, quality: {variant['quality']})") + + # Create thumbnail + thumbnail = image.copy() + thumbnail.thumbnail((300, 300), Image.Resampling.LANCZOS) + thumb_output = BytesIO() + thumbnail.save(thumb_output, format='JPEG', quality=80, optimize=True) + thumb_output.seek(0) + + processed_images.append({ + 'key': f"{base_name}_thumbnail_{unique_id}.jpg", + 'data': thumb_output.getvalue(), + 'content_type': 'image/jpeg', + 'format': 'JPEG', + 'quality': 80 + }) + + logger.info(f"Created thumbnail: {base_name}_thumbnail_{unique_id}.jpg") + + return processed_images + + except Exception as e: + logger.error(f"Error in process_image: {str(e)}", exc_info=True) + raise \ No newline at end of file diff --git a/AWS-Tf-Handson/Day18/scripts/build_layer_docker.sh b/AWS-Tf-Handson/Day18/scripts/build_layer_docker.sh new file mode 100755 index 000000000..153f7c164 --- /dev/null +++ b/AWS-Tf-Handson/Day18/scripts/build_layer_docker.sh @@ -0,0 +1,44 @@ + +#!/bin/bash +set -e + +echo "๐Ÿš€ Building Lambda Layer with Pillow using Docker..." + +# Get the directory where the script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PROJECT_DIR="$( cd "$SCRIPT_DIR/.." && pwd )" +TERRAFORM_DIR="$PROJECT_DIR/terraform" + +# Check if Docker is installed +if ! command -v docker &> /dev/null; then + echo "โŒ Docker is not installed. Please install Docker first." + echo "๐Ÿ“– Visit: https://docs.docker.com/get-docker/" + exit 1 +fi + +# Check if Docker is running +if ! docker info &> /dev/null 2>&1; then + echo "โŒ Docker is not running. Please start Docker first." + exit 1 +fi + +echo "๐Ÿ“ฆ Building layer in Linux container (Python 3.12)..." + +# Build the layer using Docker with Python 3.12 on Linux AMD64 +docker run --rm \ + --platform linux/amd64 \ + -v "$TERRAFORM_DIR":/output \ + python:3.12-slim \ + bash -c " + echo '๐Ÿ“ฆ Installing Pillow for Linux AMD64...' && \ + pip install --quiet Pillow==10.4.0 -t /tmp/python/lib/python3.12/site-packages/ && \ + cd /tmp && \ + echo '๐Ÿ“ฆ Creating layer zip file...' && \ + apt-get update -qq && apt-get install -y -qq zip > /dev/null 2>&1 && \ + zip -q -r pillow_layer.zip python/ && \ + cp pillow_layer.zip /output/ && \ + echo 'โœ… Layer built successfully for Linux (Lambda-compatible)!' + " + +echo "๐Ÿ“ Location: $TERRAFORM_DIR/pillow_layer.zip" +echo "โœ… Layer is now compatible with AWS Lambda on all platforms!" diff --git a/AWS-Tf-Handson/Day18/scripts/deploy.sh b/AWS-Tf-Handson/Day18/scripts/deploy.sh new file mode 100755 index 000000000..fa0c9327d --- /dev/null +++ b/AWS-Tf-Handson/Day18/scripts/deploy.sh @@ -0,0 +1,62 @@ +#!/bin/bash +set -e + +echo "๐Ÿš€ Deploying Image Processor Application..." + +# Get the directory where the script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PROJECT_DIR="$( cd "$SCRIPT_DIR/.." && pwd )" + +# Check if AWS CLI is installed +if ! command -v aws &> /dev/null; then + echo "โŒ AWS CLI is not installed. Please install it first." + exit 1 +fi + +# Check if Terraform is installed +if ! command -v terraform &> /dev/null; then + echo "โŒ Terraform is not installed. Please install it first." + exit 1 +fi + +# Build Lambda layer using Docker (works on all platforms) +echo "๐Ÿ“ฆ Building Lambda layer with Docker..." +chmod +x "$SCRIPT_DIR/build_layer_docker.sh" +bash "$SCRIPT_DIR/build_layer_docker.sh" + +# Initialize Terraform +echo "๐Ÿ”ง Initializing Terraform..." +cd "$PROJECT_DIR/terraform" +terraform init + +# Plan deployment +echo "๐Ÿ“‹ Planning Terraform deployment..." +terraform plan -out=tfplan + +# Apply deployment +#echo "๐Ÿš€ Applying Terraform deployment..." +#terraform apply tfplan + +# Get outputs +echo "๐Ÿ“Š Getting deployment outputs..." +UPLOAD_BUCKET=$(terraform output -raw upload_bucket_name) +PROCESSED_BUCKET=$(terraform output -raw processed_bucket_name) +LAMBDA_FUNCTION=$(terraform output -raw lambda_function_name) +REGION=$(terraform output -raw region) + +#echo "" +#echo "โœ… Deployment completed successfully!" +#echo "" +#echo "๐Ÿ“ฆ S3 Buckets:" +#echo " Upload: s3://${UPLOAD_BUCKET}" +#echo " Processed: s3://${PROCESSED_BUCKET}" +#echo "" +#echo "โšก Lambda Function: ${LAMBDA_FUNCTION}" +#echo "๐ŸŒ Region: ${REGION}" +#echo "" +#echo "๐ŸŽฏ Usage:" +#echo " Upload an image to the upload bucket:" +#echo " aws s3 cp your-image.jpg s3://${UPLOAD_BUCKET}/" +#echo "" +#echo " The Lambda function will automatically process it and save variants to:" +#echo " s3://${PROCESSED_BUCKET}/" \ No newline at end of file diff --git a/AWS-Tf-Handson/Day19/.terraform.lock.hcl b/AWS-Tf-Handson/Day19/.terraform.lock.hcl new file mode 100644 index 000000000..2f418f129 --- /dev/null +++ b/AWS-Tf-Handson/Day19/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "4.67.0" + constraints = "~> 4.0" + hashes = [ + "h1:P43vwcDPG99x5WBbmqwUPgfJrfXf6/ucAIbGlRb7k1w=", + "zh:0843017ecc24385f2b45f2c5fce79dc25b258e50d516877b3affee3bef34f060", + "zh:19876066cfa60de91834ec569a6448dab8c2518b8a71b5ca870b2444febddac6", + "zh:24995686b2ad88c1ffaa242e36eee791fc6070e6144f418048c4ce24d0ba5183", + "zh:4a002990b9f4d6d225d82cb2fb8805789ffef791999ee5d9cb1fef579aeff8f1", + "zh:559a2b5ace06b878c6de3ecf19b94fbae3512562f7a51e930674b16c2f606e29", + "zh:6a07da13b86b9753b95d4d8218f6dae874cf34699bca1470d6effbb4dee7f4b7", + "zh:768b3bfd126c3b77dc975c7c0e5db3207e4f9997cf41aa3385c63206242ba043", + "zh:7be5177e698d4b547083cc738b977742d70ed68487ce6f49ecd0c94dbf9d1362", + "zh:8b562a818915fb0d85959257095251a05c76f3467caa3ba95c583ba5fe043f9b", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9c385d03a958b54e2afd5279cd8c7cbdd2d6ca5c7d6a333e61092331f38af7cf", + "zh:b3ca45f2821a89af417787df8289cb4314b273d29555ad3b2a5ab98bb4816b3b", + "zh:da3c317f1db2469615ab40aa6baba63b5643bae7110ff855277a1fb9d8eb4f2c", + "zh:dc6430622a8dc5cdab359a8704aec81d3825ea1d305bbb3bbd032b1c6adfae0c", + "zh:fac0d2ddeadf9ec53da87922f666e1e73a603a611c57bcbc4b86ac2821619b1d", + ] +} diff --git a/AWS-Tf-Handson/Day19/main.tf b/AWS-Tf-Handson/Day19/main.tf new file mode 100644 index 000000000..02c404be9 --- /dev/null +++ b/AWS-Tf-Handson/Day19/main.tf @@ -0,0 +1,91 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } + # Backend configuration example is placed in backend.tf (commented out by default) +} + +# EC2 instance used for provisioner demos. +# Each provisioner block is included below but wrapped in block comments (/* ... */). +# For the demo, uncomment one provisioner block at a time, then `terraform apply`. + +data "aws_ami" "ubuntu" { + most_recent = true + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } + + owners = ["099720109477"] # Canonical +} + +########################################################## +# Create a security group to allow SSH access from anywhere. +########################################################## +resource "aws_security_group" "ssh_access" { + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + description = "Allow all outbound traffic" +} + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + description = "Allow SSH access" + } +} + +resource "aws_instance" "instance" { + ami = data.aws_ami.ubuntu.id + instance_type = var.instance_type + key_name = var.key_name + vpc_security_group_ids = [aws_security_group.ssh_access.id] + + + tags = { + Name = "AWS-Tf-Handson-Day19" + } + +#provisioner "local-exec" { +# command = "echo Instance ${self.id} has been created with IP ${self.public_ip}" +# } +#} + +provisioner "remote-exec" { + inline = [ + "echo 'Instance ${self.id} has been created with IP ${self.public_ip}'", + "sudo apt-get update -y", + "echo 'Provisioning complete!' | tee /home/ubuntu/provisioning.log" + ] + connection { + type = "ssh" + user = "ubuntu" + private_key = file("${path.module}/terraform-demo-key.pem") + host = self.public_ip + } +} + provisioner "file" { + source = "${path.module}/scripts/welcome.sh" + destination = "/home/ubuntu/welcome.sh" + connection { + type = "ssh" + user = "ubuntu" + private_key = file("${path.module}/terraform-demo-key.pem") + host = self.public_ip +} +} +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day19/output.tf b/AWS-Tf-Handson/Day19/output.tf new file mode 100644 index 000000000..680e8498a --- /dev/null +++ b/AWS-Tf-Handson/Day19/output.tf @@ -0,0 +1,22 @@ +output "aws_instance_public_ip" { + description = "The public IP address of the EC2 instance" + value = aws_instance.instance.public_ip + +} + + +output "aws_instance" { + value = aws_instance.instance.id + description = "Instance Id" +} + +output "aws_security_group_id" { + value = aws_security_group.ssh_access.id + description = "Security Group Id" +} + + +output "aws_profile" { + value = "468284643560" + description = "AWS Profile used" +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day19/provider.tf b/AWS-Tf-Handson/Day19/provider.tf new file mode 100644 index 000000000..aef11e414 --- /dev/null +++ b/AWS-Tf-Handson/Day19/provider.tf @@ -0,0 +1,4 @@ +provider "aws" { + region = "us-east-1" # Replace with your desired AWS region + profile = "468284643560" +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day19/scripts/welcome.sh b/AWS-Tf-Handson/Day19/scripts/welcome.sh new file mode 100644 index 000000000..81117bff2 --- /dev/null +++ b/AWS-Tf-Handson/Day19/scripts/welcome.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Welcome to Day 19 of AWS Terraform Hands-on!" +echo "This script is located at: $(pwd)/welcome.sh" +echo "Let's get started with Terraform and AWS!" \ No newline at end of file diff --git a/AWS-Tf-Handson/Day19/terraform-demo-key.pem b/AWS-Tf-Handson/Day19/terraform-demo-key.pem new file mode 100644 index 000000000..6e2f14d4a --- /dev/null +++ b/AWS-Tf-Handson/Day19/terraform-demo-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA30nH8io60AyeknK0BIQh9FVlIy+LJW+nhdeuiLUqWtV/ZBmO +c//B4q5KKH5CBai4s7pF+qNVu6RZzJPPOm9Gtogh068Ox30kLvpXKv4MACIoTnWD +bPiQlFzJo6aaZxFXkKQIhezfvJIZRVzneKjrCA26YvLga/gmuSl+QtCc+FOBRs+A +pCIXiLTU94vrWvBl++OVtxoZwj58D2JWsK/hZwDQzGTrrKa+yml8+m3zeI96EESU +mvPDiE5dFp2y+CQj4L9PtQRCOZV8HRTql/z4CeWoP0r2IXE+tR2lGcxU3G90/uXy +CcFjoOy+dBEn6NgbIoEoSxy8s8ifakukfas21wIDAQABAoIBADbahUg0n6Yks3/+ +tcK2QzEnGWV/dIuJ1nnG9pWXDASsllMdBCXzGsp5TraL7eQ+AsIEbNSZ86HZSkY7 +uZj9ZT3KS6UZSGQxlgYEcg2Zw5D00zoTHGpU1g/ci5ysfRn85Kof4ggknQJSUer8 +W9EEjli4JqXQTNm5aKnsS5xWa3mFdKheO9Yhl2Q8cfsKNJ0wVgsyoAM2tWIsHNqC +i4cCmkUmEd+xf2SS3D/IVSQJ4vxRiS5eucpNytLEHg4DezL3j4S3yhsZtwGoO9Bw +xnSKqmioRsFUYxMkSfkXLA2OJE/PbqiHSTTTlJ+4UJHtwqrRwqBf77M6fjI11g7U +X6waFCECgYEA9KtRVn+ex4dIJ/rk6Sj+4Yx4YJIuSmGR/CS6UgLU5qk55Q8lGxTY +htQjdCz0oIkDgLI99bbenFRWT9WzobZTItXLZHGtBUVRuhPZeY63KvRMG06hrkNE +/gX87GrKeD30+IlFGqy7o3cUqVayhM244xwV/WkC6Dmi9xpd5Aw0FLUCgYEA6aD6 +8w6caUpQyFgcGzNT2+VkCHdOgvofceHjhI8Z/TnL5buKwMlhMNwv8xu4buLBHzZW +4mugTCWs3E8zUXTosDMY079unD2cROtUZCrmipEjAT5VYfPOzYqqVsmvd4YPJkdb +vcsbGRdB2Urvg50OFjfcUK0mFSZRifaXmByXgNsCgYBSkrcCwFNv94IsrAoxfnp2 +2JeP0AX7aG6CcErJftcneZlmavQU7bYd2t6USM/Oli8ucfljQfJjRDtU+kSDoSrx +qMHaBltkWf65FBXjZnz+7C/7T1BVpbJVIOQ5TdlDN3XDo2BFHmK9SmUYuX/KPjvV +uhbZzWUDxt/vEWD6o/u3uQKBgQDMiUMqx//nrkhLHdh9d9JTEeEiCFGUGaat7TVj +OGVobNE1r5sqbSPJwvu3uu0dJygsS0aFs/QCtnk/55bmGZQdrp5tw4ry/n1xWRXp +HCX56lbbH9dgijLjgCLhGHGmEpfwy0hmQQrYTKQ5uIE4mKdcjEs11mEg4ws/3cOa +SG3bQQKBgCCtQ7aw98WP82Bdin9bwmKk6Lnm0+uz1Ug+8YRaVdSWnWZ8HO3ZEbQK +quFt/j8M2iUsXdcCJqvGCwImzfYJ7ZRt1sQwrzNGIrL/MeKMKbb50pv6YR/snCZb +TNWfuVBobyu1qVO6FhAvnJ66nIwJmimhwnOHxKhF9vUfGoTt08D6 +-----END RSA PRIVATE KEY----- diff --git a/AWS-Tf-Handson/Day19/variable.tf b/AWS-Tf-Handson/Day19/variable.tf new file mode 100644 index 000000000..9991a2b1a --- /dev/null +++ b/AWS-Tf-Handson/Day19/variable.tf @@ -0,0 +1,11 @@ +variable "instance_type" { + description = "Type of EC2 instance" + type = string + default = "t3.micro" +} + +variable "key_name" { + description = "Name of the key pair" + type = string + default = "terraform-demo-key" +} diff --git a/AWS-Tf-Handson/Day20/.terraform.lock.hcl b/AWS-Tf-Handson/Day20/.terraform.lock.hcl new file mode 100644 index 000000000..a7af1d87b --- /dev/null +++ b/AWS-Tf-Handson/Day20/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.27.0" + hashes = [ + "h1:yey+4NnYAp2quzSKUaExTsbb+hDvtjl3EpdrbDdnM4Y=", + "zh:177a24b806c72e8484b5cabc93b2b38e3d770ae6f745a998b54d6619fd0e8129", + "zh:4ac4a85c14fb868a3306b542e6a56c10bd6c6d5a67bc0c9b8f6a9060cf5f3be7", + "zh:552652185bc85c8ba1da1d65dea47c454728a5c6839c458b6dcd3ce71c19ccfc", + "zh:60284b8172d09aee91eae0856f09855eaf040ce3a58d6933602ae17c53f8ed04", + "zh:6be38d156756ca61fb8e7c752cc5d769cd709686700ac4b230f40a6e95b5dbc9", + "zh:7a409138fae4ef42e3a637e37cb9efedf96459e28a3c764fc4e855e8db9a7485", + "zh:8070cf5224ed1ed3a3e9a59f7c30ff88bf071c7567165275d477c1738a56c064", + "zh:894439ef340a9a79f69cd759e27ad11c7826adeca27be1b1ca82b3c9702fa300", + "zh:89d035eebf08a97c89374ff06040955ddc09f275ecca609d0c9d58d149bef5cf", + "zh:985b1145d724fc1f38369099e4a5087141885740fd6c0b1dbc492171e73c2e49", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a80b47ae8d1475201c86bd94a5dcb9dd4da5e8b73102a90820b68b66b76d50fd", + "zh:d3395be1556210f82199b9166a6b2e677cee9c4b67e96e63f6c3a98325ad7ab0", + "zh:db0b869d09657f6f1e4110b56093c5fcdf9dbdd97c020db1e577b239c0adcbce", + "zh:ffc72e680370ae7c21f9bd3082c6317730df805c6797427839a6b6b7e9a26a01", + ] +} diff --git a/AWS-Tf-Handson/Day20/Readme.md b/AWS-Tf-Handson/Day20/Readme.md new file mode 100644 index 000000000..f8a5ef3d3 --- /dev/null +++ b/AWS-Tf-Handson/Day20/Readme.md @@ -0,0 +1,99 @@ +## Traffic Flow + 1. Internet โ†’ IGW - External traffic enters through Internet Gateway. + 2. IGW โ†’ Public Subnets - Routed to public subnets. + 3. Public Subnets โ†’ NAT Gateway - Outbound traffic from private subnets. + 4. NAT โ†’ Private Subnets - NAT translates private IPs to public. + 5. Private Subnets โ†’ EKS - Kubernetes nodes communicate internally. + 6. EKS โ†’ Internet - Pods reach internet via NAT Gateway. + + + +This is a classic AWS Secure Network Architecture. It is designed to keep your application (EKS) safe while still allowing it to talk to the outside world when necessary. + +Here is a breakdown of that traffic flow using a simple "Secure Office Building" analogy to make it concrete. + +## The Analogy: A Secure Office Building + VPC: The Building. + Internet: The Street outside. + IGW (Internet Gateway): The Main Front Door. + Public Subnet: The Lobby/Reception. It has direct access to the front door. + Private Subnet: The Secure Work Area in the back. It has no direct door to the street. + EKS (Kubernetes): The Employees working in the secure area. + NAT Gateway: The Mailroom Clerk sitting in the Lobby. + + +## Step-by-Step Flow Explanation +Here is how the traffic moves based on the lines you provided: + +1. ### Internet โ†’ IGW + **Concept**: + External traffic enters through the Internet Gateway. + + **Analogy**: + A delivery person walks from the street to the building's front door. + + **Technical**: + The IGW is the only entry/exit point for traffic between your VPC and the Internet. + +2. ### IGW โ†’ Public Subnets + **Concept**: + Traffic is routed to the public subnets. + + **Analogy**: + The delivery person walks through the door into the Lobby. + + **Technical**: + The Route Table for the public subnet has a rule: 0.0.0.0/0 -> IGW. This allows resources here (like Load Balancers or the NAT Gateway) to send and receive traffic directly. + +3. ### Public Subnets โ†’ NAT Gateway + **Concept**: + The NAT Gateway resides here. + + **Analogy**: + The Mailroom Clerk sits in the Lobby because they need access to the front door to send mail out. + + **Technical**: + A NAT Gateway must be placed in a Public Subnet. If it were in a private subnet, it wouldn't be able to reach the IGW, rendering it useless. + +4. ### NAT โ†’ Private Subnets + **Concept**: + NAT translates private IPs to public IPs for the private subnets. + + **Analogy**: + The Employees in the back (Private Subnet) cannot walk out the front door. When they need to send a letter, they send it to the Mailroom Clerk (NAT). + + **Technical**: + The Private Subnet's Route Table has a rule: 0.0.0.0/0 -> NAT Gateway ID. This tells all traffic destined for the internet to go to the NAT Gateway first. + +5. ### Private Subnets โ†’ EKS + **Concept**: + Kubernetes nodes run here. + + **Analogy**: + This is where the desks are. The Employees (EKS Nodes) work here safely, away from the public street. + + **Technical**: + For security, EKS worker nodes are almost always placed in Private Subnets so they cannot be directly attacked from the internet. + +6. ### EKS โ†’ Internet (The Outbound Flow) + **Concept**: + Pods reach the internet via the NAT Gateway. + + **Analogy**: + An Employee (Pod) needs to order lunch (download a Docker image). + + They hand the order to the Mailroom Clerk (NAT) in the Lobby. + + The Clerk replaces the Employee's desk number with the Building's address (IP Translation). + + The Clerk walks out the Front Door (IGW) to get the lunch. + + **Technical**: + + Pod (IP 10.0.2.50) sends a request to google.com. + + Router sees the destination is external and sends it to NAT Gateway. + + NAT Gateway replaces 10.0.2.50 with its own Public IP (e.g., 54.1.1.1). + + NAT Gateway sends it to IGW -> Internet. \ No newline at end of file diff --git a/AWS-Tf-Handson/Day20/main.tf b/AWS-Tf-Handson/Day20/main.tf new file mode 100644 index 000000000..3c6882338 --- /dev/null +++ b/AWS-Tf-Handson/Day20/main.tf @@ -0,0 +1,45 @@ +data "aws_ami" "ubuntu" { + most_recent = true + owners = ["099720109477"] # Canonical + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] + } +} + +#data "aws_vpc" "default" { +# default = true +#} + +# 1. Call the Security Group Module +module "security_group" { + source = "./modules/security_group" + vpc_id = module.vpc.vpc_id +} + + +# 2. Call the Module +module "my_web_server" { + # SOURCE: Where is the module code? + source = "./modules/ec2_instance" + +# 3. Using count to create multiple instances + count = var.instance_count + + # INPUTS: Passing values to the module's variables + ami_id = data.aws_ami.ubuntu.id + instance_type = var.instance_type # You can define this variable in main.tf or hardcode a value + instance_name = var.instance_name + + # New input for security group ID + security_group_id = module.security_group.security_group_id # Replace with your actual security group ID + + # Input for Subnet ID is required to launch the instance in the specific VPC + subnet_id = module.vpc.public_subnets[0] # Using the first public subnet from the VPC module +} + +#Call the VPC module +module "vpc" { + source = "./modules/vpc" +} diff --git a/AWS-Tf-Handson/Day20/modules/ec2_instance/main.tf b/AWS-Tf-Handson/Day20/modules/ec2_instance/main.tf new file mode 100644 index 000000000..b07d2a982 --- /dev/null +++ b/AWS-Tf-Handson/Day20/modules/ec2_instance/main.tf @@ -0,0 +1,11 @@ +resource "aws_instance" "name" { + ami = var.ami_id + instance_type = var.instance_type + vpc_security_group_ids = [var.security_group_id] + subnet_id = var.subnet_id + + + tags = { + Name = var.instance_name + } +} diff --git a/AWS-Tf-Handson/Day20/modules/ec2_instance/outputs.tf b/AWS-Tf-Handson/Day20/modules/ec2_instance/outputs.tf new file mode 100644 index 000000000..4cf635c1f --- /dev/null +++ b/AWS-Tf-Handson/Day20/modules/ec2_instance/outputs.tf @@ -0,0 +1,8 @@ +output "public_ip" { + description = "Public IP of the created instance" + value = aws_instance.name.public_ip +} + +output "instance_id" { + value = aws_instance.name.id +} diff --git a/AWS-Tf-Handson/Day20/modules/ec2_instance/variables.tf b/AWS-Tf-Handson/Day20/modules/ec2_instance/variables.tf new file mode 100644 index 000000000..61f60687d --- /dev/null +++ b/AWS-Tf-Handson/Day20/modules/ec2_instance/variables.tf @@ -0,0 +1,25 @@ +variable "ami_id" { + description = "The AMI ID to deploy" + type = string +} + +variable "instance_type" { + description = "The instance type (e.g., t2.micro)" + type = string + default = "t3.micro" +} + +variable "instance_name" { + description = "The name tag for the instance" + type = string +} + +variable "security_group_id" { + description = "The Security Group ID to attach to the instance" + type = string +} + +variable "subnet_id" { + description = "The Subnet ID to launch the instance in" + type = string +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day20/modules/security_group/main.tf b/AWS-Tf-Handson/Day20/modules/security_group/main.tf new file mode 100644 index 000000000..81c6f6c84 --- /dev/null +++ b/AWS-Tf-Handson/Day20/modules/security_group/main.tf @@ -0,0 +1,20 @@ +# 4. Security Group for the EC2 Instance +resource "aws_security_group" "this" { + name = "web-sg" + description = "Allow HTTP and SSH traffic" + vpc_id = var.vpc_id + + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day20/modules/security_group/output.tf b/AWS-Tf-Handson/Day20/modules/security_group/output.tf new file mode 100644 index 000000000..9936dc180 --- /dev/null +++ b/AWS-Tf-Handson/Day20/modules/security_group/output.tf @@ -0,0 +1,11 @@ +output "security_group_id" { + value = aws_security_group.this.id +} + +output "security_group_name" { + value = aws_security_group.this.name +} + +output "vpc_id" { + value = aws_security_group.this.vpc_id +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day20/modules/security_group/variables.tf b/AWS-Tf-Handson/Day20/modules/security_group/variables.tf new file mode 100644 index 000000000..71eee4797 --- /dev/null +++ b/AWS-Tf-Handson/Day20/modules/security_group/variables.tf @@ -0,0 +1,4 @@ +variable "vpc_id" { + description = "The VPC ID where the security group will be created" + type = string +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day20/modules/vpc/main.tf b/AWS-Tf-Handson/Day20/modules/vpc/main.tf new file mode 100644 index 000000000..47419b69d --- /dev/null +++ b/AWS-Tf-Handson/Day20/modules/vpc/main.tf @@ -0,0 +1,19 @@ +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" # It is highly recommended to pin a specific version + + name = "my-vpc" + cidr = var.cidr + + azs = var.azs + private_subnets = var.private_subnets + public_subnets = var.public_subnets + + enable_nat_gateway = true + enable_vpn_gateway = true + + tags = { + Terraform = "true" + Environment = "dev" + } +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day20/modules/vpc/output.tf b/AWS-Tf-Handson/Day20/modules/vpc/output.tf new file mode 100644 index 000000000..3fb5976b9 --- /dev/null +++ b/AWS-Tf-Handson/Day20/modules/vpc/output.tf @@ -0,0 +1,15 @@ +output "vpc_id" { + value = module.vpc.vpc_id +} + +output "name" { + value = module.vpc.name +} + +output "public_subnets" { + value = module.vpc.public_subnets +} + +output "private_subnets" { + value = module.vpc.private_subnets +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day20/modules/vpc/variable.tf b/AWS-Tf-Handson/Day20/modules/vpc/variable.tf new file mode 100644 index 000000000..ee36bde2f --- /dev/null +++ b/AWS-Tf-Handson/Day20/modules/vpc/variable.tf @@ -0,0 +1,15 @@ +variable "cidr" { + default = "10.0.0.0/16" +} + +variable "azs" { + default = ["us-east-1a", "us-east-1b", "us-east-1c"] +} + +variable "private_subnets" { + default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] +} + +variable "public_subnets" { + default = [ "10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24" ] +} diff --git a/AWS-Tf-Handson/Day20/outputs.tf b/AWS-Tf-Handson/Day20/outputs.tf new file mode 100644 index 000000000..1f721801f --- /dev/null +++ b/AWS-Tf-Handson/Day20/outputs.tf @@ -0,0 +1,13 @@ +output "instance_id" { + description = "ID of the EC2 instance" + value = module.my_web_server[*].instance_id +} + +output "security_group_id" { + description = "ID of the Security Group created by the module" + value = module.security_group.security_group_id +} + +output "vpc_id" { + value = module.vpc.vpc_id +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day20/provider.tf b/AWS-Tf-Handson/Day20/provider.tf new file mode 100644 index 000000000..aef11e414 --- /dev/null +++ b/AWS-Tf-Handson/Day20/provider.tf @@ -0,0 +1,4 @@ +provider "aws" { + region = "us-east-1" # Replace with your desired AWS region + profile = "468284643560" +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day20/variables.tf b/AWS-Tf-Handson/Day20/variables.tf new file mode 100644 index 000000000..a3a69c501 --- /dev/null +++ b/AWS-Tf-Handson/Day20/variables.tf @@ -0,0 +1,12 @@ +variable "instance_type" { + default = "t3.micro" +} + +variable "instance_name" { + default = "Module-Demo-Server" +} + +variable "instance_count" { + default = 1 +} + diff --git a/AWS-Tf-Handson/Day21/.terraform.lock.hcl b/AWS-Tf-Handson/Day21/.terraform.lock.hcl new file mode 100644 index 000000000..b82fc7370 --- /dev/null +++ b/AWS-Tf-Handson/Day21/.terraform.lock.hcl @@ -0,0 +1,43 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.27.0" + hashes = [ + "h1:yey+4NnYAp2quzSKUaExTsbb+hDvtjl3EpdrbDdnM4Y=", + "zh:177a24b806c72e8484b5cabc93b2b38e3d770ae6f745a998b54d6619fd0e8129", + "zh:4ac4a85c14fb868a3306b542e6a56c10bd6c6d5a67bc0c9b8f6a9060cf5f3be7", + "zh:552652185bc85c8ba1da1d65dea47c454728a5c6839c458b6dcd3ce71c19ccfc", + "zh:60284b8172d09aee91eae0856f09855eaf040ce3a58d6933602ae17c53f8ed04", + "zh:6be38d156756ca61fb8e7c752cc5d769cd709686700ac4b230f40a6e95b5dbc9", + "zh:7a409138fae4ef42e3a637e37cb9efedf96459e28a3c764fc4e855e8db9a7485", + "zh:8070cf5224ed1ed3a3e9a59f7c30ff88bf071c7567165275d477c1738a56c064", + "zh:894439ef340a9a79f69cd759e27ad11c7826adeca27be1b1ca82b3c9702fa300", + "zh:89d035eebf08a97c89374ff06040955ddc09f275ecca609d0c9d58d149bef5cf", + "zh:985b1145d724fc1f38369099e4a5087141885740fd6c0b1dbc492171e73c2e49", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a80b47ae8d1475201c86bd94a5dcb9dd4da5e8b73102a90820b68b66b76d50fd", + "zh:d3395be1556210f82199b9166a6b2e677cee9c4b67e96e63f6c3a98325ad7ab0", + "zh:db0b869d09657f6f1e4110b56093c5fcdf9dbdd97c020db1e577b239c0adcbce", + "zh:ffc72e680370ae7c21f9bd3082c6317730df805c6797427839a6b6b7e9a26a01", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.7.2" + hashes = [ + "h1:hkKSY5xI4R1H4Yrg10HHbtOoxZif2dXa9HFPSbaVg5o=", + "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", + "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", + "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", + "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", + "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", + "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", + "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", + "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", + "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", + "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", + ] +} diff --git a/AWS-Tf-Handson/Day21/DEMO_GUIDE.md b/AWS-Tf-Handson/Day21/DEMO_GUIDE.md new file mode 100644 index 000000000..22d35f794 --- /dev/null +++ b/AWS-Tf-Handson/Day21/DEMO_GUIDE.md @@ -0,0 +1,425 @@ +# Demo Guide: AWS Policy and Governance (Day 21) + +--- + +## ๐Ÿ“– Part 1: Theory & Concepts (Explain First) + +### What is AWS Governance? + +Governance = **Rules + Enforcement + Monitoring** + +It ensures your AWS resources follow security and compliance standards automatically. + +### Why Do We Need It? + +| Problem | Solution | +|---------|----------| +| Developers create public S3 buckets | Config rule detects & alerts | +| EC2 launched without tags | IAM policy blocks it | +| Someone deletes critical data | MFA policy prevents it | +| Manual audits are slow | Automated 24/7 monitoring | + +### Two Types of Controls + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ”‚ +โ”‚ 1. PREVENTIVE (IAM Policies) โ”‚ +โ”‚ โ†’ Blocks bad actions BEFORE they happen โ”‚ +โ”‚ โ†’ Example: "Cannot delete S3 without MFA" โ”‚ +โ”‚ โ”‚ +โ”‚ 2. DETECTIVE (AWS Config) โ”‚ +โ”‚ โ†’ Finds violations AFTER they happen โ”‚ +โ”‚ โ†’ Example: "This bucket is not encrypted" โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Architecture Overview + +``` + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ IAM POLICIES โ”‚ โ—„โ”€โ”€ PREVENT bad actions + โ”‚ โ€ข MFA Delete โ”‚ + โ”‚ โ€ข Encryption โ”‚ + โ”‚ โ€ข Required Tags โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ AWS CONFIG โ”‚ โ—„โ”€โ”€ DETECT violations + โ”‚ 6 Rules โ”‚ + โ”‚ (Compliance) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ S3 BUCKET โ”‚ โ—„โ”€โ”€ STORE logs + โ”‚ ๐Ÿ”’ Encrypted โ”‚ + โ”‚ ๐Ÿ”’ Versioned โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### What We'll Build + +| Component | Count | Purpose | +|-----------|-------|---------| +| IAM Policies | 3 | Prevent bad actions | +| IAM User | 1 | Demo policy attachment | +| S3 Bucket | 1 | Secure log storage | +| Config Rules | 6 | Compliance checks | +| Config Recorder | 1 | Monitor resources | + +--- + +## ๐Ÿ› ๏ธ Part 2: Demo - File by File Explanation + +### File Structure + +``` +day21/ +โ”œโ”€โ”€ provider.tf โ†’ AWS provider setup +โ”œโ”€โ”€ variables.tf โ†’ Customizable inputs +โ”œโ”€โ”€ iam.tf โ†’ Policies (PREVENT) +โ”œโ”€โ”€ config.tf โ†’ Config rules (DETECT) +โ”œโ”€โ”€ main.tf โ†’ S3 bucket (STORE) +โ””โ”€โ”€ outputs.tf โ†’ Display results +``` + +--- + +### ๐Ÿ“„ provider.tf - AWS Provider + +**What it does:** Tells Terraform to use AWS + +```hcl +provider "aws" { + region = var.aws_region # us-east-1 by default +} +``` + +**Explain:** This connects Terraform to your AWS account. + +--- + +### ๐Ÿ“„ variables.tf - Inputs + +**What it does:** Makes the code reusable + +```hcl +variable "aws_region" { + default = "us-east-1" +} + +variable "project_name" { + default = "terraform-governance-demo" +} +``` + +**Explain:** You can change region or project name without editing other files. + +--- + +### ๐Ÿ“„ iam.tf - IAM Policies (PREVENTIVE CONTROLS) + +**What it does:** Creates 3 security policies + +#### Policy 1: MFA Delete Policy + +```hcl +# Denies S3 deletion unless user has MFA +"Condition": { + "BoolIfExists": { + "aws:MultiFactorAuthPresent": "false" + } +} +``` + +**Explain:** +- If someone tries to delete S3 objects WITHOUT MFA โ†’ DENIED +- Protects critical data from accidental deletion + +#### Policy 2: S3 Encryption in Transit + +```hcl +# Denies uploads over HTTP (requires HTTPS) +"Condition": { + "Bool": { + "aws:SecureTransport": "false" + } +} +``` + +**Explain:** +- Forces all S3 uploads to use HTTPS +- Prevents man-in-the-middle attacks + +#### Policy 3: Required Tags + +```hcl +# Denies EC2 creation without Environment + Owner tags +"Condition": { + "Null": { + "aws:RequestTag/Owner": "true" + } +} +``` + +**Explain:** +- Cannot launch EC2 without proper tags +- Helps with cost tracking and accountability + +#### Demo User + +```hcl +resource "aws_iam_user" "demo_user" { + name = "terraform-governance-demo-demo-user" +} +``` + +**Explain:** Creates a user and attaches the MFA policy to show how policies work in practice. + +--- + +### ๐Ÿ“„ config.tf - AWS Config (DETECTIVE CONTROLS) + +**What it does:** Creates Config recorder + 6 compliance rules + +#### Config Recorder + +```hcl +resource "aws_config_configuration_recorder" "main" { + recording_group { + all_supported = true # Monitor ALL resource types + } +} +``` + +**Explain:** This records every configuration change in your AWS account. + +#### Config Rule Example: S3 Encryption + +```hcl +resource "aws_config_config_rule" "s3_encryption" { + name = "s3-bucket-server-side-encryption-enabled" + source { + owner = "AWS" + source_identifier = "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED" + } +} +``` + +**Explain:** +- AWS provides pre-built rules (managed rules) +- This checks if ALL S3 buckets have encryption enabled +- Non-compliant buckets are flagged + +#### All 6 Rules We Create + +| Rule | What It Checks | +|------|----------------| +| `s3-bucket-public-write-prohibited` | No public write access | +| `s3-bucket-server-side-encryption-enabled` | Encryption enabled | +| `s3-bucket-public-read-prohibited` | No public read access | +| `encrypted-volumes` | EBS volumes encrypted | +| `required-tags` | Has Environment + Owner tags | +| `root-account-mfa-enabled` | Root has MFA | + +--- + +### ๐Ÿ“„ main.tf - S3 Bucket (SECURE STORAGE) + +**What it does:** Creates a fully secured S3 bucket for Config logs + +```hcl +# Versioning - keeps history +resource "aws_s3_bucket_versioning" { + status = "Enabled" +} + +# Encryption - protects data at rest +resource "aws_s3_bucket_server_side_encryption_configuration" { + sse_algorithm = "AES256" +} + +# Block public access - all 4 settings ON +resource "aws_s3_bucket_public_access_block" { + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} +``` + +**Explain:** This bucket follows ALL the security best practices we're checking with Config rules. It's compliant by design! + +--- + +### ๐Ÿ“„ outputs.tf - Display Results + +**What it does:** Shows important information after deployment + +```hcl +output "config_rules" { + value = [list of all rule names] +} + +output "config_recorder_status" { + value = true # Recorder is running +} +``` + +--- + +## ๐Ÿš€ Part 3: Run the Demo + +### Step 1: Initialize + +```bash +terraform init +``` + +**Explain:** Downloads AWS provider plugins. + +### Step 2: Plan + +```bash +terraform plan +``` + +**Explain:** Shows 23 resources will be created. Walk through key ones: +- IAM policies +- S3 bucket with security settings +- Config recorder +- 6 Config rules + +### Step 3: Apply + +```bash +terraform apply -auto-approve +``` + +**Explain:** Takes 2-3 minutes. Creates everything. + +### Step 4: View Outputs + +```bash +terraform output +``` + +**Show:** +- Policy ARNs +- S3 bucket name +- Config recorder status = true +- List of 6 rules + +--- + +## ๐Ÿ” Part 4: Verify in AWS Console + +### Check IAM Policies + +```bash +aws iam list-policies --scope Local | grep terraform-governance +``` + +**Console:** IAM โ†’ Policies โ†’ Filter "Customer managed" + +**Show:** Click on MFA policy โ†’ View JSON โ†’ Explain the condition + +### Check S3 Bucket + +```bash +aws s3api get-bucket-encryption --bucket $(terraform output -raw config_bucket_name) +``` + +**Console:** S3 โ†’ Find bucket โ†’ Properties tab + +**Show:** +- โœ… Versioning: Enabled +- โœ… Encryption: AES-256 +- โœ… Block public access: All ON + +### Check AWS Config + +```bash +aws configservice describe-configuration-recorder-status +``` + +**Console:** AWS Config โ†’ Dashboard โ†’ Rules + +**Show:** +- Recorder running +- 6 rules listed +- Compliance status (may take 5-10 min) + +--- + +## ๐Ÿงช Part 5: Test Compliance Detection + +### Create a Violation + +```bash +# Create bucket WITHOUT encryption +aws s3 mb s3://test-violation-$(date +%s) +``` + +### Check Config + +**Wait 2-3 minutes, then:** + +1. Go to AWS Config โ†’ Rules +2. Click "s3-bucket-server-side-encryption-enabled" +3. See new bucket as **NON-COMPLIANT** (red) + +**Explain:** Config detected the violation automatically! + +### Cleanup Test + +```bash +aws s3 rb s3://test-violation-* --force +``` + +--- + +## ๐Ÿงน Part 6: Cleanup + +```bash +terraform destroy -auto-approve +``` + +**Explain:** Removes all 23 resources. Important for cost control. + +--- + +## ๐Ÿ’ก Key Takeaways + +1. **IAM Policies = PREVENT** โ†’ Block bad actions before they happen +2. **AWS Config = DETECT** โ†’ Find violations after they happen +3. **Defense in Depth** โ†’ Use both for complete protection +4. **Infrastructure as Code** โ†’ Governance is version controlled +5. **Automated Compliance** โ†’ 24/7 monitoring, no manual audits + +--- + +## ๐Ÿ“Š Quick Reference + +| File | Purpose | Controls | +|------|---------|----------| +| `iam.tf` | Policies | Preventive | +| `config.tf` | Rules | Detective | +| `main.tf` | S3 Bucket | Storage | + +| Commands | | +|----------|---| +| `terraform init` | Setup | +| `terraform plan` | Preview | +| `terraform apply` | Deploy | +| `terraform output` | View results | +| `terraform destroy` | Cleanup | + +--- + +**Demo Time: ~30 minutes** + +**Cost: ~$2/month (destroy after demo)** \ No newline at end of file diff --git a/AWS-Tf-Handson/Day21/Readme.md b/AWS-Tf-Handson/Day21/Readme.md new file mode 100644 index 000000000..f8a5ef3d3 --- /dev/null +++ b/AWS-Tf-Handson/Day21/Readme.md @@ -0,0 +1,99 @@ +## Traffic Flow + 1. Internet โ†’ IGW - External traffic enters through Internet Gateway. + 2. IGW โ†’ Public Subnets - Routed to public subnets. + 3. Public Subnets โ†’ NAT Gateway - Outbound traffic from private subnets. + 4. NAT โ†’ Private Subnets - NAT translates private IPs to public. + 5. Private Subnets โ†’ EKS - Kubernetes nodes communicate internally. + 6. EKS โ†’ Internet - Pods reach internet via NAT Gateway. + + + +This is a classic AWS Secure Network Architecture. It is designed to keep your application (EKS) safe while still allowing it to talk to the outside world when necessary. + +Here is a breakdown of that traffic flow using a simple "Secure Office Building" analogy to make it concrete. + +## The Analogy: A Secure Office Building + VPC: The Building. + Internet: The Street outside. + IGW (Internet Gateway): The Main Front Door. + Public Subnet: The Lobby/Reception. It has direct access to the front door. + Private Subnet: The Secure Work Area in the back. It has no direct door to the street. + EKS (Kubernetes): The Employees working in the secure area. + NAT Gateway: The Mailroom Clerk sitting in the Lobby. + + +## Step-by-Step Flow Explanation +Here is how the traffic moves based on the lines you provided: + +1. ### Internet โ†’ IGW + **Concept**: + External traffic enters through the Internet Gateway. + + **Analogy**: + A delivery person walks from the street to the building's front door. + + **Technical**: + The IGW is the only entry/exit point for traffic between your VPC and the Internet. + +2. ### IGW โ†’ Public Subnets + **Concept**: + Traffic is routed to the public subnets. + + **Analogy**: + The delivery person walks through the door into the Lobby. + + **Technical**: + The Route Table for the public subnet has a rule: 0.0.0.0/0 -> IGW. This allows resources here (like Load Balancers or the NAT Gateway) to send and receive traffic directly. + +3. ### Public Subnets โ†’ NAT Gateway + **Concept**: + The NAT Gateway resides here. + + **Analogy**: + The Mailroom Clerk sits in the Lobby because they need access to the front door to send mail out. + + **Technical**: + A NAT Gateway must be placed in a Public Subnet. If it were in a private subnet, it wouldn't be able to reach the IGW, rendering it useless. + +4. ### NAT โ†’ Private Subnets + **Concept**: + NAT translates private IPs to public IPs for the private subnets. + + **Analogy**: + The Employees in the back (Private Subnet) cannot walk out the front door. When they need to send a letter, they send it to the Mailroom Clerk (NAT). + + **Technical**: + The Private Subnet's Route Table has a rule: 0.0.0.0/0 -> NAT Gateway ID. This tells all traffic destined for the internet to go to the NAT Gateway first. + +5. ### Private Subnets โ†’ EKS + **Concept**: + Kubernetes nodes run here. + + **Analogy**: + This is where the desks are. The Employees (EKS Nodes) work here safely, away from the public street. + + **Technical**: + For security, EKS worker nodes are almost always placed in Private Subnets so they cannot be directly attacked from the internet. + +6. ### EKS โ†’ Internet (The Outbound Flow) + **Concept**: + Pods reach the internet via the NAT Gateway. + + **Analogy**: + An Employee (Pod) needs to order lunch (download a Docker image). + + They hand the order to the Mailroom Clerk (NAT) in the Lobby. + + The Clerk replaces the Employee's desk number with the Building's address (IP Translation). + + The Clerk walks out the Front Door (IGW) to get the lunch. + + **Technical**: + + Pod (IP 10.0.2.50) sends a request to google.com. + + Router sees the destination is external and sends it to NAT Gateway. + + NAT Gateway replaces 10.0.2.50 with its own Public IP (e.g., 54.1.1.1). + + NAT Gateway sends it to IGW -> Internet. \ No newline at end of file diff --git a/AWS-Tf-Handson/Day21/TEST_CASES.md b/AWS-Tf-Handson/Day21/TEST_CASES.md new file mode 100644 index 000000000..cca7533eb --- /dev/null +++ b/AWS-Tf-Handson/Day21/TEST_CASES.md @@ -0,0 +1,71 @@ +# ๐Ÿงช Test Cases: AWS Governance & Compliance (Day 21) + +This document outlines the test scenarios to validate the infrastructure deployed in Day 21. + +## โœ… Automated Verification (Infrastructure State) + +Run the provided `verify_deployment.sh` script to confirm these checks. + +| ID | Test Case | Expected Result | +|----|-----------|-----------------| +| **TC-01** | **S3 Bucket Existence** | Bucket created with correct naming convention. | +| **TC-02** | **S3 Versioning** | Versioning status is `Enabled`. | +| **TC-03** | **S3 Encryption** | Server-side encryption is `Enabled` (AES256/KMS). | +| **TC-04** | **S3 Public Access** | All 4 Public Access Block settings are `true`. | +| **TC-05** | **Config Recorder** | Recorder is created and `recording` status is `true`. | +| **TC-06** | **Config Rules** | All 6 compliance rules exist in AWS Config. | + +--- + +## ๐Ÿ›‘ Manual Compliance Testing (Negative Testing) + +These tests verify that your **Preventive Controls (IAM Policies)** are actually blocking bad actions. + +### TC-07: Verify MFA Delete Policy +**Objective:** Ensure the S3 bucket cannot be deleted without MFA. +1. **Pre-requisite:** You must be logged in as the `demo-user` (or assume the user's context). +2. **Action:** Try to delete the S3 bucket created by Terraform. + ```bash + aws s3 rb s3:// + ``` +3. **Expected Result:** `AccessDenied` error (because MFA is not present in the CLI command). + +### TC-08: Verify Encryption Enforcement +**Objective:** Ensure objects cannot be uploaded via HTTP (unencrypted transport). +1. **Action:** Try to upload a file using standard HTTP (simulated). + *Note: The AWS CLI uses HTTPS by default, so this is hard to trigger without using `curl` or specific API calls, but the policy `aws:SecureTransport` guarantees this.* +2. **Expected Result:** If attempted via non-SSL endpoint, request is Denied. + +### TC-09: Verify Tagging Policy (If attached) +**Objective:** Ensure resources cannot be created without specific tags. +*Note: Ensure the `tagging_policy` is attached to your user before testing.* +1. **Action:** Try to launch an EC2 instance without tags. + ```bash + aws ec2 run-instances --image-id ami-12345678 --instance-type t2.micro + ``` +2. **Expected Result:** `UnauthorizedOperation` or `AccessDenied`. + +--- + +## ๐Ÿ” AWS Config Compliance Testing (Detective Controls) + +These tests verify that **AWS Config** detects non-compliant resources. + +### TC-10: Detect Unencrypted Volumes +1. **Action:** Create an unencrypted EBS volume. + ```bash + aws ec2 create-volume --availability-zone $(aws ec2 describe-availability-zones --query 'AvailabilityZones[0].ZoneName' --output text) --size 1 + ``` +2. **Wait:** Wait ~5-10 minutes for AWS Config to record the change. +3. **Check:** Go to AWS Console > Config > Rules > `encrypted-volumes`. +4. **Expected Result:** The new volume ID appears under **Non-compliant resources**. +5. **Cleanup:** Delete the volume. + ```bash + aws ec2 delete-volume --volume-id + ``` + +### TC-11: Detect Public S3 Buckets +1. **Action:** Create a new S3 bucket and remove "Block Public Access" settings. +2. **Wait:** Wait ~5-10 minutes. +3. **Check:** Go to AWS Console > Config > Rules > `s3-bucket-public-write-prohibited`. +4. **Expected Result:** The bucket appears as **Non-compliant**. \ No newline at end of file diff --git a/AWS-Tf-Handson/Day21/config.tf b/AWS-Tf-Handson/Day21/config.tf new file mode 100644 index 000000000..8083929bb --- /dev/null +++ b/AWS-Tf-Handson/Day21/config.tf @@ -0,0 +1,97 @@ +################################################# +# AWS Config recorder and delivery channel setup +################################################# + +resource "aws_config_configuration_recorder" "main" { + name = "${var.project_name}-config-recorder" + role_arn = aws_iam_role.config_role.arn +} + +resource "aws_config_delivery_channel" "main" { + name = "${var.project_name}-config-delivery-channel" + s3_bucket_name = aws_s3_bucket.config_bucket.bucket + depends_on = [aws_config_configuration_recorder.main, aws_s3_bucket_policy.config_bucket_policy] + +} + +resource "aws_config_configuration_recorder_status" "main" { + name = aws_config_configuration_recorder.main.name + is_enabled = true + depends_on = [aws_config_delivery_channel.main] +} + + +############################################### +# AWS Config Rules - Compliance Rules +############################################### +resource "aws_config_config_rule" "s3_bucket_versioning_enabled" { + name = "s3-bucket-versioning-enabled" + + source { + owner = "AWS" + source_identifier = "S3_BUCKET_VERSIONING_ENABLED" + } + depends_on = [ aws_config_configuration_recorder.main ] +} + +resource "aws_config_config_rule" "aws_s3_bucket_public_access_block" { + name = "s3-bucket-public-write-prohibited" + + source { + owner = "AWS" + source_identifier = "S3_BUCKET_PUBLIC_WRITE_PROHIBITED" + } + depends_on = [ aws_config_configuration_recorder.main ] +} + + +resource "aws_config_config_rule" "s3_bucket_server_side_encryption_configuration" { + name = "s3-bucket-server-side-encryption-enabled" + + source { + owner = "AWS" + source_identifier = "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED" + } + depends_on = [ aws_config_configuration_recorder.main ] +} + + +resource "aws_config_config_rule" "ebs_encrypted_volumes" { + name = "encrypted-volumes" + + source { + owner = "AWS" + source_identifier = "ENCRYPTED_VOLUMES" + } + depends_on = [ aws_config_configuration_recorder.main ] +} + +resource "aws_config_config_rule" "ec2_instance_no_public_ip" { + name = "ec2-instance-no-public-ip" + + source { + owner = "AWS" + source_identifier = "EC2_INSTANCE_NO_PUBLIC_IP" + } + depends_on = [ aws_config_configuration_recorder.main ] +} + +resource "aws_config_config_rule" "iam_password_policy" { + name = "iam-password-policy" + + source { + owner = "AWS" + source_identifier = "IAM_PASSWORD_POLICY" + } + depends_on = [ aws_config_configuration_recorder.main ] +} + +resource "aws_config_config_rule" "aws_iam_mfa_enabled" { + name = "iam-user-mfa-enabled" + + source { + owner = "AWS" + source_identifier = "IAM_USER_MFA_ENABLED" + } + depends_on = [ aws_config_configuration_recorder.main ] +} diff --git a/AWS-Tf-Handson/Day21/iam.tf b/AWS-Tf-Handson/Day21/iam.tf new file mode 100644 index 000000000..5b74d11bb --- /dev/null +++ b/AWS-Tf-Handson/Day21/iam.tf @@ -0,0 +1,167 @@ +# IAM Policy to enforce MFA for S3 Bucket Deletion +resource "aws_iam_policy" "mfa_delete_policy" { + name = "${var.project_name}-MFA-Delete-Policy" + description = "IAM policy to allow deletion of S3 buckets only with MFA" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "AllowDeletionWithMFA" + Effect = "Deny" + Action = "s3:DeleteBucket" + Resource = "*" + Condition = { + Bool = { + "aws:MultiFactorAuthPresent" = "false" + } + } + } + ] + }) +} + +# IAM Policy enforce Encryption in transit for S3 Bucket +resource "aws_iam_policy" "s3_encryption_policy" { + name = "${var.project_name}-S3-Encryption-Policy" + description = "IAM policy to enforce encryption in transit for S3 buckets" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "EnforceEncryptionInTransit" + Effect = "Deny" + Action = "s3:PutObject" + Resource = "*" + Condition = { + Bool = { + "aws:SecureTransport" = "false" + } + } + } + ] + }) +} + +# IAM Policy to require tagging of resources creation +resource "aws_iam_policy" "tagging_policy" { + name = "${var.project_name}-Tagging-Policy" + description = "IAM policy to require tagging of resources creation" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "EnvironmentTagging" + Effect = "Deny" + Action = "s3:*" + Resource = "*" + Condition = { + StringNotEquals = { + "aws:RequestTag/Environment" = ["Production", "Development"] + } + } + } + ] + }) +} + +# IAM Policy to restrict EC2 instance launch without owner tags +resource "aws_iam_policy" "ec2_owner_tagging_policy" { + name = "${var.project_name}-EC2-Owner-Tagging-Policy" + description = "IAM policy to restrict EC2 instance launch without owner tags" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "RequireOwnerTag" + Effect = "Deny" + Action = "ec2:RunInstances" + Resource = "*" + Condition = { + StringNotEquals = { + "aws:RequestTag/Owner" = ["Ankur", "DevOps"] + } + } + } + ] + }) +} + +# IAM user for demonstration purposes +resource "aws_iam_user" "demo_user" { + name = "demo-User" + path = "/governance/" + + tags = { + Project = var.project_name + Owner = "Ankur" + Environment = "Production" + } +} + + +# Attach the MFA policies to the demo user +resource "aws_iam_user_policy_attachment" "mfa_delete_policy_attachment" { + user = aws_iam_user.demo_user.name + policy_arn = aws_iam_policy.mfa_delete_policy.arn +} + +# Attach S3 Full Access to allow the user to list buckets and test the Deny policies +resource "aws_iam_user_policy_attachment" "demo_user_s3_full_access" { + user = aws_iam_user.demo_user.name + policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess" +} + +# IAM role for AWS Config to assume and evaluate compliance +resource "aws_iam_role" "config_role" { + name = "${var.project_name}-AWS-Config-Role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "AllowAWSConfigService" + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "config.amazonaws.com" + } + } + ] + }) +} + + +# Attach AWS managed policy for AWS Config role +resource "aws_iam_role_policy_attachment" "config_policy_attach" { + role = aws_iam_role.config_role.name + #policy_arn = "arn:aws:iam::aws:policy/service-role/AWSConfigRolePolicyForConfigurationRecorder" + policy_arn = "arn:aws:iam::aws:policy/service-role/AWS_ConfigRole" +} + + + +# Additional policy to write to S3 bucket for AWS Config +resource "aws_iam_policy" "config_s3_policy" { + name = "${var.project_name}-Config-S3-Policy" + description = "IAM policy to allow AWS Config to write to S3 bucket" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "AllowS3Write" + Effect = "Allow" + Action = [ + "s3:PutObject", + "s3:GetBucketAcl", + "s3:GetBucketVersioning" + ] + Resource = "${aws_s3_bucket.config_bucket.arn}/*" + } + ] + }) +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day21/main.tf b/AWS-Tf-Handson/Day21/main.tf new file mode 100644 index 000000000..affeaf1d0 --- /dev/null +++ b/AWS-Tf-Handson/Day21/main.tf @@ -0,0 +1,121 @@ +# Main Terraform configuration file + +data "aws_caller_identity" "current" {} + +# S3 Bucket to store Config logs +resource "aws_s3_bucket" "config_bucket" { + bucket = "${var.project_name}-bucket-${random_string.bucket_suffix.result}" # Replace with your desired bucket name + #acl = "private" + + tags = { + Name = "${var.project_name}-Config-Bucket" + Manageby = "Ankur" + Environment = "Production" + purposes = "AWS Config logs storage" +} +} + +# Generate a random suffix for the bucket name +resource "random_string" "bucket_suffix" { + length = 4 + upper = false + special = false +} + +# Enable versioning on the S3 bucket +resource "aws_s3_bucket_versioning" "versioning_example" { + bucket = aws_s3_bucket.config_bucket.id + versioning_configuration { + status = "Enabled" + } +} + +# Enable server-side encryption on the S3 bucket +resource "aws_s3_bucket_server_side_encryption_configuration" "config_bucket" { + bucket = aws_s3_bucket.config_bucket.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "aws:kms" + } + } +} + + +# Block public access to the S3 bucket +resource "aws_s3_bucket_public_access_block" "example" { + bucket = aws_s3_bucket.config_bucket.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true + skip_destroy = true +} + + +# S3 Bucket Policy to allow AWS Config to write to the bucket +resource "aws_s3_bucket_policy" "config_bucket_policy" { + bucket = aws_s3_bucket.config_bucket.id + + policy = jsonencode({ + "Version":"2012-10-17", + "Statement": [ + { + "Sid": "AWSConfigBucketPermissionsCheck", + "Effect": "Allow", + "Principal": { + "Service": "config.amazonaws.com" + }, + "Action": "s3:GetBucketAcl", + "Resource": "${aws_s3_bucket.config_bucket.arn}", + "Condition": { + "StringEquals": { + "AWS:SourceAccount": "${data.aws_caller_identity.current.account_id}" + } + } + }, + { + "Sid": "AWSConfigBucketExistenceCheck", + "Effect": "Allow", + "Principal": { + "Service": "config.amazonaws.com" + }, + "Action": "s3:ListBucket", + "Resource": "${aws_s3_bucket.config_bucket.arn}", + "Condition": { + "StringEquals": { + "AWS:SourceAccount": "${data.aws_caller_identity.current.account_id}" + } + } + }, + { + "Sid": "AWSConfigBucketDelivery", + "Effect": "Allow", + "Principal": { + "Service": "config.amazonaws.com" + }, + "Action": "s3:PutObject", + "Resource": "${aws_s3_bucket.config_bucket.arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/Config/*", + "Condition": { + "StringEquals": { + "s3:x-amz-acl": "bucket-owner-full-control", + "AWS:SourceAccount": "${data.aws_caller_identity.current.account_id}" + } + } + }, + { + "Sid": "DenyInsecureTransport", + "Effect": "Deny", + "Principal": "*", + "Action": "s3:*", + "Resource": "${aws_s3_bucket.config_bucket.arn}/*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + } + } + ] +}) +} diff --git a/AWS-Tf-Handson/Day21/outputs.tf b/AWS-Tf-Handson/Day21/outputs.tf new file mode 100644 index 000000000..6d81f9cae --- /dev/null +++ b/AWS-Tf-Handson/Day21/outputs.tf @@ -0,0 +1,72 @@ +####################### +## IAM Policy Outputs## +####################### + +output "mfa_delete_policy_arn" { + description = "The ARN of the MFA delete policy" + value = aws_iam_policy.mfa_delete_policy.arn +} + +output "s3_encryption_policy" { + description = "The ARN of the S3 encryption policy" + value = aws_iam_policy.config_s3_policy.arn +} + +output "config_role_arn" { + description = "The ARN of the AWS Config IAM role" + value = aws_iam_role.config_role.arn +} + +output "demo_user" { + description = "The name of the demo IAM user" + value = aws_iam_user.demo_user.id +} + +####################### +## S3 Bucket Outputs## +####################### + +output "s3_bucket_name" { + description = "The name of the S3 bucket for AWS Config" + value = aws_s3_bucket.config_bucket.bucket +} + +output "s3_bucket_arn" { + description = "The ARN of the S3 bucket for AWS Config" + value = aws_s3_bucket.config_bucket.arn +} + +output "versioning" { + description = "Whether versioning is enabled or not on S3 bucket" + value = aws_s3_bucket_versioning.versioning_example.versioning_configuration[0].status +} + +####################### +## AWS Config Outputs## +####################### + +output "config_recorder_name" { + description = "The name of the AWS Config configuration recorder" + value = aws_config_configuration_recorder.main.name +} + +output "config_recorder_id" { + description = "The ID of the AWS Config configuration recorder" + value = aws_config_configuration_recorder.main.id +} + +output "delivery_channel_name" { + description = "The name of the AWS Config delivery channel" + value = aws_config_delivery_channel.main.name +} + + +output "delivery_channel_s3_bucket_name" { + description = "The name of the S3 bucket for AWS Config delivery channel" + value = aws_config_delivery_channel.main.s3_bucket_name +} + +output "region" { + description = "The AWS region where resources are deployed" + value = var.region +} diff --git a/AWS-Tf-Handson/Day21/provider.tf b/AWS-Tf-Handson/Day21/provider.tf new file mode 100644 index 000000000..8ea2a90c6 --- /dev/null +++ b/AWS-Tf-Handson/Day21/provider.tf @@ -0,0 +1,4 @@ +provider "aws" { + region = var.region # Replace with your desired AWS region + profile = "468284643560" +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day21/variables.tf b/AWS-Tf-Handson/Day21/variables.tf new file mode 100644 index 000000000..1e9793c84 --- /dev/null +++ b/AWS-Tf-Handson/Day21/variables.tf @@ -0,0 +1,12 @@ +variable "region" { + description = "The AWS region to deploy resources in" + type = string + default = "us-west-1" +} + +variable "project_name" { + description = "The name of the project" + type = string + default = "aws-config-terraform" + +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day21/verify_deployment.sh b/AWS-Tf-Handson/Day21/verify_deployment.sh new file mode 100755 index 000000000..30e8696af --- /dev/null +++ b/AWS-Tf-Handson/Day21/verify_deployment.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +# Color codes for output +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +echo "--------------------------------------------------" +echo "๐Ÿ•ต๏ธโ€โ™‚๏ธ Identity & Account Check" +echo "--------------------------------------------------" + +# 1. Force cleanup of conflicting Environment Variables +# This ensures the script relies ONLY on the profile, not exported keys +unset AWS_ACCESS_KEY_ID +unset AWS_SECRET_ACCESS_KEY +unset AWS_SESSION_TOKEN + +# 2. Set the specific profile and expected account +export AWS_PROFILE="468284643560" +EXPECTED_ACCOUNT="468284643560" + +echo "Using Profile: $AWS_PROFILE" + +# 3. Verify Identity +CURRENT_ACCOUNT=$(aws sts get-caller-identity --query Account --output text) +CURRENT_ARN=$(aws sts get-caller-identity --query Arn --output text) + +if [ "$CURRENT_ACCOUNT" != "$EXPECTED_ACCOUNT" ]; then + echo -e "${RED}โŒ CRITICAL ERROR: Wrong Account Detected${NC}" + echo "--------------------------------------------------" + echo "Expected Account: $EXPECTED_ACCOUNT" + echo "Actual Account: $CURRENT_ACCOUNT" + echo "Actual Identity: $CURRENT_ARN" + echo "--------------------------------------------------" + echo "๐Ÿ‘‰ CAUSE: The profile '$AWS_PROFILE' in your ~/.aws/credentials file contains the WRONG keys." + echo " It is currently logging you in as 'dynamodb-training'." + echo "" + echo "๐Ÿ‘‰ FIX: Open ~/.aws/credentials and update the [$AWS_PROFILE] section with the correct Access Keys." + exit 1 +else + echo -e "${GREEN}โœ… Identity Verified: Account $CURRENT_ACCOUNT${NC}" +fi + +echo "--------------------------------------------------" +echo "๐Ÿš€ Starting Infrastructure Verification" +echo "--------------------------------------------------" + +# 4. Get Terraform Outputs +BUCKET_NAME=$(terraform output -raw s3_bucket_name) +RECORDER_NAME=$(terraform output -raw config_recorder_name) +REGION=$(terraform output -raw region 2>/dev/null || echo "us-west-1") + +echo "Target Region: $REGION" +echo "Target Bucket: $BUCKET_NAME" +echo "Target Recorder: $RECORDER_NAME" + +# 5. Verify S3 Versioning +echo "๐Ÿ” Checking S3 Versioning..." +VERSIONING=$(aws s3api get-bucket-versioning --bucket $BUCKET_NAME --region $REGION --query 'Status' --output text) +if [ "$VERSIONING" == "Enabled" ]; then + echo -e "${GREEN}โœ… Versioning is ENABLED${NC}" +else + echo -e "${RED}โŒ Versioning is NOT Enabled (Status: $VERSIONING)${NC}" +fi + +# 6. Verify S3 Encryption +echo "๐Ÿ” Checking S3 Encryption..." +ENCRYPTION=$(aws s3api get-bucket-encryption --bucket $BUCKET_NAME --region $REGION --query 'ServerSideEncryptionConfiguration.Rules[0].ApplyServerSideEncryptionByDefault.SSEAlgorithm' --output text 2>/dev/null) +if [ "$ENCRYPTION" == "AES256" ] || [ "$ENCRYPTION" == "aws:kms" ]; then + echo -e "${GREEN}โœ… Encryption is ENABLED ($ENCRYPTION)${NC}" +else + echo -e "${RED}โŒ Encryption is NOT Enabled${NC}" +fi + +# 7. Verify AWS Config Recorder +echo "๐Ÿ” Checking AWS Config Recorder..." +RECORDER_STATUS=$(aws configservice describe-configuration-recorder-status --configuration-recorder-names $RECORDER_NAME --region $REGION --query 'ConfigurationRecordersStatus[0].recording' --output text) +if [ "$RECORDER_STATUS" == "True" ]; then + echo -e "${GREEN}โœ… Config Recorder is RECORDING${NC}" +else + echo -e "${RED}โŒ Config Recorder is NOT recording (Status: $RECORDER_STATUS)${NC}" +fi + +echo "--------------------------------------------------" +echo "๐ŸŽ‰ Verification Complete" +echo "--------------------------------------------------" \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/.terraform.lock.hcl b/AWS-Tf-Handson/Day22/.terraform.lock.hcl new file mode 100644 index 000000000..b82fc7370 --- /dev/null +++ b/AWS-Tf-Handson/Day22/.terraform.lock.hcl @@ -0,0 +1,43 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.27.0" + hashes = [ + "h1:yey+4NnYAp2quzSKUaExTsbb+hDvtjl3EpdrbDdnM4Y=", + "zh:177a24b806c72e8484b5cabc93b2b38e3d770ae6f745a998b54d6619fd0e8129", + "zh:4ac4a85c14fb868a3306b542e6a56c10bd6c6d5a67bc0c9b8f6a9060cf5f3be7", + "zh:552652185bc85c8ba1da1d65dea47c454728a5c6839c458b6dcd3ce71c19ccfc", + "zh:60284b8172d09aee91eae0856f09855eaf040ce3a58d6933602ae17c53f8ed04", + "zh:6be38d156756ca61fb8e7c752cc5d769cd709686700ac4b230f40a6e95b5dbc9", + "zh:7a409138fae4ef42e3a637e37cb9efedf96459e28a3c764fc4e855e8db9a7485", + "zh:8070cf5224ed1ed3a3e9a59f7c30ff88bf071c7567165275d477c1738a56c064", + "zh:894439ef340a9a79f69cd759e27ad11c7826adeca27be1b1ca82b3c9702fa300", + "zh:89d035eebf08a97c89374ff06040955ddc09f275ecca609d0c9d58d149bef5cf", + "zh:985b1145d724fc1f38369099e4a5087141885740fd6c0b1dbc492171e73c2e49", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a80b47ae8d1475201c86bd94a5dcb9dd4da5e8b73102a90820b68b66b76d50fd", + "zh:d3395be1556210f82199b9166a6b2e677cee9c4b67e96e63f6c3a98325ad7ab0", + "zh:db0b869d09657f6f1e4110b56093c5fcdf9dbdd97c020db1e577b239c0adcbce", + "zh:ffc72e680370ae7c21f9bd3082c6317730df805c6797427839a6b6b7e9a26a01", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.7.2" + hashes = [ + "h1:hkKSY5xI4R1H4Yrg10HHbtOoxZif2dXa9HFPSbaVg5o=", + "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", + "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", + "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", + "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", + "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", + "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", + "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", + "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", + "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", + "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", + ] +} diff --git a/AWS-Tf-Handson/Day22/DEMO_GUIDE.md b/AWS-Tf-Handson/Day22/DEMO_GUIDE.md new file mode 100644 index 000000000..e69de29bb diff --git a/AWS-Tf-Handson/Day22/Readme.md b/AWS-Tf-Handson/Day22/Readme.md new file mode 100644 index 000000000..e69de29bb diff --git a/AWS-Tf-Handson/Day22/main.tf b/AWS-Tf-Handson/Day22/main.tf new file mode 100644 index 000000000..ae7996914 --- /dev/null +++ b/AWS-Tf-Handson/Day22/main.tf @@ -0,0 +1,76 @@ +################### +## Secret modules +################### + +module "secrets" { + source = "./modules/secrets" + + project_name = var.project_name + environment = var.environment + description = "This is my secret" + db_username = var.db_username +} + + +################### +## VPC modules +################### +module "vpc" { + source = "./modules/vpc" + + project_name = "${var.project_name}-${var.environment}-vpc" + environment = var.environment + aws_region = var.aws_region + vpc_cidr = var.vpc_cidr + private_subnets = var.private_subnets + public_subnets = var.public_subnets +} + + +################### +## Security groups +################### +module "security_groups" { + source = "./modules/security_groups" + + project_name = var.project_name + environment = var.environment + vpc_id = module.vpc.vpc_id +} + +################### +## RDS modules +################### +module "rds" { + source = "./modules/rds" + + project_name = var.project_name + environment = var.environment + private_subnets_id = module.vpc.private_subnets + db_name = var.db_name + db_password = module.secrets.db_password + db_username = var.db_username + db_security_group_id = module.security_groups.db_sg_id + engine_version = var.db_engine_version + instance_class = var.db_instance_class + region = var.aws_region + allocated_storage = var.db_allocated_storage + +} + +################### +## EC2 modules +################### +module "ec2" { + source = "./modules/ec2" + + project_name = var.project_name + #environment = var.environment + instance_type = var.ec2_instance_type + public_subnets = module.vpc.public_subnets + web_security_group_id = module.security_groups.web_sg_id + db_endpoint = module.rds.db_endpoint + db_name = var.db_name + db_username = var.db_username + db_password = module.secrets.db_password +} diff --git a/AWS-Tf-Handson/Day22/modules/ec2/main.tf b/AWS-Tf-Handson/Day22/modules/ec2/main.tf new file mode 100644 index 000000000..8239c1f4a --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/ec2/main.tf @@ -0,0 +1,37 @@ +# EC2 module + +# Get latest Ubuntu AMI +data "aws_ami" "ubuntu" { + most_recent = true + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } + + owners = ["099720109477"] # Canonical +} + + +resource "aws_instance" "example" { + ami = data.aws_ami.ubuntu.id + instance_type = var.instance_type + subnet_id = var.public_subnets + vpc_security_group_ids = [var.web_security_group_id] + associate_public_ip_address = true + + user_data = templatefile("${path.module}/template/user_data.sh", { + db_host = var.db_endpoint + db_username = var.db_username + db_password = var.db_password + db_name = var.db_name + }) + tags = { + Name = "${var.project_name}-ec2" + } +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/modules/ec2/outputs.tf b/AWS-Tf-Handson/Day22/modules/ec2/outputs.tf new file mode 100644 index 000000000..2e4d0c5e1 --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/ec2/outputs.tf @@ -0,0 +1,11 @@ +output "instance_id" { + value = aws_instance.example.id +} + +output "public_ip" { + value = aws_instance.example.public_ip +} + +output "public_dns" { + value = aws_instance.example.public_dns +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/modules/ec2/template/user_data.sh b/AWS-Tf-Handson/Day22/modules/ec2/template/user_data.sh new file mode 100644 index 000000000..023da68f7 --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/ec2/template/user_data.sh @@ -0,0 +1,267 @@ +#!/bin/bash +set -e + +# Update system +apt-get update +apt-get install -y python3-pip python3-venv mysql-client + +# Create app directory +mkdir -p /home/ubuntu/app +cd /home/ubuntu/app + +# Create virtual environment +python3 -m venv venv +source venv/bin/activate + +# Install dependencies +pip install flask mysql-connector-python + +# Create Flask application +cat > app.py << 'APPEOF' +from flask import Flask, request, redirect, url_for +import mysql.connector +import time +import os + +app = Flask(__name__) + +# Database configuration +DB_CONFIG = { + "host": "${db_host}", + "user": "${db_username}", + "password": "${db_password}", + "database": "${db_name}" +} + +def get_db_connection(): + """Establish database connection with retry logic""" + retries = 5 + while retries > 0: + try: + connection = mysql.connector.connect(**DB_CONFIG) + return connection + except Exception as e: + retries -= 1 + if retries == 0: + raise e + time.sleep(3) + return None + +def init_db(): + """Initialize database table""" + try: + conn = get_db_connection() + if conn: + cursor = conn.cursor() + cursor.execute(""" + CREATE TABLE IF NOT EXISTS messages ( + id INT AUTO_INCREMENT PRIMARY KEY, + content VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + conn.commit() + cursor.close() + conn.close() + except Exception as e: + print(f"DB Init Error: {e}") + +# Initialize DB on startup +init_db() + +@app.route("/", methods=["GET", "POST"]) +def home(): + conn = get_db_connection() + messages = [] + error = None + + if request.method == "POST": + content = request.form.get("content") + if content and conn: + try: + cursor = conn.cursor() + cursor.execute("INSERT INTO messages (content) VALUES (%s)", (content,)) + conn.commit() + cursor.close() + return redirect(url_for("home")) + except Exception as e: + error = str(e) + + if conn: + try: + cursor = conn.cursor() + cursor.execute("SELECT content, created_at FROM messages ORDER BY created_at DESC LIMIT 10") + messages = cursor.fetchall() + cursor.close() + conn.close() + except Exception as e: + error = str(e) + else: + error = "Could not connect to database" + + messages_html = "".join([f"

{m[0]}

{m[1]}
" for m in messages]) + + return f""" + + + Day 22 - RDS Demo App + + + +
+
+

๐Ÿš€ Terraform RDS Demo

+

+ Status: + + {'โ— Connected to RDS' if not error else 'โ— Disconnected'} + +

+

This application is running on EC2 and storing data in an RDS MySQL database.

+ + {f'
{error}
' if error else ''} + +
+ +
+ +
+
+
+ +
+

Recent Messages

+ {messages_html if messages else '

No messages yet. Be the first to post!

'} +
+ +
+

Database Host: {DB_CONFIG['host']}

+
+
+ + + """ + +@app.route("/health") +def health(): + + try: + connection = get_db_connection() + if connection and connection.is_connected(): + connection.close() + return """ + + Health Check + + + +
+

โœ… Database Connected Successfully!

+

The Flask application is successfully connected to the RDS MySQL database.

+ โ† Back to Home +
+ + + """ + except Exception as e: + return f""" + + Health Check + + + +
+

โŒ Database Connection Failed

+

Error: {str(e)}

+ โ† Back to Home +
+ + + """, 500 + +@app.route("/db-info") +def db_info(): + try: + connection = get_db_connection() + if connection and connection.is_connected(): + cursor = connection.cursor() + cursor.execute("SELECT VERSION()") + version = cursor.fetchone()[0] + cursor.execute("SELECT DATABASE()") + db_name = cursor.fetchone()[0] + cursor.close() + connection.close() + return f""" + + Database Info + + + +
+

๐Ÿ“Š Database Information

+
+

MySQL Version: {version}

+

Database Name: {db_name}

+

Host: ${db_host}

+
+ โ† Back to Home +
+ + + """ + except Exception as e: + return f"Error: {str(e)}", 500 + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=80) +APPEOF + +# Create systemd service for the Flask app +cat > /etc/systemd/system/flask-app.service << 'SERVICEEOF' +[Unit] +Description=Flask Web Application +After=network.target + +[Service] +User=root +WorkingDirectory=/home/ubuntu/app +ExecStart=/home/ubuntu/app/venv/bin/python /home/ubuntu/app/app.py +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +SERVICEEOF + +# Enable and start the service +systemctl daemon-reload +systemctl enable flask-app +systemctl start flask-app \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/modules/ec2/variables.tf b/AWS-Tf-Handson/Day22/modules/ec2/variables.tf new file mode 100644 index 000000000..350cb6293 --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/ec2/variables.tf @@ -0,0 +1,31 @@ +variable "instance_type" { + type = string +} + +variable "public_subnets" { + type = string +} + +variable "web_security_group_id" { + type = string +} + +variable "db_endpoint" { + type = string +} + +variable "db_username" { + type = string +} + +variable "db_password" { + type = string +} + +variable "db_name" { + type = string +} + +variable "project_name" { + type = string +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/modules/rds/main.tf b/AWS-Tf-Handson/Day22/modules/rds/main.tf new file mode 100644 index 000000000..e5fcbc05e --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/rds/main.tf @@ -0,0 +1,34 @@ +# RDS Module + +resource "aws_db_subnet_group" "default" { + name = "${var.project_name}-db-subnet-group" + subnet_ids = var.private_subnets_id + + tags = { + Name = "${var.project_name}-db-subnet-group" + Environment = "Demo" + } +} + +resource "aws_db_instance" "default" { + identifier = "${var.project_name}-db-instance" + allocated_storage = var.allocated_storage + storage_type = "gp2" + engine = "mysql" + engine_version = var.engine_version + instance_class = var.instance_class + db_name = var.db_name + username = var.db_username + password = var.db_password + parameter_group_name = "default.mysql8.0" + db_subnet_group_name = aws_db_subnet_group.default.name + vpc_security_group_ids = [var.db_security_group_id] + skip_final_snapshot = true + publicly_accessible = true + + + tags = { + Name = "${var.project_name}-db-instance" + Environment = "Demo" + } +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/modules/rds/outputs.tf b/AWS-Tf-Handson/Day22/modules/rds/outputs.tf new file mode 100644 index 000000000..7bec8ee6e --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/rds/outputs.tf @@ -0,0 +1,16 @@ +output "db_endpoint" { + value = aws_db_instance.default.address + } + +output "db_port" { + value = aws_db_instance.default.port +} + +output "db_name" { + value = aws_db_instance.default.db_name + } + + +output "db_instance_id" { + value = aws_db_instance.default.id +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/modules/rds/variables.tf b/AWS-Tf-Handson/Day22/modules/rds/variables.tf new file mode 100644 index 000000000..3d9d96d2e --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/rds/variables.tf @@ -0,0 +1,44 @@ +variable "private_subnets_id" { + type = list(string) +} + +variable "project_name" { + type = string +} + +variable "allocated_storage" { + type = number +} + +variable "engine_version" { + type = string +} + +variable "instance_class" { + type = string +} + +variable "db_name" { + type = string +} + +variable "db_username" { + type = string +} + +variable "db_password" { + type = string +} + +variable "db_security_group_id" { + type = string +} + +variable "environment" { + type = string +} + +variable "region" { + type = string + +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/modules/secrets/main.tf b/AWS-Tf-Handson/Day22/modules/secrets/main.tf new file mode 100644 index 000000000..336aa132a --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/secrets/main.tf @@ -0,0 +1,30 @@ +resource "random_password" "password" { + length = 16 + special = true + override_special = "!#$%&*()-_=+[]{}<>:?" +} + +resource "random_id" "name" { + byte_length = 4 +} + +resource "aws_secretsmanager_secret" "this" { + name = "${var.project_name}-${var.environment}-db-password-${random_id.name.id}" + description = var.description + + tags = { + Project = var.project_name + Environment = var.environment + } +} + +resource "aws_secretsmanager_secret_version" "dblogininfo" { + secret_id = aws_secretsmanager_secret.this.id + secret_string = jsonencode({ + username = var.db_username + password = random_password.password.result + engine = "mysql" + host = "" # Will be automatically filled after RDS creation + port = 3306 + }) +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/modules/secrets/output.tf b/AWS-Tf-Handson/Day22/modules/secrets/output.tf new file mode 100644 index 000000000..f2b170857 --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/secrets/output.tf @@ -0,0 +1,7 @@ +#output "db_password" { +# value = random_password.password.result +#} + +output "secret_arn" { + value = aws_secretsmanager_secret.this.arn +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/modules/secrets/outputs.tf b/AWS-Tf-Handson/Day22/modules/secrets/outputs.tf new file mode 100644 index 000000000..710b70d34 --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/secrets/outputs.tf @@ -0,0 +1,4 @@ +output "db_password" { + value = random_password.password.result + sensitive = true +} diff --git a/AWS-Tf-Handson/Day22/modules/secrets/variables.tf b/AWS-Tf-Handson/Day22/modules/secrets/variables.tf new file mode 100644 index 000000000..09a11b3ea --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/secrets/variables.tf @@ -0,0 +1,15 @@ +variable "project_name" { + type = string +} + +variable "environment" { + type = string +} + +variable "description" { + type = string +} + +variable "db_username" { + type = string +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/modules/security_groups/main.tf b/AWS-Tf-Handson/Day22/modules/security_groups/main.tf new file mode 100644 index 000000000..c22346721 --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/security_groups/main.tf @@ -0,0 +1,64 @@ +# Security Groups Module + +resource "aws_security_group" "web" { + name = "${var.project_name}-web-sg" + description = "Security group for web server to allow HTTP and SSH traffic" + vpc_id = var.vpc_id + + ingress { + description = "HTTP access to web server" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "SSH access to web server" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + egress { + description = "Allow all outbound traffic" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.project_name}-web-sg" + } +} + +# Database Security Groups Module + +resource "aws_security_group" "db" { + name = "${var.project_name}-db-sg" + description = "Security group for database to allow traffic from web server" + vpc_id = var.vpc_id + + ingress { + description = "Allow MySQL traffic from web server" + from_port = 3306 + to_port = 3306 + protocol = "tcp" + security_groups = [aws_security_group.web.id] + } + + egress { + description = "Allow all outbound traffic" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.project_name}-db-sg" + environment = var.environment + } +} + diff --git a/AWS-Tf-Handson/Day22/modules/security_groups/outputs.tf b/AWS-Tf-Handson/Day22/modules/security_groups/outputs.tf new file mode 100644 index 000000000..2bf5c44cc --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/security_groups/outputs.tf @@ -0,0 +1,7 @@ +output "db_sg_id" { + value = aws_security_group.db.id +} + +output "web_sg_id" { + value = aws_security_group.web.id +} diff --git a/AWS-Tf-Handson/Day22/modules/security_groups/variables.tf b/AWS-Tf-Handson/Day22/modules/security_groups/variables.tf new file mode 100644 index 000000000..3b55b79de --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/security_groups/variables.tf @@ -0,0 +1,14 @@ +variable "project_name" { + description = "The name of the project" + type = string +} + +variable "environment" { + description = "Environment" + type = string +} + +variable "vpc_id" { + type = string +} + diff --git a/AWS-Tf-Handson/Day22/modules/vpc/main.tf b/AWS-Tf-Handson/Day22/modules/vpc/main.tf new file mode 100644 index 000000000..281856b52 --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/vpc/main.tf @@ -0,0 +1,88 @@ +#------------------------------ +# VPC Module +#------------------------------ +resource "aws_vpc" "name" { + cidr_block = var.vpc_cidr + enable_dns_support = true + enable_dns_hostnames = true + + tags = { + Name = "${var.project_name}-vpc" + Environment = var.environment + } +} + +#------------------------------ +# Public Subnet Module +#------------------------------ + +resource "aws_subnet" "public_subnet" { + vpc_id = aws_vpc.name.id + cidr_block = var.public_subnets + map_public_ip_on_launch = true + availability_zone = "${var.aws_region}a" + + tags = { + Name = "${var.project_name}-subnet" + } +} + +#------------------------------ +# Private Subnet Module for RDS +#------------------------------ +resource "aws_subnet" "private_1" { + vpc_id = aws_vpc.name.id + cidr_block = var.private_subnets[0] + availability_zone = "${var.aws_region}a" + + tags = { + Name = "${var.project_name}-private-subnet-1" + } +} + +resource "aws_subnet" "private_2" { + vpc_id = aws_vpc.name.id + cidr_block = var.private_subnets[1] + availability_zone = "${var.aws_region}c" + + tags = { + Name = "${var.project_name}-private-subnet-2" + } +} + +#------------------------------ +# Internet Gateway Module +#------------------------------ +resource "aws_internet_gateway" "igw" { + vpc_id = aws_vpc.name.id + + tags = { + Name = "${var.project_name}-igw" + Environment = var.environment + } +} + +#------------------------------ +# Route Table Module +#------------------------------ +resource "aws_route_table" "public_rt" { + vpc_id = aws_vpc.name.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.igw.id + } + + tags = { + Name = "${var.project_name}-public-rt" + Environment = var.environment + } +} + +#------------------------------ +# Route Table Association Module +#------------------------------ +resource "aws_route_table_association" "public_rta" { + subnet_id = aws_subnet.public_subnet.id + route_table_id = aws_route_table.public_rt.id +} diff --git a/AWS-Tf-Handson/Day22/modules/vpc/output.tf b/AWS-Tf-Handson/Day22/modules/vpc/output.tf new file mode 100644 index 000000000..ce8d35d74 --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/vpc/output.tf @@ -0,0 +1,23 @@ +output "vpc_id" { + value = aws_vpc.name.id +} + +output "public_subnets" { + value = aws_subnet.public_subnet.id +} + +output "private_subnets" { + value = [aws_subnet.private_1.id, aws_subnet.private_2.id] +} + +output "aws_internet_gateway_id" { + value = aws_internet_gateway.igw.id +} + +output "aws_route_table_id" { + value = aws_route_table.public_rt.id +} + +output "aws_route_table_association" { + value = aws_route_table_association.public_rta +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/modules/vpc/variables.tf b/AWS-Tf-Handson/Day22/modules/vpc/variables.tf new file mode 100644 index 000000000..a0612e5fe --- /dev/null +++ b/AWS-Tf-Handson/Day22/modules/vpc/variables.tf @@ -0,0 +1,32 @@ +variable "project_name" { + description = "Name of the project" + type = string +} + +variable "environment" { + description = "Environment name" + type = string +} + +variable "vpc_cidr" { + description = "CIDR range for the VPC" + type = string + default = "10.0.0.0/16" +} + +variable "public_subnets" { + description = "Public subnet" + type = string + default = "10.0.1.0/24" +} + +variable "private_subnets" { + description = "Private subnet" + type = list(string) + default = ["10.0.2.0/24", "10.0.3.0/24"] +} + +variable "aws_region" { + description = "AWS region for the subnet" + type = string +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/outputs.tf b/AWS-Tf-Handson/Day22/outputs.tf new file mode 100644 index 000000000..110e734e0 --- /dev/null +++ b/AWS-Tf-Handson/Day22/outputs.tf @@ -0,0 +1,28 @@ +output "ec2_instance_id" { + value = module.ec2.instance_id +} + +output "ec2_instance_ip" { + value = module.ec2.public_ip +} + +output "random_endpoint" { + value = module.rds.db_endpoint +} + +output "vpc_id" { + value = module.vpc.vpc_id +} + +output "application_url" { + value = "http://${module.ec2.public_ip}" +} + +output "database_name" { + value = module.rds.db_name +} + + +output "database_port" { + value = module.rds.db_port +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/provider.tf b/AWS-Tf-Handson/Day22/provider.tf new file mode 100644 index 000000000..cba10eb46 --- /dev/null +++ b/AWS-Tf-Handson/Day22/provider.tf @@ -0,0 +1,15 @@ +terraform { + required_version = ">= 1.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + + +provider "aws" { + region = var.aws_region # Replace with your desired AWS region + profile = "468284643560" +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day22/variables.tf b/AWS-Tf-Handson/Day22/variables.tf new file mode 100644 index 000000000..747bd44e3 --- /dev/null +++ b/AWS-Tf-Handson/Day22/variables.tf @@ -0,0 +1,75 @@ +variable "aws_region" { + description = "The AWS region to deploy resources in" + type = string + default = "us-west-1" +} + +variable "project_name" { + description = "The name of the project" + type = string + default = "aws-rds-handson" + +} + +variable "environment" { + description = "value" + default = "dev" +} + +# VPC related variables +variable "vpc_cidr" { + description = "The CIDR block for the VPC" + type = string + default = "10.0.0.0/16" +} + +variable "public_subnets" { + description = "List of public subnet CIDR blocks" + type = string + default = "10.0.1.0/24" +} + +variable "private_subnets" { + description = "List of private subnet CIDR blocks" + type = list(string) + default = ["10.0.2.0/24", "10.0.3.0/24"] +} + +# RDS related variables +variable "db_username" { + description = "The username for the database" + type = string + default = "admin" + sensitive = true +} + +variable "db_name" { + description = "The name of the database" + type = string + default = "webappdb" +} + +variable "db_allocated_storage" { + description = "The allocated storage for the database in GB" + type = number + default = 10 +} + +variable "db_engine_version" { + description = "The database engine version to use" + type = string + default = "8.0" +} + +variable "db_instance_class" { + description = "The Db instance class" + type = string + default = "db.t3.micro" +} + +# EC2 related variables +variable "ec2_instance_type" { + description = "The instance type for the EC2 instance" + type = string + default = "t3.micro" +} \ No newline at end of file diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/.gitignore b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/.gitignore new file mode 100644 index 000000000..e21f80bdb --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/.gitignore @@ -0,0 +1,8 @@ +# Terraform +.terraform/ +*.tfstate +*.tfstate.backup +*.tfplan + +# Lambda artifacts +*.zip \ No newline at end of file diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/DEMO_GUIDE.md b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/DEMO_GUIDE.md new file mode 100644 index 000000000..b496c8ec6 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/DEMO_GUIDE.md @@ -0,0 +1,1513 @@ +# ๐ŸŽฅ AWS Lambda Monitoring Demo Guide +## Video Presentation & Testing Guide + +This guide helps you showcase the **AWS Lambda Image Processor with Comprehensive Monitoring** in your video tutorial. Follow these steps to demonstrate each monitoring feature effectively. + +--- + +## ๐Ÿ“‹ Table of Contents +1. [Pre-Demo Setup](#pre-demo-setup) +2. [Part 1: Project Overview](#part-1-project-overview-2-3-minutes) +3. [Part 2: Modular Architecture](#part-2-modular-architecture-3-4-minutes) +4. [Part 3: Infrastructure Deployment](#part-3-infrastructure-deployment-5-7-minutes) +5. [Part 4: SNS Topics & Email Confirmation](#part-4-sns-topics--email-confirmation-2-minutes) +6. [Part 5: CloudWatch Dashboard](#part-5-cloudwatch-dashboard-4-5-minutes) +7. [Part 6: Testing Lambda Function](#part-6-testing-lambda-function-3-4-minutes) +8. [Part 7: CloudWatch Metrics](#part-7-cloudwatch-metrics-4-5-minutes) +9. [Part 8: CloudWatch Alarms](#part-8-cloudwatch-alarms-5-6-minutes) +10. [Part 9: Log Alerts & Metric Filters](#part-9-log-alerts--metric-filters-4-5-minutes) +11. [Part 10: Triggering Alarms](#part-10-triggering-alarms-demo-6-8-minutes) +12. [Part 11: Cleanup](#part-11-cleanup-2-minutes) + +--- + +## Pre-Demo Setup + +### Before Starting the Video: + +1. **Prepare Sample Images** + ```bash + # Create a test directory with various images + mkdir -p ~/demo-images + # Download or copy test images (JPEG, PNG, different sizes) + # Have at least: + # - 1 normal image (< 1MB) + # - 1 large image (> 5MB) to trigger warnings + # - 1 corrupted/invalid image to test error handling + ``` + +2. **Set Up Email** + - Use a real email address you can access during the demo + - Open your email client in a separate tab/window + +3. **Configure AWS CLI** + ```bash + aws configure + # Ensure AWS credentials are set up + aws sts get-caller-identity # Verify authentication + ``` + +4. **Prepare Code Editor** + - Open VS Code with the project directory + - Have terminal split-screen ready + +5. **Clean AWS Account** (Optional) + ```bash + # Remove any old test resources + cd terraform + terraform destroy -auto-approve # If old deployment exists + ``` + +--- + +## Part 1: Project Overview (2-3 minutes) + +### ๐ŸŽฌ What to Show: + +1. **Project Introduction** + - Explain: "Today we're building an AWS Lambda image processor with comprehensive CloudWatch monitoring" + - Show the project directory structure + +2. **Show Directory Structure** + ```bash + tree terraform/modules -L 2 + ``` + + **Screen Capture**: Show the modular structure + ``` + terraform/modules/ + โ”œโ”€โ”€ cloudwatch_alarms/ + โ”œโ”€โ”€ cloudwatch_metrics/ + โ”œโ”€โ”€ lambda_function/ + โ”œโ”€โ”€ log_alerts/ + โ”œโ”€โ”€ s3_buckets/ + โ””โ”€โ”€ sns_notifications/ + ``` + +3. **Quick Architecture Explanation** + - Draw or show diagram (can be hand-drawn on paper/whiteboard) + - Components: + - S3 Upload Bucket โ†’ Lambda โ†’ S3 Processed Bucket + - CloudWatch Logs, Metrics, Alarms + - SNS Topics for notifications + +### ๐Ÿ“ Script Template: +> "In this project, we have 6 Terraform modules covering different aspects of AWS monitoring. When an image is uploaded to S3, Lambda automatically processes it, and we monitor everything through CloudWatch metrics, alarms, and log-based alerts." + +--- + +## Part 2: Modular Architecture (3-4 minutes) + +### ๐ŸŽฌ What to Show: + +1. **Open Main Configuration** + ```bash + code terraform/main.tf + ``` + + **Highlight**: Show how modules are called + - Point out each module block + - Mention how outputs from one module feed into another + +2. **Show a Sample Module** + ```bash + code terraform/modules/cloudwatch_alarms/main.tf + ``` + + **Explain**: + - Variables coming in + - Resources being created + - Outputs going out + +3. **Show Variables File** + ```bash + code terraform/variables.tf + ``` + + **Point Out**: + - Alarm thresholds (configurable) + - Email settings + - Toggle switches (enable_dashboard) + +### ๐Ÿ“ Script Template: +> "Each module is self-contained. For example, the CloudWatch Alarms module creates 6 different alarms - for errors, duration, throttles, memory, and more. All thresholds are configurable through variables." + +--- + +## Part 3: Infrastructure Deployment (5-7 minutes) + +### ๐ŸŽฌ What to Show: + +1. **Configure Variables** + ```bash + cd terraform + cp terraform.tfvars.example terraform.tfvars + nano terraform.tfvars # or code terraform.tfvars + ``` + + **Edit on Screen**: + ```hcl + alert_email = "your-actual-email@example.com" # Change this! + error_threshold = 2 # Lower for demo purposes + ``` + +2. **Build Pillow Layer** + ```bash + cd ../scripts + chmod +x build_layer_docker.sh + ./build_layer_docker.sh + ``` + + **Explain**: "We're using Docker to build the Pillow library layer for image processing" + + โฑ๏ธ **Time Saver**: Have this pre-built to save time, just show the command + +3. **Initialize Terraform** + ```bash + cd ../terraform + terraform init + ``` + + **Screen Capture**: Show providers being downloaded + +4. **Plan Infrastructure** + ```bash + terraform plan + ``` + + **Highlight**: + - Number of resources to be created (should be 40+) + - Point out key resources: Lambda, S3 buckets, SNS topics, alarms + +5. **Apply Configuration** + ```bash + terraform apply + ``` + + **During Apply**: + - Explain what's being created + - Show resource creation in real-time + - โฑ๏ธ **Time Consideration**: This takes 2-3 minutes - perfect time for voice-over about modules + +6. **Capture Outputs** + ```bash + terraform output + ``` + + **Screen Capture**: Show all outputs + - Bucket names + - Lambda function name + - SNS topic ARNs + - Dashboard URL + - Alarm names + +### ๐Ÿ“ Script Template: +> "Let's deploy this infrastructure. Terraform is creating over 40 resources including Lambda function, S3 buckets, 6 CloudWatch alarms, 6 log-based alarms, metric filters, and a comprehensive dashboard." + +--- + +## Part 4: SNS Topics & Email Confirmation (2 minutes) + +### ๐ŸŽฌ What to Show: + +1. **Check SNS Topics in Console** + - Navigate to: **AWS Console โ†’ SNS โ†’ Topics** + - Show 3 topics created: + - `image-processor-dev-critical-alerts` + - `image-processor-dev-performance-alerts` + - `image-processor-dev-log-alerts` + +2. **Check Email for Confirmations** + - Switch to email client + - Show 3 confirmation emails from AWS + - **Click "Confirm subscription" on each one** (important!) + + **Screen Capture**: Show the confirmation emails + +3. **Verify Subscriptions** + - Go back to SNS Console + - Click on each topic + - Show "Subscriptions" tab + - Status should be "Confirmed" + +### ๐Ÿ“ Script Template: +> "AWS has sent three confirmation emails - one for each alert type. This is crucial - without confirming these, you won't receive alarm notifications. Let's confirm all three." + +### โš ๏ธ Common Issue: +- Email not received? Check spam folder +- Show how to resend confirmation from SNS console + +--- + +## Part 5: CloudWatch Dashboard (4-5 minutes) + +### ๐ŸŽฌ What to Show: + +1. **Navigate to Dashboard** + - Two methods: + + **Method 1 - From Terraform Output**: + ```bash + terraform output cloudwatch_dashboard_url + # Copy and paste URL in browser + ``` + + **Method 2 - AWS Console**: + - AWS Console โ†’ CloudWatch โ†’ Dashboards + - Find: `image-processor-dev-processor-monitoring` + +2. **Dashboard Tour** + + **Widget 1: Invocations & Errors** + - Point out: "This shows total Lambda invocations and errors" + - Currently empty (no data yet) + + **Widget 2: Duration** + - Explain: "Execution time metrics - average, max, and P99" + + **Widget 3: Concurrent Executions** + - Show: "How many Lambda instances running simultaneously" + + **Widget 4: Custom Metrics - Errors vs Success** + - Highlight: "These come from our log metric filters" + + **Widget 5: Processing Time** + - Explain: "Custom metric we emit from Lambda code" + + **Widget 6: Image Size** + - Point out: "Tracks the size of images being processed" + + **Widget 7: Recent Errors (Log Insights)** + - Show: "Live query of ERROR-level logs" + +3. **Explain Dashboard Value** + - "Everything in one place" + - "No need to navigate multiple screens" + - "Custom metrics alongside AWS metrics" + +### ๐Ÿ“ Script Template: +> "This dashboard gives us a comprehensive view of our Lambda function. We have AWS-native metrics like invocations and duration, plus custom metrics from our application code and log filters." + +--- + +## Part 6: Testing Lambda Function (3-4 minutes) + +### ๐ŸŽฌ What to Show: + +1. **Get Bucket Name** + ```bash + terraform output upload_bucket_name + # Copy the bucket name + ``` + +2. **Upload Test Image** + ```bash + aws s3 cp ~/demo-images/test-image.jpg s3://YOUR-UPLOAD-BUCKET-NAME/ + ``` + + **Screen Capture**: Show successful upload + +3. **Check Lambda Execution** + + **Method 1 - CloudWatch Logs (Preferred)**: + ```bash + aws logs tail /aws/lambda/image-processor-dev-processor --follow + ``` + + **Screen Capture**: Show structured logs appearing + - REQUEST_ID + - Processing time + - Image size + - "Successfully processed" message + +4. **Verify Processed Images** + ```bash + aws s3 ls s3://YOUR-PROCESSED-BUCKET-NAME/ + ``` + + **Point Out**: Multiple variants created: + - compressed (JPEG 85%) + - low quality (JPEG 60%) + - webp format + - PNG format + - thumbnail + +5. **Download and Show Results** + ```bash + aws s3 cp s3://YOUR-PROCESSED-BUCKET-NAME/test-image_thumbnail_xxx.jpg ./ + # Open the downloaded thumbnail + ``` + +### ๐Ÿ“ Script Template: +> "Let's test the function. I'm uploading an image to S3. Lambda is triggered automatically, processes the image, creates 5 variants, and uploads them to the processed bucket. Look at these detailed logs with processing times!" + +--- + +## Part 7: CloudWatch Metrics (4-5 minutes) + +### ๐ŸŽฌ What to Show: + +1. **Navigate to Metrics** + - AWS Console โ†’ CloudWatch โ†’ Metrics โ†’ All metrics + +2. **AWS Lambda Metrics** + - Click "AWS/Lambda" + - Select "By Function Name" + - Choose your function: `image-processor-dev-processor` + - **Show metrics**: + - โœ… Invocations (should show 1) + - โœ… Duration (show average execution time) + - โœ… Errors (should be 0) + - โœ… Throttles (should be 0) + +3. **Custom Metrics** + - Go back to "All metrics" + - Click "ImageProcessor/Lambda" (your custom namespace) + - **Show metrics**: + - โœ… LambdaErrors (from log filter) + - โœ… SuccessfulProcesses (from log filter) + - โœ… ImageProcessingTime (from log filter) + - โœ… ImageSizeBytes (from log filter) + - โœ… ProcessingSuccess (from Lambda code) + +4. **Graphed View** + - Select 2-3 metrics + - Click "Graphed metrics" tab + - Change time range to "Last hour" + - **Show the graph** + - Explain: "These metrics are auto-populated from our logs and Lambda code" + +5. **Metric Filters** + - Navigate to: CloudWatch โ†’ Log groups + - Click on `/aws/lambda/image-processor-dev-processor` + - Go to "Metric filters" tab + - **Show filters**: + - error-count + - processing-time + - success-count + - image-size + - Click on one โ†’ "Show in Metrics" โ†’ Demonstrate connection + +### ๐Ÿ“ Script Template: +> "CloudWatch is automatically collecting these metrics. AWS Lambda metrics are built-in, but we also created custom metrics from our logs using metric filters. This gives us business-level insights beyond just infrastructure metrics." + +--- + +## Part 8: CloudWatch Alarms (5-6 minutes) + +### ๐ŸŽฌ What to Show: + +1. **Navigate to Alarms** + - AWS Console โ†’ CloudWatch โ†’ Alarms โ†’ All alarms + - **Show all 12 alarms created**: + - 6 standard alarms + - 6 log-based alarms + +2. **Explain Alarm Types** + + **Critical Alarms** (Red severity): + - `image-processor-dev-processor-high-error-rate` + - `image-processor-dev-processor-throttles` + - `image-processor-dev-processor-log-errors` + - `image-processor-dev-processor-timeout-errors` + - `image-processor-dev-processor-memory-errors` + - `image-processor-dev-processor-s3-permission-errors` + - `image-processor-dev-processor-critical-errors` + + **Performance Alarms** (Yellow severity): + - `image-processor-dev-processor-high-duration` + - `image-processor-dev-processor-high-concurrency` + - `image-processor-dev-processor-low-success-rate` + + **Informational Alarms**: + - `image-processor-dev-processor-image-processing-errors` + - `image-processor-dev-processor-large-images` + +3. **Inspect an Alarm** + - Click on: `image-processor-dev-processor-high-error-rate` + - **Show alarm details**: + - Threshold: 3 errors + - Evaluation period: 1 minute + - State: "OK" (green) + - Actions: SNS topic ARN + - Click "History" tab โ†’ Show state changes + - Click "Actions" tab โ†’ Show SNS notification configuration + +4. **Alarm States** + - Explain the 3 states: + - โœ… **OK** (Green): Everything normal + - โš ๏ธ **ALARM** (Red): Threshold breached + - โ“ **INSUFFICIENT_DATA** (Gray): Not enough data yet + +5. **Show Alarm Connections** + - From alarm details โ†’ Click "View in metrics" + - Shows the underlying metric + - Demonstrate the alarm threshold line on the graph + +### ๐Ÿ“ Script Template: +> "We've created 12 alarms covering every possible issue - from simple errors to complex patterns in logs. Each alarm is connected to the appropriate SNS topic so you get the right notification for the right issue." + +--- + +## Part 9: Log Alerts & Metric Filters (4-5 minutes) + +### ๐ŸŽฌ What to Show: + +1. **Navigate to Log Group** + - CloudWatch โ†’ Log groups + - Click `/aws/lambda/image-processor-dev-processor` + +2. **Show Log Streams** + - Click "Log streams" + - Show recent stream from your test + - Expand log entries + - **Highlight**: + - Structured logging with REQUEST_ID + - Processing time metrics + - Image size logging + - Success messages + +3. **Demonstrate Metric Filters** + - Go to "Metric filters" tab + - Click on `image-processor-dev-processor-error-count` + - **Show**: + - Filter pattern: `[timestamp, request_id, level = ERROR*, ...]` + - Test pattern โ†’ paste sample ERROR log + - Show matches + - Explain: "This filter catches all ERROR level logs and creates a metric" + +4. **Create a Test Error Log** + - Go back to Log streams + - Use CloudWatch Logs Insights: + ``` + fields @timestamp, @message + | filter @message like /ERROR/ + | sort @timestamp desc + | limit 20 + ``` + - Run query + - Show: Currently no errors (good!) + +5. **Show Log-Based Alarms** + - Go back to Alarms + - Filter by "log" in search + - Click on `image-processor-dev-processor-timeout-errors` + - **Explain**: + - Watches for specific pattern: "Task timed out" + - Creates metric when pattern matches + - Alarm fires when metric > 0 + +6. **Other Log Patterns** + - Show alarm for "Memory errors" + - Pattern: `?\"MemoryError\" ?\"Runtime exited\"` + - Show alarm for "S3 permission errors" + - Pattern: `?\"AccessDenied\" ?\"Access Denied\"` + +### ๐Ÿ“ Script Template: +> "Metric filters are powerful - they let us create alarms from log patterns. Instead of just knowing the Lambda failed, we know WHY it failed - timeout, memory, permissions, or specific application errors." + +--- + +## Part 10: Triggering ALL Alarms Demo (8-10 minutes) + +### ๐ŸŽฏ Overview: We have 12 Alarms to Trigger +**Standard Lambda Alarms (6):** +1. High Error Rate +2. High Duration +3. Throttles +4. High Concurrency +5. Log Errors +6. Low Success Rate + +**Log-Based Alarms (6):** +7. Timeout Errors +8. Memory Errors +9. PIL/Image Processing Errors +10. S3 Permission Errors +11. Critical Errors +12. Large Image Warnings + +--- + +## ๐ŸŽฌ How to Trigger Each Alarm (One-by-One Demo Using AWS Console) + +### **1๏ธโƒฃ Trigger: PIL/Image Processing Errors Alarm** +**Alarm Name:** `image-processor-dev-processor-image-processing-errors` +**Threshold:** 2 errors in 1 minute +**Method:** Upload non-image files disguised as images + +**Step-by-Step UI Demo:** + +1. **Create Fake Image Files on Your Computer:** + - Open text editor (Notepad/TextEdit) + - Type: `This is HTML not an image` + - Save as: `fake-image-1.jpg` on your Desktop + - Create another: `This is just plain text` + - Save as: `fake-image-2.jpg` + - Create third: `{"error": "not an image"}` + - Save as: `fake-image-3.jpg` + +2. **Upload via S3 Console:** + - Go to AWS Console โ†’ S3 + - Click on bucket: `image-processor-dev-upload-9b5ecf4e` + - Click **"Upload"** button (orange) + - Click **"Add files"** + - Select all 3 fake-image files from Desktop + - Click **"Upload"** at bottom + - **Screen Record:** Files uploading โ†’ Show "Upload succeeded" + +3. **Watch Lambda Execution (Split Screen):** + - Keep S3 tab open + - Open new tab โ†’ CloudWatch โ†’ Log groups + - Click `/aws/lambda/image-processor-dev-processor` + - Click **"Log streams"** + - Click the **most recent stream** (top one) + - **Screen Record:** Show ERROR logs appearing: + - `"cannot identify image file"` + - `"Image processing failed"` + - Refresh every 5-10 seconds to see new logs + +4. **Check Alarm Status:** + - Open new tab โ†’ CloudWatch โ†’ **Alarms** + - Search: `image-processing-errors` + - **Screen Record:** Watch alarm change from OK (green) โ†’ ALARM (red) + - Click alarm name โ†’ Show graph with error spike + - Click **"History"** tab โ†’ Show state change event + +5. **Check Email:** + - Open your email inbox (split screen or phone) + - **Screen Record:** Show SNS email notification arriving + - Open email โ†’ Show alarm details + +**What to Show:** +- Split screen: S3 upload on left, CloudWatch logs on right +- Point out ERROR messages in logs +- Highlight alarm turning red +- Show email notification with details + +--- + +### **2๏ธโƒฃ Trigger: Large Image Warnings Alarm** +**Alarm Name:** `image-processor-dev-processor-large-images` +**Threshold:** 5 large images in 5 minutes +**Method:** Upload 6 high-resolution images + +**Step-by-Step UI Demo:** + +1. **Prepare Large Images (Before Demo):** + - Download 6 large images from https://picsum.photos/3000/2000 + - Or use your own high-res photos (>2MB each) + - Save them as: `large-1.jpg`, `large-2.jpg`, ... `large-6.jpg` + - Alternative: Use your phone camera photos (usually 3-5MB) + +2. **Upload via S3 Console:** + - AWS Console โ†’ S3 โ†’ `image-processor-dev-upload-9b5ecf4e` + - Click **"Upload"** + - Click **"Add files"** + - **Select all 6 large images** (hold Ctrl/Cmd) + - **Screen Record:** Show file sizes in upload dialog (each >2MB) + - Click **"Upload"** + - **Point out:** "These are large files - watch the upload progress bar" + +3. **Watch CloudWatch Dashboard (Real-Time):** + - Open new tab โ†’ CloudWatch โ†’ **Dashboards** + - Click: `image-processor-dev-processor-monitoring` + - **Screen Record:** Dashboard widgets updating + - **Point to:** + - "Invocations" widget - shows 6 spikes + - "Duration" widget - higher bars (slower processing) + - Scroll down to custom metrics + +4. **Check Logs for Large Image Warnings:** + - CloudWatch โ†’ Log groups โ†’ Click log group + - Click latest **log stream** + - **Search:** Type `"Large image"` in filter box + - **Screen Record:** Show 6 WARNING messages: + - `"Large image detected: 3.2MB"` + - Show REQUEST_ID for each + +5. **Check Metrics Graph:** + - CloudWatch โ†’ **Metrics** โ†’ **All metrics** + - Click **"ImageProcessor/Lambda"** namespace + - Check **"LargeImageWarnings"** metric + - **Screen Record:** Graph showing 6 data points + - Switch view to "Number" โ†’ Shows count: 6 + +6. **Wait for Alarm (2-3 minutes):** + - CloudWatch โ†’ **Alarms** + - Search: `large-images` + - **Screen Record:** + - Initially shows "Insufficient data" or "OK" + - Refresh page every 30 seconds + - Watch it turn ALARM (red) + - Click alarm โ†’ Show threshold line crossed on graph + +**What to Show:** +- File sizes during upload +- Dashboard updating in real-time +- Log search for "Large image" warnings +- Metric graph with 6 data points +- Alarm turning red after threshold crossed + +--- + +### **3๏ธโƒฃ Trigger: High Concurrency Alarm** +**Alarm Name:** `image-processor-dev-processor-high-concurrency` +**Threshold:** 50 concurrent executions +**Method:** Upload 60 images simultaneously (in batches) + +**Step-by-Step UI Demo:** + +1. **Prepare Test Images (Before Demo):** + - Create 60 copies of a small test image + - Or download one image and manually duplicate it 60 times + - Name them: `concurrent-1.jpg` to `concurrent-60.jpg` + - **Tip:** Use same image file to save space + +2. **Upload Multiple Batches Quickly:** + - AWS Console โ†’ S3 โ†’ Upload bucket + - Click **"Upload"** + - Click **"Add files"** + - **Select 30 files** (concurrent-1.jpg to concurrent-30.jpg) + - Click **"Upload"** (bottom of page) + - **DO NOT WAIT** - Immediately repeat: + - Open **NEW browser tab** โ†’ Same S3 bucket + - Click **"Upload"** again + - Select remaining 30 files (concurrent-31.jpg to concurrent-60.jpg) + - Click **"Upload"** + - **Screen Record:** Two upload dialogs running simultaneously + +3. **Watch Lambda Concurrency in Real-Time:** + - Open CloudWatch โ†’ **Dashboards** โ†’ Your dashboard + - **Screen Record:** "Concurrent Executions" widget + - **Point out:** Spike to 50+ (crosses red threshold line) + - Explain: "60 Lambda functions running at the same time!" + +4. **Check CloudWatch Metrics:** + - CloudWatch โ†’ **Metrics** โ†’ **All metrics** + - Click **"AWS/Lambda"** namespace + - Select **"ConcurrentExecutions"** + - Filter by function name: `image-processor-dev-processor` + - **Screen Record:** Graph showing spike to 50+ + +5. **Verify Alarm Triggered:** + - CloudWatch โ†’ **Alarms** + - Search: `high-concurrency` + - **Screen Record:** Alarm state = ALARM (red) + - Click alarm โ†’ Show graph with threshold line + - Show latest execution crossed 50 + +**What to Show:** +- Two browser tabs uploading simultaneously +- Dashboard concurrent execution widget spiking +- Metric graph showing 50+ concurrent executions +- Alarm turning red +- Explain the spike on the graph + +--- + +### **4๏ธโƒฃ Trigger: High Duration Alarm** +**Alarm Name:** `image-processor-dev-processor-high-duration` +**Threshold:** Duration > 45 seconds +**Method:** Process multiple large images simultaneously + +**Step-by-Step UI Demo:** + +1. **Use Large Images from Step 2:** + - Keep those 6 large images ready + - Or prepare 10 new large images (3000x2000 or larger) + +2. **Upload Large Images in Batch:** + - AWS Console โ†’ S3 โ†’ Upload bucket + - Click **"Upload"** โ†’ **"Add files"** + - **Select all 10 large images at once** + - Click **"Upload"** + - **Screen Record:** Upload progress bar for large files + +3. **Watch Dashboard Duration Widget:** + - CloudWatch โ†’ **Dashboards** โ†’ Your dashboard + - **Focus on "Duration" widget** + - **Screen Record:** Bars getting taller (higher duration) + - **Point out:** Some bars approaching or exceeding 45,000 ms (red line) + - Explain: "Large images take longer to process - creating multiple formats" + +4. **Check Metrics Detail:** + - CloudWatch โ†’ **Metrics** โ†’ **AWS/Lambda** + - Select **"Duration"** metric + - Filter: `image-processor-dev-processor` + - Change statistic to **"Maximum"** (top right) + - **Screen Record:** Graph showing duration peaks + - **Point to values:** Show some executions >45000ms + +5. **Verify Alarm:** + - CloudWatch โ†’ **Alarms** + - Search: `high-duration` + - **Screen Record:** Alarm = ALARM (red) + - Click alarm โ†’ Show graph + - **Highlight:** Threshold line at 45000ms + - Show data points above the line + +**What to Show:** +- Large file upload progress +- Dashboard duration widget with tall bars +- Metrics showing maximum duration >45 seconds +- Alarm triggered with red state +- Explain why: "Processing 3000x2000 images into 5 formats takes time" + +--- + +### **5๏ธโƒฃ Trigger: Log Errors Alarm** +**Alarm Name:** `image-processor-dev-processor-log-errors` +**Threshold:** 1 ERROR log in 1 minute +**Method:** Already triggered by fake images from Step 1 + +**Step-by-Step UI Demo:** + +1. **View Error Logs:** + - CloudWatch โ†’ **Log groups** + - Click `/aws/lambda/image-processor-dev-processor` + - Click **"Log streams"** + - Click the **most recent stream** + - **Screen Record:** Scroll through logs + - **Highlight:** All ERROR level entries + - `[ERROR] Image processing failed` + - `[ERROR] cannot identify image` + +2. **Use CloudWatch Logs Insights:** + - Click **"Logs Insights"** (left sidebar) + - Select log group: `/aws/lambda/image-processor-dev-processor` + - Enter query: + ```sql + fields @timestamp, @message + | filter @message like /ERROR/ + | sort @timestamp desc + | limit 20 + ``` + - Click **"Run query"** + - **Screen Record:** Table showing all ERROR logs + - **Point out:** Multiple error entries from fake images + +3. **Check Metric Filter:** + - Log groups โ†’ Click your log group + - Click **"Metric filters"** tab + - **Screen Record:** Show `image-processor-dev-processor-error-count` + - Click the metric filter name + - Click **"Test pattern"** + - Paste an ERROR log line + - Click **"Test pattern"** button + - **Show:** Pattern matches โ†’ Creates metric + +4. **View Error Metric:** + - Click **"View in metrics"** button + - **Screen Record:** Graph showing LambdaErrors metric + - **Point to:** Data points where errors occurred + - Explain: "Each ERROR log creates a metric data point" + +5. **Verify Alarm:** + - CloudWatch โ†’ **Alarms** + - Search: `log-errors` + - **Screen Record:** Alarm = ALARM (red) + - Click alarm โ†’ Show metric graph + - **Point out:** Threshold = 1, Current value > 1 + +**What to Show:** +- Raw log entries with ERROR level +- Logs Insights query results +- Metric filter configuration +- How logs convert to metrics +- Alarm triggered from log patterns + +--- + +### **6๏ธโƒฃ Trigger: Low Success Rate Alarm** +**Alarm Name:** `image-processor-dev-processor-low-success-rate` +**Threshold:** Success rate < 50% +**Method:** Combination of failed uploads (fake images) vs successful ones + +**Step-by-Step UI Demo:** + +1. **Review Processed Files:** + - S3 โ†’ **Buckets** + - Open: `image-processor-dev-processed-9b5ecf4e` (processed bucket) + - **Screen Record:** Show successfully processed images + - Count successes (from large images) + - **Point out:** "Only these succeeded" + +2. **Check Success Metric:** + - CloudWatch โ†’ **Metrics** โ†’ **All metrics** + - Click **"ImageProcessor/Lambda"** namespace + - Select **"SuccessfulProcesses"** metric + - **Screen Record:** Graph showing successful processes + - Note the count (e.g., 6 successes) + +3. **Check Error Metric:** + - Same page, add metric: + - Select **"LambdaErrors"** + - **Screen Record:** Graph showing both metrics + - **Point out:** Error count (e.g., 3 from fake images) + - Calculate: 6 success / (6 + 3 errors) = 67% โœ“ (above threshold) + +4. **Upload More Fake Files to Drop Below 50%:** + - Create 10 more fake .jpg files on Desktop + - S3 โ†’ Upload bucket โ†’ **Upload** โ†’ Add all 10 files + - **Screen Record:** Uploading fake files + - Wait 1-2 minutes for processing + +5. **Recalculate Success Rate:** + - CloudWatch โ†’ Metrics โ†’ Refresh graphs + - New calculation: 6 success / (6 + 13 errors) = 31% โŒ + - **Show:** Success rate dropped below 50% + +6. **Verify Alarm:** + - CloudWatch โ†’ **Alarms** + - Search: `low-success-rate` + - **Screen Record:** Alarm = ALARM (red) + - Click alarm โ†’ Show graph + - **Explain:** "More failures than successes triggers this alarm" + +**What to Show:** +- Count processed vs failed images +- Two metrics on same graph (success vs errors) +- Live calculation of success percentage +- Alarm triggered when <50% + +--- + +### **7๏ธโƒฃ Trigger: Timeout Errors Alarm** +**Alarm Name:** `image-processor-dev-processor-timeout-errors` +**Threshold:** 1 timeout in 1 minute +**Method:** Reduce Lambda timeout to 5 seconds via Console, then upload large image + +**Step-by-Step UI Demo:** + +1. **Reduce Lambda Timeout:** + - AWS Console โ†’ **Lambda** + - Click function: `image-processor-dev-processor` + - Click **"Configuration"** tab + - Click **"General configuration"** (left sidebar) + - Click **"Edit"** button (top right) + - **Screen Record:** Change timeout from 60 to **5 seconds** + - Scroll down โ†’ Click **"Save"** + - **Show:** Configuration saved notification + +2. **Upload Large Image:** + - Open S3 in new tab โ†’ Upload bucket + - Click **"Upload"** โ†’ **"Add files"** + - Select one of your large images (3000x2000) + - Click **"Upload"** + - **Screen Record:** Large file uploading + +3. **Watch Lambda Fail:** + - Switch to CloudWatch โ†’ **Log groups** + - Click log group โ†’ **Log streams** + - Click **latest stream** (will appear in 5-10 seconds) + - **Screen Record:** Log showing: + - `START RequestId: ...` + - `"Processing image: ..."` + - **`Task timed out after 5.00 seconds`** โ† Key message! + - **Highlight:** Timeout error message + +4. **Check Timeout Metric:** + - CloudWatch โ†’ **Metrics** โ†’ **ImageProcessor/Lambda** + - Select **"TimeoutErrors"** metric + - **Screen Record:** Graph showing 1 data point + - **Point out:** Spike when timeout occurred + +5. **Verify Alarm:** + - CloudWatch โ†’ **Alarms** + - Search: `timeout-errors` + - **Screen Record:** Alarm = ALARM (red) + - Click alarm โ†’ Show graph with threshold crossed + - Check **"History"** tab โ†’ Show state change event + +6. **Restore Lambda Timeout:** + - Lambda โ†’ Function โ†’ Configuration โ†’ General configuration + - Click **"Edit"** + - Change timeout back to **60 seconds** + - Click **"Save"** + - **Show:** "Configuration saved successfully" + +**What to Show:** +- Lambda configuration before/after timeout change +- Upload progress of large file +- Log showing exact timeout message +- Metric spike at timeout moment +- Alarm triggered +- Restore configuration + +--- + +### **8๏ธโƒฃ Trigger: Memory Errors Alarm** +**Alarm Name:** `image-processor-dev-processor-memory-errors` +**Threshold:** 1 memory error +**Method:** Reduce Lambda memory to 128MB via Console, upload large image + +**Step-by-Step UI Demo:** + +1. **Reduce Lambda Memory:** + - AWS Console โ†’ **Lambda** + - Click: `image-processor-dev-processor` + - Click **"Configuration"** tab + - Click **"General configuration"** + - Click **"Edit"** + - **Screen Record:** Change Memory from 1024 MB to **128 MB** + - **Point out:** "This is very low for image processing" + - Click **"Save"** + +2. **Upload Large Image:** + - S3 โ†’ Upload bucket โ†’ **"Upload"** + - Select large image (3000x2000) + - Click **"Upload"** + - **Screen Record:** Upload completing + +3. **Watch Lambda Run Out of Memory:** + - CloudWatch โ†’ Log groups โ†’ Click log group + - Click latest **log stream** + - **Screen Record:** Logs showing: + - `START RequestId: ...` + - `"Processing image: ..."` + - `"Downloaded in ...ms"` + - **`Runtime exited with error: signal: killed`** โ† Memory error! + - **Highlight:** No graceful error, just killed + - **Explain:** "Lambda ran out of memory mid-execution" + +4. **Check Memory Usage (Optional):** + - In same log stream, look for: + - `REPORT RequestId: ... Memory Size: 128 MB Max Memory Used: 128 MB` + - **Point out:** Used 100% of available memory + +5. **Check Metric:** + - CloudWatch โ†’ **Metrics** โ†’ **ImageProcessor/Lambda** + - Select **"MemoryErrors"** metric + - **Screen Record:** Spike showing memory error + +6. **Verify Alarm:** + - CloudWatch โ†’ **Alarms** + - Search: `memory-errors` + - **Screen Record:** Alarm = ALARM (red) + - Click alarm โ†’ Show graph + +7. **Restore Memory:** + - Lambda โ†’ Configuration โ†’ General configuration + - Click **"Edit"** + - Change Memory back to **1024 MB** + - Click **"Save"** + +**What to Show:** +- Memory setting at 128 MB (way too low) +- Log showing process killed (no error handling possible) +- Max memory used = 100% of available +- Alarm triggered +- Restore to proper memory size + +--- + +### **9๏ธโƒฃ Trigger: Throttles Alarm** +**Alarm Name:** `image-processor-dev-processor-throttles` +**Threshold:** 5 throttles +**Method:** Set concurrency limit to 1 via Console, then upload 20 images + +**Step-by-Step UI Demo:** + +1. **Set Reserved Concurrency Limit:** + - AWS Console โ†’ **Lambda** + - Click: `image-processor-dev-processor` + - Click **"Configuration"** tab + - Scroll down to **"Concurrency"** section + - Click **"Edit"** (right side) + - **Screen Record:** + - Select **"Reserve concurrency"** + - Enter value: **1** + - **Explain:** "Only 1 function can run at a time" + - Click **"Save"** + - **Show:** Warning message about limits + +2. **Prepare 20 Test Images:** + - Have 20 small images ready on Desktop + - Or duplicate one image 20 times + - Name: `throttle-1.jpg` to `throttle-20.jpg` + +3. **Upload Images in Batches (Quickly):** + - S3 โ†’ Upload bucket โ†’ **"Upload"** + - Add first 10 files โ†’ Click **"Upload"** + - **Immediately open new tab** โ†’ Same bucket + - Click **"Upload"** โ†’ Add next 10 files โ†’ **"Upload"** + - **Screen Record:** Two uploads running simultaneously + +4. **Watch Throttling Happen:** + - CloudWatch โ†’ **Log groups** โ†’ Click log group + - **Screen Record:** Notice only 1-2 log streams appearing + - **Explain:** "Most invocations were throttled, didn't even start" + - Open **Lambda** tab โ†’ Click **"Monitor"** tab + - Scroll to **"Throttles"** metric + - **Show:** Spike in throttle count + +5. **Check Throttle Metric:** + - CloudWatch โ†’ **Metrics** โ†’ **AWS/Lambda** + - Select **"Throttles"** metric + - Filter: `image-processor-dev-processor` + - **Screen Record:** Graph showing throttle spike + - **Point to:** Value > 5 (threshold crossed) + +6. **Verify Alarm:** + - CloudWatch โ†’ **Alarms** + - Search: `throttles` + - **Screen Record:** Alarm = ALARM (red) + - Click alarm โ†’ Show throttle count graph + - **Explain:** "AWS rejected most invocations because of concurrency limit" + +7. **Remove Concurrency Limit:** + - Lambda โ†’ Configuration โ†’ Concurrency + - Click **"Edit"** + - Select **"Use unreserved account concurrency"** + - Click **"Save"** + - **Show:** Concurrency restriction removed + +**What to Show:** +- Setting reserved concurrency to 1 +- Multiple uploads happening simultaneously +- Only 1 execution succeeding, others throttled +- Throttle metric spiking above threshold +- Alarm triggered +- Remove restriction + +--- + +### **๐Ÿ”Ÿ Trigger: S3 Permission Errors Alarm** +**Alarm Name:** `image-processor-dev-processor-s3-permission-errors` +**Threshold:** 1 access denied error +**Method:** Temporarily remove S3 write permissions from Lambda IAM role + +**Step-by-Step UI Demo:** + +1. **Find Lambda IAM Role:** + - AWS Console โ†’ **Lambda** + - Click: `image-processor-dev-processor` + - Click **"Configuration"** tab + - Click **"Permissions"** (left sidebar) + - **Screen Record:** Show "Execution role" + - Note role name: `image-processor-dev-processor-role` + - Click the role name (opens IAM in new tab) + +2. **Edit IAM Policy:** + - In IAM role page, find **"Permissions"** tab + - **Screen Record:** Show inline policy + - Click policy name: `image-processor-dev-processor-policy` + - Click **"Edit"** button + - Click **"JSON"** tab + - **Find the S3 PutObject permission:** + ```json + "s3:PutObject" + ``` + - **Screen Record:** + - Comment it out or delete the line + - Keep `s3:GetObject` (read is still allowed) + - Click **"Next"** โ†’ **"Save changes"** + +3. **Upload Test Image:** + - S3 โ†’ Upload bucket + - Click **"Upload"** โ†’ Add a valid image + - Click **"Upload"** + - **Screen Record:** File uploading successfully to S3 + +4. **Watch Lambda Fail on S3 Write:** + - CloudWatch โ†’ Log groups โ†’ Click log group + - Click latest **log stream** + - **Screen Record:** Logs showing: + - `"Processing image: ..."` + - `"Downloaded in ...ms"` (read works) + - **`AccessDenied`** or **`Access Denied`** or **`403`** โ† Error! + - `"An error occurred (403) when calling the PutObject operation"` + - **Highlight:** Lambda can READ from S3 but can't WRITE + +5. **Check Metric:** + - CloudWatch โ†’ **Metrics** โ†’ **ImageProcessor/Lambda** + - Select **"S3PermissionErrors"** metric + - **Screen Record:** Data point showing error + +6. **Verify Alarm:** + - CloudWatch โ†’ **Alarms** + - Search: `s3-permission-errors` + - **Screen Record:** Alarm = ALARM (red) + - Click alarm โ†’ Show graph + +7. **Restore S3 Permission:** + - IAM โ†’ Roles โ†’ Click the Lambda role + - Click policy name โ†’ **"Edit"** + - Click **"JSON"** + - **Add back:** `"s3:PutObject"` permission + - Click **"Next"** โ†’ **"Save changes"** + - **Show:** "Policy updated successfully" + +**What to Show:** +- IAM policy JSON before/after +- Lambda can read file but fails to write +- Access Denied error in logs +- S3 permission error metric spike +- Alarm triggered +- Restore permission + +--- + +### **1๏ธโƒฃ1๏ธโƒฃ Trigger: High Error Rate Alarm (Manual)** +**Alarm Name:** `image-processor-dev-processor-high-error-rate` +**Threshold:** 3 errors in 5 minutes +**Method:** Manually set alarm state via CloudWatch Console + +**Step-by-Step UI Demo:** + +1. **Navigate to CloudWatch Alarms:** + - AWS Console โ†’ **CloudWatch** + - Click **"Alarms"** (left sidebar) + - Click **"All alarms"** + - **Screen Record:** List of all 12 alarms + +2. **Select Alarm to Test:** + - Search: `high-error-rate` + - Click alarm name: `image-processor-dev-processor-high-error-rate` + - **Screen Record:** Current state (probably OK - green) + +3. **Manually Trigger Alarm:** + - Click **"Actions"** dropdown (top right) + - Select **"Set alarm state"** + - **Screen Record:** Dialog appears + - **State:** Select **"In alarm"** (red) + - **State reason:** Type `"Demo: Testing alarm notification system"` + - Click **"Update state"** + +4. **Watch Alarm Change:** + - **Screen Record:** + - Alarm state changes immediately to **ALARM** (red) + - Refresh icon appears + - State reason updated + +5. **Check History:** + - Click **"History"** tab + - **Screen Record:** Show state change entry + - **Point out:** + - Timestamp of state change + - Previous state: OK + - New state: ALARM + - Reason: Your demo message + +6. **Check Email Notification:** + - Open email inbox (split screen or phone camera) + - **Screen Record:** SNS notification email arriving + - Open email โ†’ Show: + - **Subject:** "ALARM: image-processor-dev-processor-high-error-rate" + - Alarm name + - Reason: "Demo: Testing alarm notification system" + - Threshold details + - **Explain:** "Email arrives in 30-60 seconds" + +7. **Reset Alarm to OK:** + - CloudWatch โ†’ Alarms โ†’ Same alarm + - Click **"Actions"** โ†’ **"Set alarm state"** + - **State:** Select **"OK"** (green) + - **State reason:** Type `"Demo complete - resetting alarm"` + - Click **"Update state"** + - **Screen Record:** Alarm turns green again + +**What to Show:** +- Manual alarm state change (no code/infrastructure needed) +- Immediate state change in console +- Email notification arriving +- History tracking all state changes +- Quick way to test alarm notifications +- Reset alarm to normal state + +--- + +### **1๏ธโƒฃ2๏ธโƒฃ Trigger: Critical Errors Alarm (Manual)** +**Alarm Name:** `image-processor-dev-processor-critical-errors` +**Threshold:** 1 CRITICAL log +**Method:** Manually trigger via CloudWatch Console + +**Step-by-Step UI Demo:** + +1. **Navigate to Alarm:** + - CloudWatch โ†’ **Alarms** + - Search: `critical-errors` + - Click: `image-processor-dev-processor-critical-errors` + - **Screen Record:** Current state (OK - green) + +2. **Manually Set to ALARM:** + - Click **"Actions"** โ†’ **"Set alarm state"** + - **State:** Select **"In alarm"** + - **State reason:** Type `"Demo: Simulating critical application failure"` + - Click **"Update state"** + - **Screen Record:** Alarm turns red immediately + +3. **Check SNS Topic:** + - Click **"Actions"** tab in alarm details + - **Screen Record:** Show "Alarm actions" + - **Point out:** Connected to `log-alerts` SNS topic + - **Explain:** "CRITICAL errors go to a different notification channel" + +4. **Check Email:** + - Open email inbox + - **Screen Record:** Email with "CRITICAL" in subject + - Open email โ†’ Show: + - Severity indicated in message + - Different from standard errors + - Reason: "Simulating critical application failure" + +5. **Explain Real-World Usage:** + - **Say to camera:** + - "In production, this alarm catches application-level CRITICAL logs" + - "Examples: Database connection lost, external API down, data corruption" + - "These require immediate attention, different from standard errors" + - "Notice it goes to a separate email/notification channel" + +6. **Reset Alarm:** + - Actions โ†’ Set alarm state โ†’ **"OK"** + - State reason: `"Demo complete"` + - Click **"Update state"** + +**What to Show:** +- Manual trigger for critical severity +- Different SNS topic (log alerts vs standard alerts) +- Email indicating critical severity +- Explain when CRITICAL logs would occur in real apps +- Differentiate from regular errors + +--- + +## ๐Ÿ“Š Demo Workflow Summary + +**Recommended Order for Video:** + +1. **Start Easy** โ†’ PIL Errors (upload HTML file as .jpg) +2. **Visual Impact** โ†’ Large Images (upload 6 big images) +3. **Concurrency** โ†’ Upload 60 images at once +4. **Duration** โ†’ Show dashboard during large image processing +5. **Log Errors** โ†’ Show Logs Insights query +6. **Configuration Changes:** + - Timeout โ†’ Reduce to 5s, show timeout + - Memory โ†’ Reduce to 128MB, show OOM error + - Throttle โ†’ Set limit to 1, upload 20 files +7. **Manual Tests** โ†’ High Error Rate, Critical Errors +8. **Advanced** โ†’ S3 Permissions (if time permits) + +--- + +## โœ… Expected Results Checklist + +| # | Alarm Name | Trigger Method | Time to Alarm | What to Show | +|---|------------|----------------|---------------|--------------| +| 1 | PIL Errors | HTML file as .jpg | 1-2 min | Error logs + email | +| 2 | Large Images | 6 big images (3000x2000) | 2-3 min | Metric graph spike | +| 3 | High Concurrency | 60 simultaneous uploads | 1-2 min | Dashboard spike | +| 4 | High Duration | Large images processing | 1-2 min | Duration metric >45s | +| 5 | Log Errors | From PIL errors | 1-2 min | Logs Insights query | +| 6 | Low Success Rate | Failed vs successful ratio | 2-3 min | Success rate <50% | +| 7 | Timeout | Timeout=5s + large image | 1-2 min | Timeout log message | +| 8 | Memory | Memory=128MB + image | 1-2 min | OOM error log | +| 9 | Throttles | Limit=1 + 20 uploads | 1-2 min | Throttle metric | +| 10 | S3 Permission | Remove IAM permission | 1-2 min | Access Denied log | +| 11 | High Error Rate | Manual set-alarm-state | Immediate | Email notification | +| 12 | Critical Errors | Manual set-alarm-state | Immediate | Critical severity email | + +**Total Demo Time: 8-10 minutes** +**Email Notifications: Expect 12+ emails** ๐Ÿ“ง + +--- + +## ๐ŸŽฌ Presentation Script + +> "Now for the exciting part - let's trigger all 12 alarms one by one! I'll start with the simple ones. Watch what happens when I upload an HTML file disguised as a JPEG - the Lambda tries to process it, fails, and immediately logs an error. Within 60 seconds, the PIL errors alarm fires and I get an email notification. +> +> Next, I'll upload six large high-resolution images. These will trigger not just the 'large image' alarm, but also cause duration spikes and potentially trigger the duration alarm too. +> +> For the really interesting tests, I'll modify the infrastructure using Terraform - reducing the timeout to 5 seconds, then uploading a large image. Watch it timeout mid-processing! Same with memory - I'll drop it to 128MB and force an out-of-memory error. +> +> The beauty of Infrastructure as Code is I can make these changes, test the alarms, and restore everything back in minutes. Let's see it in action!" + +--- + +## Part 11: Cleanup (2 minutes) + +### ๐ŸŽฌ What to Show: + +1. **Destroy Infrastructure** + ```bash + cd terraform + terraform destroy + ``` + + **During Destroy**: + - Type `yes` when prompted + - Explain: "Terraform is removing all resources" + - Show resources being deleted + +2. **Verify Cleanup** + - AWS Console โ†’ S3 โ†’ Show buckets deleted + - CloudWatch โ†’ Dashboards โ†’ Show dashboard deleted + - CloudWatch โ†’ Alarms โ†’ Show alarms deleted + - Lambda โ†’ Functions โ†’ Show function deleted + - SNS โ†’ Topics โ†’ Show topics deleted + +3. **Final Note** + ```bash + # If any S3 buckets remain (due to versioning) + aws s3 rb s3://bucket-name --force + ``` + +### ๐Ÿ“ Script Template: +> "Cleanup is simple with Terraform. One command removes everything we created. In production, you'd keep these monitoring systems running, but for this demo, we'll clean up to avoid charges." + +--- + +## ๐Ÿ“ธ Screenshot Checklist + +Make sure to capture these key screens: + +- [ ] Modular directory structure +- [ ] Terraform plan output (showing 40+ resources) +- [ ] SNS email confirmations (all 3) +- [ ] CloudWatch Dashboard (full view with all widgets) +- [ ] Lambda execution logs (structured with REQUEST_ID) +- [ ] Processed images in S3 (showing 5 variants) +- [ ] CloudWatch Metrics graphs +- [ ] All 12 alarms in Alarms console +- [ ] Alarm in ALARM state (red) +- [ ] Email notification from SNS +- [ ] Metric filter configuration +- [ ] Log Insights query results +- [ ] ERROR logs from invalid image +- [ ] Large image warning in logs + +--- + +## โฑ๏ธ Time Management Tips + +**Total Video Length: 35-45 minutes** (with explanations) + +### To Shorten Video: +- Pre-build Pillow layer (saves 3-5 min) +- Fast-forward Terraform apply (2-3 min) +- Pre-upload test images +- Use slides/diagrams instead of showing code files + +### To Extend Video: +- Show more module files in detail +- Explain each alarm threshold +- Demonstrate CloudWatch Logs Insights queries +- Show SNS topic policies +- Explain IAM roles and policies + +--- + +## ๐ŸŽฏ Key Talking Points + +### Module Benefits: +- โœ… Reusable across projects +- โœ… Easy to maintain and update +- โœ… Clear separation of concerns +- โœ… Can enable/disable features + +### Monitoring Best Practices: +- โœ… Multiple alert channels (email, SMS) +- โœ… Different severity levels +- โœ… Proactive monitoring (not just reactive) +- โœ… Custom business metrics alongside infrastructure metrics +- โœ… Centralized dashboard for visibility + +### Real-World Applications: +- Production debugging +- Performance optimization +- Cost tracking (via metrics) +- Compliance and audit trails +- Capacity planning + +--- + +## ๐Ÿšจ Common Issues & Solutions + +### Issue 1: Alarm Not Triggering +**Solution**: Check SNS subscription is confirmed + +### Issue 2: No Metrics Showing +**Solution**: Wait 2-3 minutes for metrics to populate + +### Issue 3: Lambda Timeout +**Solution**: Increase timeout in variables.tf + +### Issue 4: Email Not Received +**Solution**: Check spam folder, verify email in SNS console + +### Issue 5: Terraform Apply Fails +**Solution**: Check AWS credentials, region, and quotas + +--- + +## ๐Ÿ“š Additional Demo Ideas + +### Advanced Demonstrations: + +1. **Anomaly Detection** + - Enable CloudWatch Anomaly Detection on metrics + - Show how it learns normal patterns + +2. **CloudWatch Composite Alarms** + - Create alarm that fires when BOTH errors AND high duration occur + +3. **Insights Queries** + - Demo complex CloudWatch Logs Insights queries + - Show stats on processing times + +4. **Cross-Region Monitoring** + - Briefly mention how to monitor across regions + +5. **Cost Analysis** + - Show how to track costs via CloudWatch metrics + - Demonstrate cost per image processed + +--- + +## ๐ŸŽฌ Video Recording Tips + +1. **Audio**: Use clear narration, explain WHY not just WHAT +2. **Screen**: Use 1080p recording, zoom in on important parts +3. **Pace**: Don't rush - let viewers absorb information +4. **Repeat**: Mention key concepts multiple times +5. **Engage**: Ask rhetorical questions to keep viewers engaged + +--- + +## โœ… Final Checklist Before Recording + +- [ ] AWS account ready with credits/billing set up +- [ ] AWS CLI configured and tested +- [ ] Sample images prepared (normal, large, corrupted) +- [ ] Email client open and ready +- [ ] Browser tabs ready (AWS Console, Gmail) +- [ ] VS Code with project open +- [ ] Terminal split-screen configured +- [ ] Terraform state clean (no old resources) +- [ ] Pillow layer built +- [ ] terraform.tfvars configured with real email +- [ ] Screen recording software ready +- [ ] Microphone tested +- [ ] Notes/script accessible + +--- + +## ๐ŸŽ‰ Wrap-Up Message + +**End your video with:** + +> "We've built a production-ready Lambda function with enterprise-grade monitoring. You now have: +> - 12 CloudWatch alarms covering every scenario +> - Custom metrics from your application +> - Log-based alerts for specific error patterns +> - A comprehensive dashboard +> - All managed through modular, reusable Terraform code +> +> Fork this repo, customize it for your needs, and deploy monitoring in minutes instead of hours!" + +--- + +**Good luck with your video! ๐Ÿš€** + +*Questions? Need help? Open an issue on GitHub!* diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/QUICK_START.md b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/QUICK_START.md new file mode 100644 index 000000000..87f1adbfa --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/QUICK_START.md @@ -0,0 +1,98 @@ +# Quick Deployment Guide + +## Prerequisites Check +```bash +# Verify AWS CLI +aws sts get-caller-identity + +# Verify Terraform +terraform version + +# Verify Docker +docker --version +``` + +## Step 1: Build Lambda Layer (One-time) +```bash +cd scripts +./build_layer_docker.sh +cd ../terraform +``` + +## Step 2: Configure +```bash +cp terraform.tfvars.example terraform.tfvars +nano terraform.tfvars +``` + +**Required:** Change `alert_email` to your email address! + +## Step 3: Deploy +```bash +terraform init +terraform plan +terraform apply +``` + +## Step 4: Confirm SNS +Check your email and confirm 3 subscription links. + +## Step 5: Test +```bash +# Get bucket name +BUCKET=$(terraform output -raw upload_bucket_name) + +# Upload image +aws s3 cp path/to/image.jpg s3://$BUCKET/ + +# Watch logs +aws logs tail $(terraform output -raw lambda_log_group_name) --follow +``` + +## Step 6: View Dashboard +```bash +terraform output cloudwatch_dashboard_url +# Open URL in browser +``` + +## Cleanup +```bash +terraform destroy +``` + +## Troubleshooting + +### Lambda not triggering? +```bash +# Check S3 notification +aws s3api get-bucket-notification-configuration --bucket $BUCKET +``` + +### No email alerts? +- Check spam folder +- Verify SNS subscription confirmed +- AWS Console โ†’ SNS โ†’ Subscriptions + +### Check alarms +```bash +aws cloudwatch describe-alarms --alarm-names image-processor-dev-processor-high-error-rate +``` + +## Useful Commands + +```bash +# List all alarms +aws cloudwatch describe-alarms --query 'MetricAlarms[].AlarmName' + +# View recent logs +aws logs tail /aws/lambda/image-processor-dev-processor --since 1h + +# Test alarm +aws cloudwatch set-alarm-state \ + --alarm-name image-processor-dev-processor-high-error-rate \ + --state-value ALARM \ + --state-reason "Testing" + +# List S3 objects +aws s3 ls s3://$(terraform output -raw processed_bucket_name)/ --recursive +``` diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/README.md b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/README.md new file mode 100644 index 000000000..47ae1a344 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/README.md @@ -0,0 +1,632 @@ +# ๐Ÿ–ผ๏ธ AWS Lambda Image Processor with Comprehensive Monitoring + +A production-ready AWS Lambda function for automated image processing with enterprise-grade CloudWatch monitoring, implemented using modular Terraform. + +## ๐Ÿ“‹ Overview + +This project demonstrates AWS serverless best practices by combining: +- **Lambda-based image processing** (resize, compress, format conversion) +- **S3 event-driven architecture** (automatic triggering) +- **Comprehensive CloudWatch monitoring** (metrics, alarms, dashboards) +- **SNS alerting** (email/SMS notifications) +- **Modular Terraform** (reusable, maintainable infrastructure) + +### What It Does + +1. ๐Ÿ“ค Upload an image to S3 upload bucket +2. โšก Lambda function automatically triggers +3. ๐ŸŽจ Processes image (creates 5 variants: compressed, low-quality, WebP, PNG, thumbnail) +4. ๐Ÿ“ฅ Saves processed images to destination bucket +5. ๐Ÿ“Š Monitors everything with CloudWatch metrics and alarms +6. ๐Ÿ“ง Sends alerts via SNS when issues occur + +--- + +## ๐Ÿ—๏ธ Architecture + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ S3 Upload โ”‚ +โ”‚ Bucket โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ + โ”‚ S3 Event + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Lambda โ”‚ + โ”‚ Function โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ โ”‚ + โ–ผ โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ S3 Processedโ”‚ โ”‚ CloudWatch โ”‚ โ”‚ SNS โ”‚ +โ”‚ Bucket โ”‚ โ”‚ Logs โ”‚ โ”‚ Topics โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ + โ–ผ โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Metrics & โ”‚ โ”‚ Email/ โ”‚ + โ”‚ Alarms โ”‚ โ”‚ SMS โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ๐ŸŽฏ Key Features + +### Image Processing +- โœ… Multiple format support (JPEG, PNG, WebP, BMP, TIFF) +- โœ… Automatic format conversion +- โœ… Quality-based compression (85%, 60%) +- โœ… Thumbnail generation (300x300) +- โœ… Large image resizing (max 4096px) +- โœ… Automatic color space conversion + +### Monitoring & Observability +- โœ… **12 CloudWatch Alarms**: + - Error rate monitoring + - Duration/timeout warnings + - Throttle detection + - Memory usage tracking + - Concurrent execution limits + - Log-based error patterns + +- โœ… **Custom Metrics**: + - Image processing time + - Image sizes processed + - Success/failure rates + - Business-level insights + +- โœ… **Comprehensive Dashboard**: + - Real-time metrics visualization + - AWS metrics + custom metrics + - Log insights integration + - Performance trends + +- โœ… **Log-Based Alerts**: + - Timeout detection + - Memory errors + - S3 permission issues + - Image processing failures + - Critical application errors + +### Infrastructure +- โœ… **Modular Terraform** (6 reusable modules) +- โœ… **Security best practices** (IAM least privilege, S3 encryption) +- โœ… **Scalable architecture** (auto-scaling Lambda) +- โœ… **Cost-optimized** (pay per use) +- โœ… **Environment-agnostic** (dev/staging/prod) + +--- + +## ๐Ÿ“ Project Structure + +``` +aws-lamda-monitoring/ +โ”œโ”€โ”€ lambda/ +โ”‚ โ”œโ”€โ”€ lambda_function.py # Enhanced Lambda with structured logging +โ”‚ โ””โ”€โ”€ requirements.txt # Python dependencies (Pillow) +โ”œโ”€โ”€ scripts/ +โ”‚ โ”œโ”€โ”€ build_layer_docker.sh # Build Pillow layer using Docker +โ”‚ โ”œโ”€โ”€ deploy.sh # Deployment automation +โ”‚ โ””โ”€โ”€ destroy.sh # Cleanup script +โ”œโ”€โ”€ terraform/ +โ”‚ โ”œโ”€โ”€ main.tf # Root module orchestration +โ”‚ โ”œโ”€โ”€ variables.tf # Input variables +โ”‚ โ”œโ”€โ”€ outputs.tf # Output values +โ”‚ โ”œโ”€โ”€ provider.tf # AWS provider configuration +โ”‚ โ”œโ”€โ”€ terraform.tfvars.example # Configuration template +โ”‚ โ””โ”€โ”€ modules/ +โ”‚ โ”œโ”€โ”€ lambda_function/ # Lambda + IAM + CloudWatch Logs +โ”‚ โ”œโ”€โ”€ s3_buckets/ # S3 buckets with security +โ”‚ โ”œโ”€โ”€ sns_notifications/ # SNS topics + subscriptions +โ”‚ โ”œโ”€โ”€ cloudwatch_metrics/ # Metrics, filters, dashboard +โ”‚ โ”œโ”€โ”€ cloudwatch_alarms/ # Standard CloudWatch alarms +โ”‚ โ””โ”€โ”€ log_alerts/ # Log-based metric filters + alarms +โ”œโ”€โ”€ DEMO_GUIDE.md # Video presentation guide +โ””โ”€โ”€ README.md # This file +``` + +--- + +## ๐Ÿš€ Quick Start + +### Prerequisites + +- AWS Account with appropriate permissions +- AWS CLI configured (`aws configure`) +- Terraform >= 1.0 +- Docker (for building Pillow layer) +- Python 3.12 +- Git + +### Installation + +1. **Clone Repository** + ```bash + git clone + cd aws-lamda-monitoring + ``` + +2. **Build Lambda Layer** + ```bash + cd scripts + chmod +x build_layer_docker.sh + ./build_layer_docker.sh + ``` + + This creates `pillow_layer.zip` in the terraform directory. + +3. **Configure Variables** + ```bash + cd ../terraform + cp terraform.tfvars.example terraform.tfvars + nano terraform.tfvars # Edit with your settings + ``` + + **Required changes:** + ```hcl + alert_email = "your-email@example.com" # Change this! + aws_region = "us-east-1" # Your preferred region + ``` + +4. **Deploy Infrastructure** + ```bash + terraform init + terraform plan # Review changes + terraform apply # Type 'yes' to confirm + ``` + + โฑ๏ธ **Deployment time:** ~2-3 minutes + + ๐Ÿ“ **Resources created:** 40+ AWS resources + +5. **Confirm SNS Subscriptions** + - Check your email inbox + - Confirm 3 subscription emails from AWS + - This step is **required** to receive alerts + +6. **Capture Outputs** + ```bash + terraform output + ``` + + Save the bucket names for testing. + +--- + +## ๐Ÿงช Testing + +### Upload Test Image + +```bash +# Get bucket name from Terraform output +UPLOAD_BUCKET=$(terraform output -raw upload_bucket_name) + +# Upload an image +aws s3 cp path/to/your/image.jpg s3://$UPLOAD_BUCKET/ +``` + +### Watch Logs in Real-Time + +```bash +# Get log group name +LOG_GROUP=$(terraform output -raw lambda_log_group_name) + +# Tail logs +aws logs tail $LOG_GROUP --follow +``` + +### Check Processed Images + +```bash +# Get processed bucket name +PROCESSED_BUCKET=$(terraform output -raw processed_bucket_name) + +# List processed images +aws s3 ls s3://$PROCESSED_BUCKET/ --recursive +``` + +Expected output: 5 variants per uploaded image +- `image_compressed_xxx.jpg` (JPEG 85% quality) +- `image_low_xxx.jpg` (JPEG 60% quality) +- `image_webp_xxx.webp` (WebP format) +- `image_png_xxx.png` (PNG format) +- `image_thumbnail_xxx.jpg` (300x300 thumbnail) + +### Access CloudWatch Dashboard + +```bash +# Get dashboard URL +terraform output cloudwatch_dashboard_url +# Copy and paste in browser +``` + +Or navigate manually: +**AWS Console โ†’ CloudWatch โ†’ Dashboards โ†’ image-processor-dev-processor-monitoring** + +--- + +## ๐Ÿ“Š Monitoring + +### CloudWatch Dashboard + +The automatically created dashboard includes: + +1. **Lambda Invocations & Errors** - Total calls and failures +2. **Duration Metrics** - Avg, Max, P99 execution times +3. **Concurrent Executions** - Simultaneous Lambda instances +4. **Custom Metrics** - Success vs Error counts +5. **Processing Time** - Image processing performance +6. **Image Size** - Size distribution of processed images +7. **Recent Errors** - Live error log viewer + +### CloudWatch Alarms + +**Standard Alarms (6):** +- `high-error-rate` - Triggers on 3+ errors +- `high-duration` - Warns when approaching timeout (45s) +- `throttles` - Detects concurrent execution limits +- `high-concurrency` - Performance warning +- `log-errors` - ERROR logs detected +- `low-success-rate` - Success rate below threshold + +**Log-Based Alarms (6):** +- `timeout-errors` - Lambda timeout detection +- `memory-errors` - Out of memory issues +- `image-processing-errors` - PIL/Pillow failures +- `s3-permission-errors` - Access denied to S3 +- `critical-errors` - CRITICAL log level +- `large-images` - Large image performance warning + +### SNS Topics + +Three separate topics for different alert severities: +- **Critical Alerts** - Errors, failures, timeouts +- **Performance Alerts** - Duration, memory, throttles +- **Log Alerts** - Pattern-based log alerts + +### Custom Metrics + +Emitted from Lambda code and log filters: +- `ProcessingTime` - Milliseconds per image +- `ImagesProcessed` - Count of images +- `ProcessingSuccess` / `ProcessingFailure` - Success rates +- `LambdaErrors` - From log filter +- `ImageSizeBytes` - From log filter +- `TimeoutErrors` - From log filter +- `MemoryErrors` - From log filter + +--- + +## ๐Ÿ”ง Configuration + +### Alarm Thresholds + +Edit `terraform.tfvars` to customize: + +```hcl +# Error threshold (critical) +error_threshold = 3 + +# Duration warning (75% of timeout recommended) +duration_threshold_ms = 45000 + +# Throttle detection +throttle_threshold = 5 + +# Concurrent execution limit +concurrent_executions_threshold = 50 + +# Log error threshold +log_error_threshold = 1 +``` + +### Lambda Configuration + +```hcl +lambda_timeout = 60 # Seconds (max 900) +lambda_memory_size = 1024 # MB (128-10240) +log_level = "INFO" # DEBUG, INFO, WARNING, ERROR +log_retention_days = 7 # Days (1, 3, 5, 7, 14, 30, etc.) +``` + +### Enable/Disable Features + +```hcl +enable_cloudwatch_dashboard = true # Create dashboard +enable_no_invocation_alarm = false # Alert on no invocations +enable_s3_versioning = true # S3 version control +``` + +--- + +## ๐Ÿงช Testing Alarms + +### Manually Trigger Alarm + +```bash +aws cloudwatch set-alarm-state \ + --alarm-name image-processor-dev-processor-high-error-rate \ + --state-value ALARM \ + --state-reason "Testing alarm notification" +``` + +Check your email for notification! + +### Trigger Real Error + +```bash +# Create invalid image file +echo "This is not an image" > fake-image.jpg + +# Upload it +aws s3 cp fake-image.jpg s3://$UPLOAD_BUCKET/ + +# Watch logs for ERROR +aws logs tail $LOG_GROUP --follow + +# Check alarm in ~2 minutes +``` + +### Generate Load + +```bash +# Upload multiple images simultaneously +for i in {1..10}; do + aws s3 cp image.jpg s3://$UPLOAD_BUCKET/test-$i.jpg & +done +wait +``` + +Watch concurrent executions and duration metrics. + +--- + +## ๐Ÿ“ˆ Costs + +### Estimated Monthly Costs (Light Usage) + +| Service | Usage | Cost | +|---------|-------|------| +| Lambda | 1000 invocations, 1GB-s | $0.20 | +| S3 | 10GB storage, 1000 requests | $0.30 | +| CloudWatch Logs | 1GB ingested, 7-day retention | $0.50 | +| CloudWatch Metrics | 12 custom metrics | $3.60 | +| CloudWatch Alarms | 12 alarms | $1.20 | +| SNS | 100 notifications | $0.10 | +| **Total** | | **~$6/month** | + +๐Ÿ’ก **Tip**: Use `log_retention_days = 3` and reduce alarm count for cost savings. + +--- + +## ๐Ÿ› ๏ธ Troubleshooting + +### Lambda Not Triggering + +**Check:** +1. S3 event notification configured? + ```bash + aws s3api get-bucket-notification-configuration --bucket $UPLOAD_BUCKET + ``` +2. Lambda permission granted? + ```bash + aws lambda get-policy --function-name image-processor-dev-processor + ``` + +### No Email Notifications + +**Check:** +1. SNS subscription confirmed? + - AWS Console โ†’ SNS โ†’ Subscriptions + - Status should be "Confirmed" +2. Check spam folder +3. Resend confirmation from SNS console + +### Alarms Not Firing + +**Check:** +1. Wait 2-5 minutes for metrics to populate +2. Verify alarm threshold values +3. Check alarm state in CloudWatch console +4. Ensure metric has data: + ```bash + aws cloudwatch get-metric-statistics \ + --namespace AWS/Lambda \ + --metric-name Errors \ + --dimensions Name=FunctionName,Value=image-processor-dev-processor \ + --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \ + --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \ + --period 300 \ + --statistics Sum + ``` + +### Terraform Errors + +**Common issues:** +1. **Provider initialization**: Run `terraform init` +2. **State lock**: Another process using state? Wait or force unlock +3. **Resource conflicts**: Names already exist? Change `project_name` variable +4. **AWS credentials**: Run `aws sts get-caller-identity` to verify + +### Lambda Errors + +**Check logs:** +```bash +aws logs tail $LOG_GROUP --follow --filter-pattern ERROR +``` + +**Common errors:** +- PIL import error โ†’ Rebuild layer +- S3 access denied โ†’ Check IAM permissions +- Timeout โ†’ Increase `lambda_timeout` +- Memory error โ†’ Increase `lambda_memory_size` + +--- + +## ๐Ÿ”’ Security + +### IAM Least Privilege + +Lambda has minimal permissions: +- `s3:GetObject` on upload bucket (read-only) +- `s3:PutObject` on processed bucket (write-only) +- `logs:CreateLogGroup/Stream/PutLogEvents` (logging) +- `cloudwatch:PutMetricData` (custom metrics) + +### S3 Security + +- โœ… Encryption at rest (AES256) +- โœ… Versioning enabled +- โœ… Public access blocked +- โœ… Bucket policies restricted + +### Network + +- Lambda runs in AWS-managed VPC +- No public internet access required +- S3 access via AWS internal network + +--- + +## ๐Ÿš€ Production Deployment + +### Recommendations + +1. **Separate Environments** + ```bash + terraform workspace new prod + terraform workspace select prod + terraform apply -var="environment=prod" + ``` + +2. **Enable Advanced Monitoring** + ```hcl + enable_no_invocation_alarm = true + log_retention_days = 30 + ``` + +3. **Configure Different Alert Channels** + ```hcl + critical_alert_email = "oncall@company.com" + performance_alert_email = "devops@company.com" + log_alert_email = "team@company.com" + critical_alert_sms = "+1234567890" # Oncall phone + ``` + +4. **Implement CI/CD** + - Use GitHub Actions / GitLab CI + - Automated testing before deployment + - Blue/green deployments with Lambda versions + +5. **Enable X-Ray Tracing** + ```hcl + tracing_config { + mode = "Active" + } + ``` + +6. **Use CloudWatch Insights** + - Create saved queries for common investigations + - Set up dashboards per environment + +--- + +## ๐Ÿ“š Modules Documentation + +Each module is self-contained and reusable: + +### Module: `lambda_function` +Creates Lambda function with IAM role, policies, and CloudWatch log group. + +**Inputs:** function_name, runtime, timeout, memory_size, bucket ARNs +**Outputs:** function_arn, function_name, log_group_name, role_arn + +### Module: `s3_buckets` +Creates upload and processed S3 buckets with security configurations. + +**Inputs:** bucket names, versioning, lambda_function_arn +**Outputs:** upload_bucket_id, processed_bucket_id, bucket_arns + +### Module: `sns_notifications` +Creates SNS topics and email/SMS subscriptions. + +**Inputs:** project_name, alert emails, SMS numbers +**Outputs:** topic_arns, topic_names + +### Module: `cloudwatch_metrics` +Creates custom metrics, metric filters, and dashboard. + +**Inputs:** function_name, log_group_name, metric_namespace +**Outputs:** dashboard_name, dashboard_arn, metric_filter_names + +### Module: `cloudwatch_alarms` +Creates standard CloudWatch alarms for Lambda monitoring. + +**Inputs:** function_name, SNS topic ARNs, thresholds +**Outputs:** alarm_arns, alarm_names + +### Module: `log_alerts` +Creates log-based metric filters and alarms. + +**Inputs:** function_name, log_group_name, SNS topic ARN +**Outputs:** alarm_arns, alarm_names + +--- + +## ๐Ÿค Contributing + +Contributions welcome! Please: + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +--- + +## ๐Ÿ“ License + +This project is licensed under the MIT License - see LICENSE file for details. + +--- + +## ๐Ÿ™ Acknowledgments + +- AWS Lambda team for serverless compute +- HashiCorp for Terraform +- Pillow (PIL Fork) for image processing +- CloudWatch for comprehensive monitoring + +--- + +## ๐Ÿ“ž Support + +- **Issues**: Open a GitHub issue +- **Questions**: Use GitHub Discussions +- **Security**: Email security@yourcompany.com + +--- + +## ๐Ÿ—บ๏ธ Roadmap + +Future enhancements: +- [ ] Add API Gateway for direct uploads +- [ ] Implement Step Functions for complex workflows +- [ ] Add DynamoDB for processing metadata +- [ ] Create CloudFormation templates +- [ ] Add Lambda Powertools for advanced features +- [ ] Implement dead letter queue (DLQ) +- [ ] Add cost allocation tags +- [ ] Create multi-region deployment +- [ ] Add automated testing suite +- [ ] Implement canary deployments + +--- + +**Built with โค๏ธ for AWS monitoring enthusiasts** + +โญ Star this repo if you find it helpful! diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/lambda/README.md b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/lambda/README.md new file mode 100644 index 000000000..a38d45651 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/lambda/README.md @@ -0,0 +1,68 @@ +# Lambda Function Logic Explained + +Think of this Lambda function as an automated **Photo Processing Service**. + +### 1. The Manager (`lambda_handler` function) +This is the main function that gets triggered when you upload an image to an S3 bucket. + +* **Receives the Order:** It gets the notification that a new image has arrived. +* **Downloads the File:** It fetches the original image from the "upload" S3 bucket. +* **Delegates the Work:** It hands the image data over to the `process_image` function to do the actual work. +* **Uploads the Results:** After getting the processed images back, it uploads all the new versions to a different "processed" S3 bucket. +* **Files a Report:** It calls the `publish_metrics` function to send statistics (like how long it took) to CloudWatch for monitoring. + +### 2. The Image Expert (`process_image` function) +This function does all the heavy lifting for image manipulation. + +* **Handles Transparency:** It converts transparent images (like PNGs) to have a solid white background so they work well as JPEGs. +* **Resizes Large Images:** If an image is too big (over 4096 pixels), it shrinks it down while keeping the aspect ratio. +* **Creates Variants:** It creates four different versions of the image: + * A compressed JPEG (good quality for web). + * A low-quality JPEG (smaller file size). + * A modern WEBP version. + * A standard PNG version. +* **Creates a Thumbnail:** It also makes one small thumbnail (max 300x300 pixels), perfect for previews. + +### 3. The Reporter (`publish_metrics` function) +This function's only job is to report on what happened. + +* **Gathers Data:** It collects information like total processing time, how many images were handled, and whether the job was a success or failure. +* **Sends to CloudWatch:** It sends this data as "Custom Metrics" to CloudWatch, which allows you to build alarms and dashboards to see how well the function is performing. + +--- + +**In short:** You upload one image, and this code automatically creates multiple optimized versions and a thumbnail, stores them in a separate bucket, and reports on its own performance. + +____________________________________________________________________________________________________________________________________________________________________ + +## Logging Configuration + +### Explanation +* **`logging.getLogger()`**: This retrieves the standard Python logger object. In the AWS Lambda environment, this logger is automatically configured to send all output to **AWS CloudWatch Logs**. You don't need to manually configure handlers or formatters; AWS handles that for you. +* **`logger.setLevel(...)`**: This determines the "verbosity" of your logs. + * It looks for an Environment Variable named `LOG_LEVEL`. + * If you set `LOG_LEVEL` to `DEBUG` in your Terraform configuration (or AWS Console), the function will log everything. + * If not set, it defaults to `INFO`, meaning `DEBUG` messages are ignored to save space. + +### How it is called in the code +The logger object is used throughout your function to record events at different severity levels. Here are examples from your code: + +* **`logger.info(...)`**: Used for standard operational events. + * *Example:* `logger.info(f"REQUEST_ID: {request_id} - Received event: ...")` + * This tells you "The function started" or "The download finished". + +* **`logger.warning(...)`**: Used for potential issues that aren't fatal errors. + * *Example:* `logger.warning(f"REQUEST_ID: {request_id} - Large image detected...")` + * This alerts you that a user uploaded a huge file, which might be why the function was slow, but it didn't crash. + +* **`logger.error(...)`**: Used when the function crashes or fails. + * *Example:* `logger.error(f"... Image processing failed: {str(e)}", exc_info=True)` + * The `exc_info=True` argument is powerful: it automatically prints the full Python **Traceback** (stack trace) to CloudWatch so you can debug exactly where the code broke. + +* **`logger.debug(...)`**: Used for detailed developer info. + * *Example:* `logger.debug(f"Published {len(metrics)} custom metrics...")` + * These messages will **not** appear in CloudWatch unless you change the `LOG_LEVEL` environment variable to `DEBUG`. + + +--------------------------------------------------------------------- +--------------------------------------------------------------------- diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/lambda/lambda_function.py b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/lambda/lambda_function.py new file mode 100644 index 000000000..a8feefc86 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/lambda/lambda_function.py @@ -0,0 +1,318 @@ +########################################################### +########################################################### +# Lambda Function: Image Processor +# Description: Processes images uploaded to S3 by compressing +# and converting formats. Logs structured data and +# publishes custom metrics to CloudWatch. +# Documentation: +# https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html +########################################################### +########################################################### + + + +import json +import boto3 +import os +import logging +import time +from urllib.parse import unquote_plus +from io import BytesIO +from PIL import Image +import uuid +from datetime import datetime + +# Configure structured logging +logger = logging.getLogger() # Get the root logger instance provided by the Lambda runtime +logger.setLevel(os.environ.get('LOG_LEVEL', 'INFO')) # Set logging level from environment variable, default to INFO + +# AWS clients +s3_client = boto3.client('s3') +cloudwatch = boto3.client('cloudwatch') + +# Supported formats +SUPPORTED_FORMATS = ['JPEG', 'PNG', 'WEBP', 'BMP', 'TIFF'] +DEFAULT_QUALITY = 85 +MAX_DIMENSION = 4096 + +def lambda_handler(event, context): + """ + Lambda function to process images uploaded to S3. + Supports compression and format conversion. + """ + start_time = time.time() # Record the start time of the Lambda execution + request_id = context.aws_request_id if context else 'local' # Get the AWS request ID or default to 'local' for testing + + try: + logger.info(f"REQUEST_ID: {request_id} - Received event: {json.dumps(event)}") # Log the incoming event + + processed_count = 0 # Initialize a counter for processed images + + # Get the S3 event details from each record in the event + for record in event['Records']: # Iterate through each S3 event record + bucket = record['s3']['bucket']['name'] # Extract the S3 bucket name + key = unquote_plus(record['s3']['object']['key']) # Extract and URL-decode the S3 object key + file_size = record['s3']['object'].get('size', 0) # Get the size of the S3 object, default to 0 if not present + + logger.info(f"REQUEST_ID: {request_id} - Processing image: {key} from bucket: {bucket}, image_size: {file_size} bytes") # Log details of the image being processed + + # Log a warning if the image file size exceeds 10 MB + if file_size > 10 * 1024 * 1024: # Check if file size is greater than 10 MB + logger.warning(f"REQUEST_ID: {request_id} - Large image detected: {key} ({file_size} bytes)") # Log a warning for large images + + # Download the image from S3 + download_start = time.time() # Record the start time of the download + response = s3_client.get_object(Bucket=bucket, Key=key) # Download the object from S3 + image_data = response['Body'].read() # Read the image data from the response body + download_time = (time.time() - download_start) * 1000 # Calculate download time in milliseconds + + logger.info(f"REQUEST_ID: {request_id} - Downloaded in {download_time:.2f}ms") # Log the download time + + # Process the image + process_start = time.time() # Record the start time of image processing + processed_images = process_image(image_data, key, request_id) # Call the helper function to process the image + process_time = (time.time() - process_start) * 1000 # Calculate processing time in milliseconds + + logger.info(f"REQUEST_ID: {request_id} - processing_time: {process_time:.2f}ms") # Log the image processing time + + # Upload processed images to the processed bucket + processed_bucket = os.environ['PROCESSED_BUCKET'] # Get the name of the processed bucket from environment variables + + upload_start = time.time() # Record the start time of the upload + for processed_image in processed_images: # Iterate through each processed image variant + output_key = processed_image['key'] # Get the S3 key for the processed image + output_data = processed_image['data'] # Get the binary data of the processed image + content_type = processed_image['content_type'] # Get the content type of the processed image + + s3_client.put_object( # Upload the processed image to S3 + Bucket=processed_bucket, # Specify the target bucket + Key=output_key, # Specify the S3 key for the uploaded object + Body=output_data, # Provide the image data + ContentType=content_type, # Set the content type + Metadata={ # Add custom metadata to the S3 object + 'original-key': key, # Store the original S3 key + 'processed-by': 'lambda-image-processor', # Identify the processor + 'request-id': request_id, # Store the Lambda request ID + 'processing-time-ms': str(int(process_time)) # Store the processing time + } + ) + + upload_time = (time.time() - upload_start) * 1000 # Calculate upload time in milliseconds + logger.info(f"REQUEST_ID: {request_id} - Uploaded {len(processed_images)} variants in {upload_time:.2f}ms") # Log the upload time and number of variants + + processed_count += len(processed_images) # Increment the total count of processed images + logger.info(f"REQUEST_ID: {request_id} - Successfully processed {len(processed_images)} variants of {key}") # Log successful processing of variants + + # Calculate total execution time + total_time = (time.time() - start_time) * 1000 # Calculate the total execution time of the Lambda function + + # Publish custom metrics to CloudWatch + publish_metrics( # Call the helper function to publish metrics + function_name=context.function_name if context else 'local', # Pass the function name or 'local' + processing_time=total_time, # Pass the total processing time + image_count=len(event['Records']), # Pass the number of S3 records (original images) + success=True # Indicate successful execution + ) + + logger.info(f"REQUEST_ID: {request_id} - COMPLETED - Total time: {total_time:.2f}ms, Processed {processed_count} images") # Log completion message + + return { # Return a success response + 'statusCode': 200, # HTTP status code for success + 'body': json.dumps({ # JSON body with details + 'message': 'Image processed successfully', # Success message + 'processed_images': processed_count, # Number of processed image variants + 'execution_time_ms': int(total_time), # Total execution time + 'request_id': request_id # The request ID + }) + } + + except Exception as e: + error_time = (time.time() - start_time) * 1000 # Calculate the time until the error occurred + logger.error(f"REQUEST_ID: {request_id} - ERROR processing image after {error_time:.2f}ms: {str(e)}", exc_info=True) # Log the error with traceback + + # Publish failure metrics + if context: # Only publish metrics if context is available + publish_metrics( # Call the helper function to publish metrics + function_name=context.function_name, # Pass the function name + processing_time=error_time, # Pass the time until error + image_count=len(event.get('Records', [])), # Pass the number of S3 records (original images) + success=False # Indicate failure + ) + + return { # Return an error response + 'statusCode': 500, # HTTP status code for internal server error + 'body': json.dumps({ # JSON body with error details + 'error': str(e), # The error message + 'request_id': request_id # The request ID + }) + } + + +def process_image(image_data, original_key, request_id='unknown'): + """ + Process the image: create compressed versions and convert formats. + + Args: + image_data: Raw image data + original_key: Original S3 key + request_id: Request ID for logging + + Returns: + List of processed image dictionaries + """ + processed_images = [] + + try: + # Open the image + image = Image.open(BytesIO(image_data)) # Open the image from the binary data using BytesIO + + # Convert RGBA to RGB for JPEG compatibility + if image.mode in ('RGBA', 'LA', 'P'): # Check if the image has transparency (RGBA, LA) or is palette-based (P) + background = Image.new('RGB', image.size, (255, 255, 255)) # Create a new white background image + if image.mode == 'P': # If palette mode + image = image.convert('RGBA') # Convert to RGBA first to handle transparency correctly + background.paste(image, mask=image.split()[-1] if image.mode in ('RGBA', 'LA') else None) # Paste the image onto the white background using the alpha channel as a mask + image = background # Update the image variable to point to the flattened image + elif image.mode != 'RGB': # If not RGB and not one of the transparency modes handled above + image = image.convert('RGB') # Convert directly to RGB + + # Get original format and dimensions + original_format = image.format or 'JPEG' # Get the original format, default to JPEG if unknown + width, height = image.size # Get the dimensions of the image + + logger.info(f"REQUEST_ID: {request_id} - Original image: {width}x{height}, format: {original_format}") # Log the original image details + + # Resize if image is too large + if width > MAX_DIMENSION or height > MAX_DIMENSION: # Check if width or height exceeds the maximum allowed dimension + ratio = min(MAX_DIMENSION / width, MAX_DIMENSION / height) # Calculate the scaling ratio to fit within MAX_DIMENSION + new_width = int(width * ratio) # Calculate the new width + new_height = int(height * ratio) # Calculate the new height + image = image.resize((new_width, new_height), Image.Resampling.LANCZOS) # Resize the image using high-quality resampling + logger.info(f"REQUEST_ID: {request_id} - Resized to: {new_width}x{new_height}") # Log the new dimensions + + # Generate base filename + base_name = os.path.splitext(original_key)[0] # Extract the filename without extension from the S3 key + unique_id = str(uuid.uuid4())[:8] # Generate a short unique ID to prevent filename collisions + + # Create multiple variants + variants = [ # Define the list of image variants to generate + {'format': 'JPEG', 'quality': 85, 'suffix': 'compressed'}, # High quality JPEG + {'format': 'JPEG', 'quality': 60, 'suffix': 'low'}, # Low quality JPEG + {'format': 'WEBP', 'quality': 85, 'suffix': 'webp'}, # WebP format + {'format': 'PNG', 'quality': None, 'suffix': 'png'} # PNG format (lossless) + ] + + for variant in variants: # Iterate over each variant configuration + output = BytesIO() # Create an in-memory byte stream for the output + save_format = variant['format'] # Get the target format + + if variant['quality']: # Check if quality setting is provided + image.save(output, format=save_format, quality=variant['quality'], optimize=True) # Save with specific quality + else: + image.save(output, format=save_format, optimize=True) # Save without explicit quality (e.g., for PNG) + + output.seek(0) # Reset the stream position to the beginning + + # Generate output key + extension = save_format.lower() # Get lowercase extension + if extension == 'jpeg': # Normalize jpeg extension + extension = 'jpg' + + output_key = f"{base_name}_{variant['suffix']}_{unique_id}.{extension}" # Construct the new S3 key + + # Determine content type + content_type_map = { # Map formats to MIME types + 'JPEG': 'image/jpeg', + 'PNG': 'image/png', + 'WEBP': 'image/webp' + } + content_type = content_type_map.get(save_format, 'image/jpeg') # Get content type, default to jpeg + + processed_images.append({ # Add the processed image details to the list + 'key': output_key, # S3 Key + 'data': output.getvalue(), # Binary data + 'content_type': content_type, # MIME type + 'format': save_format, # Image format + 'quality': variant['quality'] # Quality setting used + }) + + logger.info(f"REQUEST_ID: {request_id} - Created variant: {output_key} ({save_format}, quality: {variant['quality']})") # Log creation + + # Create thumbnail + thumbnail = image.copy() # Create a copy of the image for the thumbnail + thumbnail.thumbnail((300, 300), Image.Resampling.LANCZOS) # Create a thumbnail (preserves aspect ratio, max 300x300) + thumb_output = BytesIO() # Create byte stream for thumbnail + thumbnail.save(thumb_output, format='JPEG', quality=80, optimize=True) # Save thumbnail as JPEG + thumb_output.seek(0) # Reset stream position + + processed_images.append({ # Add thumbnail to processed list + 'key': f"{base_name}_thumbnail_{unique_id}.jpg", # Thumbnail filename + 'data': thumb_output.getvalue(), # Thumbnail data + 'content_type': 'image/jpeg', # MIME type + 'format': 'JPEG', # Format + 'quality': 80 # Quality + }) + + logger.info(f"REQUEST_ID: {request_id} - Created thumbnail: {base_name}_thumbnail_{unique_id}.jpg") # Log thumbnail creation + + return processed_images # Return the list of all processed images + + except Exception as e: + logger.error(f"REQUEST_ID: {request_id} - Image processing failed: {str(e)}", exc_info=True) # Log any errors with traceback + raise # Re-raise the exception + + +def publish_metrics(function_name, processing_time, image_count, success): + """ + Publish custom metrics to CloudWatch. + + Args: + function_name: Name of the Lambda function + processing_time: Processing time in milliseconds + image_count: Number of images processed + success: Whether processing was successful + """ + try: + metrics = [ # Define a list of metric dictionaries to publish + { + 'MetricName': 'ProcessingTime', # Name of the metric for processing duration + 'Value': processing_time, # The actual time taken in milliseconds + 'Unit': 'Milliseconds', # Unit of measurement + 'Timestamp': datetime.utcnow() # Time of the metric event (UTC) + }, + { + 'MetricName': 'ImagesProcessed', # Name of the metric for count of images + 'Value': image_count, # Number of images processed in this batch + 'Unit': 'Count', # Unit is a count + 'Timestamp': datetime.utcnow() # Time of the metric event + }, + { + 'MetricName': 'ProcessingSuccess' if success else 'ProcessingFailure', # Dynamic name based on success status + 'Value': 1, # Value is 1 (binary indicator of success/failure occurrence) + 'Unit': 'Count', # Unit is a count + 'Timestamp': datetime.utcnow() # Time of the metric event + } + ] + + for metric in metrics: # Iterate through each metric defined above + cloudwatch.put_metric_data( # Call the CloudWatch API to put metric data + Namespace='ImageProcessor/Lambda', # Define the custom namespace for these metrics + MetricData=[{ # List of metric data points (sending one at a time here) + 'MetricName': metric['MetricName'], # Set the metric name + 'Value': metric['Value'], # Set the metric value + 'Unit': metric['Unit'], # Set the unit + 'Timestamp': metric['Timestamp'], # Set the timestamp + 'Dimensions': [ # Define dimensions to categorize the metric + { + 'Name': 'FunctionName', # Dimension name + 'Value': function_name # Dimension value (the Lambda function name) + } + ] + }] + ) + + logger.debug(f"Published {len(metrics)} custom metrics to CloudWatch") # Log debug message indicating success + + except Exception as e: # Catch any exceptions during metric publishing + # Don't fail the function if metrics publishing fails + logger.warning(f"Failed to publish metrics: {str(e)}") # Log a warning instead of raising an error diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/lambda/requirements.txt b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/lambda/requirements.txt new file mode 100644 index 000000000..e8c2a9b6d --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/lambda/requirements.txt @@ -0,0 +1 @@ +Pillow==10.4.0 diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/real-test.jpg b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/real-test.jpg new file mode 100644 index 000000000..52c0d1b3b Binary files /dev/null and b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/real-test.jpg differ diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/scripts/README.md b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/scripts/README.md new file mode 100644 index 000000000..9c96af9d5 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/scripts/README.md @@ -0,0 +1,55 @@ +# Deployment Scripts & Manual Build Instructions + +This directory contains the automation scripts for the Image Processor project. + +## Scripts + +### `deploy.sh` +Full deployment automation: +1. Checks for AWS CLI and Terraform prerequisites. +2. **Attempts to build the Lambda layer using Docker.** +3. Initializes and applies Terraform configuration. +4. Outputs S3 bucket names and usage instructions. + +### `destroy.sh` +Cleanup automation: +1. Empties all project S3 buckets (handling versioned objects and delete markers). +2. Destroys Terraform resources. + +--- + +## โš ๏ธ Manual Layer Build (If Docker Fails) + +If you are unable to run Docker (e.g., on older macOS versions), the `deploy.sh` script will fail at the build step. You must manually create the `pillow_layer.zip` file using the steps below. + +### Prerequisites +- Python 3.9 installed locally. +- `pip` installed. + +### Steps to Create `pillow_layer.zip` +Run these commands from the **project root** directory: + +1. **Create a temporary build directory:** + ```bash + mkdir -p pillow-layer/python + ``` + +2. **Install the Linux-compatible Pillow library:** + We use specific flags to download the binary compatible with AWS Lambda (Amazon Linux 2), rather than the macOS version. + ```bash + pip install Pillow \ + --platform manylinux2014_x86_64 \ + --target ./pillow-layer/python \ + --implementation cp \ + --python-version 3.9 \ + --only-binary=:all: \ + --upgrade + ``` + +3. **Zip the artifact:** + ```bash + cd pillow-layer + zip -r ../pillow_layer.zip python + cd .. + rm -rf pillow-layer + ``` \ No newline at end of file diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/scripts/build_layer_docker.sh b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/scripts/build_layer_docker.sh new file mode 100755 index 000000000..05632471a --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/scripts/build_layer_docker.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -e + +echo "๐Ÿš€ Building Lambda Layer with Pillow using Docker..." + +# Get the directory where the script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PROJECT_DIR="$( cd "$SCRIPT_DIR/.." && pwd )" +TERRAFORM_DIR="$PROJECT_DIR/terraform" + +# Check if Docker is installed +if ! command -v docker &> /dev/null; then + echo "โŒ Docker is not installed. Please install Docker first." + echo "๐Ÿ“– Visit: https://docs.docker.com/get-docker/" + exit 1 +fi + +# Check if Docker is running +if ! docker info &> /dev/null 2>&1; then + echo "โŒ Docker is not running. Please start Docker first." + exit 1 +fi + +echo "๐Ÿ“ฆ Building layer in Linux container (Python 3.12)..." + +# Build the layer using Docker with Python 3.12 on Linux AMD64 +docker run --rm \ + --platform linux/amd64 \ + -v "$TERRAFORM_DIR":/output \ + python:3.12-slim \ + bash -c " + echo '๐Ÿ“ฆ Installing Pillow for Linux AMD64...' && \ + pip install --quiet Pillow==10.4.0 -t /tmp/python/lib/python3.12/site-packages/ && \ + cd /tmp && \ + echo '๐Ÿ“ฆ Creating layer zip file...' && \ + apt-get update -qq && apt-get install -y -qq zip > /dev/null 2>&1 && \ + zip -q -r pillow_layer.zip python/ && \ + cp pillow_layer.zip /output/ && \ + echo 'โœ… Layer built successfully for Linux (Lambda-compatible)!' + " + +echo "๐Ÿ“ Location: $TERRAFORM_DIR/pillow_layer.zip" +echo "โœ… Layer is now compatible with AWS Lambda on all platforms!" diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/scripts/deploy.sh b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/scripts/deploy.sh new file mode 100755 index 000000000..86b98eec1 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/scripts/deploy.sh @@ -0,0 +1,62 @@ +#!/bin/bash +set -e + +echo "๐Ÿš€ Deploying Image Processor Application..." + +# Get the directory where the script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PROJECT_DIR="$( cd "$SCRIPT_DIR/.." && pwd )" + +# Check if AWS CLI is installed +if ! command -v aws &> /dev/null; then + echo "โŒ AWS CLI is not installed. Please install it first." + exit 1 +fi + +# Check if Terraform is installed +if ! command -v terraform &> /dev/null; then + echo "โŒ Terraform is not installed. Please install it first." + exit 1 +fi + +# Build Lambda layer using Docker (works on all platforms) +echo "๐Ÿ“ฆ Building Lambda layer with Docker..." +chmod +x "$SCRIPT_DIR/build_layer_docker.sh" +bash "$SCRIPT_DIR/build_layer_docker.sh" + +# Initialize Terraform +echo "๐Ÿ”ง Initializing Terraform..." +cd "$PROJECT_DIR/terraform" +terraform init + +# Plan deployment +echo "๐Ÿ“‹ Planning Terraform deployment..." +terraform plan -out=tfplan + +# Apply deployment +echo "๐Ÿš€ Applying Terraform deployment..." +terraform apply tfplan + +# Get outputs +echo "๐Ÿ“Š Getting deployment outputs..." +UPLOAD_BUCKET=$(terraform output -raw upload_bucket_name) +PROCESSED_BUCKET=$(terraform output -raw processed_bucket_name) +LAMBDA_FUNCTION=$(terraform output -raw lambda_function_name) +REGION=$(terraform output -raw region) + +echo "" +echo "โœ… Deployment completed successfully!" +echo "" +echo "๐Ÿ“ฆ S3 Buckets:" +echo " Upload: s3://${UPLOAD_BUCKET}" +echo " Processed: s3://${PROCESSED_BUCKET}" +echo "" +echo "โšก Lambda Function: ${LAMBDA_FUNCTION}" +echo "๐ŸŒ Region: ${REGION}" +echo "" +echo "๐ŸŽฏ Usage:" +echo " Upload an image to the upload bucket:" +echo " aws s3 cp your-image.jpg s3://${UPLOAD_BUCKET}/" +echo "" +echo " The Lambda function will automatically process it and save variants to:" +echo " s3://${PROCESSED_BUCKET}/" diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/scripts/destroy.sh b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/scripts/destroy.sh new file mode 100755 index 000000000..6da0bba75 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/scripts/destroy.sh @@ -0,0 +1,66 @@ +#!/bin/bash +set -e + +echo "๐Ÿ—‘๏ธ Destroying Image Processor Application..." + +# Get the directory where the script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PROJECT_DIR="$( cd "$SCRIPT_DIR/.." && pwd )" + +# Check if Terraform is installed +if ! command -v terraform &> /dev/null; then + echo "โŒ Terraform is not installed." + exit 1 +fi + +cd "$PROJECT_DIR/terraform" + +# Get bucket names from terraform state (more reliable than outputs) +UPLOAD_BUCKET=$(terraform state show 'aws_s3_bucket.upload_bucket' 2>/dev/null | grep -E '^\s+id\s+=' | awk -F'"' '{print $2}' || echo "") +PROCESSED_BUCKET=$(terraform state show 'aws_s3_bucket.processed_bucket' 2>/dev/null | grep -E '^\s+id\s+=' | awk -F'"' '{print $2}' || echo "") +FRONTEND_BUCKET=$(terraform state show 'aws_s3_bucket.frontend_bucket' 2>/dev/null | grep -E '^\s+id\s+=' | awk -F'"' '{print $2}' || echo "") + +# Function to empty versioned S3 bucket +empty_versioned_bucket() { + local bucket=$1 + echo "๐Ÿ—‘๏ธ Emptying bucket: $bucket (including all versions)..." + + # Delete all object versions + aws s3api list-object-versions --bucket "$bucket" --output json | \ + jq -r '.Versions[]? | "\(.Key) \(.VersionId)"' | \ + while read key version; do + if [ ! -z "$key" ]; then + aws s3api delete-object --bucket "$bucket" --key "$key" --version-id "$version" 2>/dev/null || true + fi + done + + # Delete all delete markers + aws s3api list-object-versions --bucket "$bucket" --output json | \ + jq -r '.DeleteMarkers[]? | "\(.Key) \(.VersionId)"' | \ + while read key version; do + if [ ! -z "$key" ]; then + aws s3api delete-object --bucket "$bucket" --key "$key" --version-id "$version" 2>/dev/null || true + fi + done + + echo "โœ“ Bucket $bucket emptied" +} + +# Empty S3 buckets +if [ ! -z "$UPLOAD_BUCKET" ]; then + empty_versioned_bucket "$UPLOAD_BUCKET" +fi + +if [ ! -z "$PROCESSED_BUCKET" ]; then + empty_versioned_bucket "$PROCESSED_BUCKET" +fi + +if [ ! -z "$FRONTEND_BUCKET" ]; then + empty_versioned_bucket "$FRONTEND_BUCKET" +fi + +# Destroy Terraform resources +echo "๐Ÿ”ฅ Destroying Terraform resources..." +terraform destroy -auto-approve + +echo "โœ… All resources destroyed successfully!" diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/.terraform.lock.hcl b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/.terraform.lock.hcl new file mode 100644 index 000000000..f90617a67 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/.terraform.lock.hcl @@ -0,0 +1,65 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/archive" { + version = "2.7.1" + constraints = "~> 2.4" + hashes = [ + "h1:Tr6LvLbm30zX4BRNPHhXo8SnOP0vg5UKAeunRNfnas8=", + "zh:19881bb356a4a656a865f48aee70c0b8a03c35951b7799b6113883f67f196e8e", + "zh:2fcfbf6318dd514863268b09bbe19bfc958339c636bcbcc3664b45f2b8bf5cc6", + "zh:3323ab9a504ce0a115c28e64d0739369fe85151291a2ce480d51ccbb0c381ac5", + "zh:362674746fb3da3ab9bd4e70c75a3cdd9801a6cf258991102e2c46669cf68e19", + "zh:7140a46d748fdd12212161445c46bbbf30a3f4586c6ac97dd497f0c2565fe949", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:875e6ce78b10f73b1efc849bfcc7af3a28c83a52f878f503bb22776f71d79521", + "zh:b872c6ed24e38428d817ebfb214da69ea7eefc2c38e5a774db2ccd58e54d3a22", + "zh:cd6a44f731c1633ae5d37662af86e7b01ae4c96eb8b04144255824c3f350392d", + "zh:e0600f5e8da12710b0c52d6df0ba147a5486427c1a2cc78f31eea37a47ee1b07", + "zh:f21b2e2563bbb1e44e73557bcd6cdbc1ceb369d471049c40eb56cb84b6317a60", + "zh:f752829eba1cc04a479cf7ae7271526b402e206d5bcf1fcce9f535de5ff9e4e6", + ] +} + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.27.0" + constraints = "~> 6.0" + hashes = [ + "h1:yey+4NnYAp2quzSKUaExTsbb+hDvtjl3EpdrbDdnM4Y=", + "zh:177a24b806c72e8484b5cabc93b2b38e3d770ae6f745a998b54d6619fd0e8129", + "zh:4ac4a85c14fb868a3306b542e6a56c10bd6c6d5a67bc0c9b8f6a9060cf5f3be7", + "zh:552652185bc85c8ba1da1d65dea47c454728a5c6839c458b6dcd3ce71c19ccfc", + "zh:60284b8172d09aee91eae0856f09855eaf040ce3a58d6933602ae17c53f8ed04", + "zh:6be38d156756ca61fb8e7c752cc5d769cd709686700ac4b230f40a6e95b5dbc9", + "zh:7a409138fae4ef42e3a637e37cb9efedf96459e28a3c764fc4e855e8db9a7485", + "zh:8070cf5224ed1ed3a3e9a59f7c30ff88bf071c7567165275d477c1738a56c064", + "zh:894439ef340a9a79f69cd759e27ad11c7826adeca27be1b1ca82b3c9702fa300", + "zh:89d035eebf08a97c89374ff06040955ddc09f275ecca609d0c9d58d149bef5cf", + "zh:985b1145d724fc1f38369099e4a5087141885740fd6c0b1dbc492171e73c2e49", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a80b47ae8d1475201c86bd94a5dcb9dd4da5e8b73102a90820b68b66b76d50fd", + "zh:d3395be1556210f82199b9166a6b2e677cee9c4b67e96e63f6c3a98325ad7ab0", + "zh:db0b869d09657f6f1e4110b56093c5fcdf9dbdd97c020db1e577b239c0adcbce", + "zh:ffc72e680370ae7c21f9bd3082c6317730df805c6797427839a6b6b7e9a26a01", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.7.2" + constraints = "~> 3.6" + hashes = [ + "h1:hkKSY5xI4R1H4Yrg10HHbtOoxZif2dXa9HFPSbaVg5o=", + "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", + "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", + "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", + "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", + "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", + "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", + "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", + "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", + "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", + "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", + ] +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/main.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/main.tf new file mode 100644 index 000000000..9c7761d4b --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/main.tf @@ -0,0 +1,197 @@ +# ============================================================================ +# AWS LAMBDA IMAGE PROCESSOR WITH COMPREHENSIVE MONITORING +# Modular Terraform Configuration +# ============================================================================ + +# Random suffix for unique resource names +resource "random_id" "suffix" { + byte_length = 4 +} + +locals { + bucket_prefix = "${var.project_name}-${var.environment}" + upload_bucket_name = "${local.bucket_prefix}-upload-${random_id.suffix.hex}" + processed_bucket_name = "${local.bucket_prefix}-processed-${random_id.suffix.hex}" + lambda_function_name = "${var.project_name}-${var.environment}-processor" + + common_tags = { + Project = var.project_name + Environment = var.environment + ManagedBy = "Terraform" + CreatedDate = timestamp() + } +} + +# ============================================================================ +# LAMBDA LAYER (Pillow) +# ============================================================================ + +resource "aws_lambda_layer_version" "pillow_layer" { + filename = "${path.module}/pillow_layer.zip" + layer_name = "${var.project_name}-pillow-layer" + compatible_runtimes = ["python3.12"] + description = "Pillow library for image processing" +} + +# Data source for Lambda function zip +data "archive_file" "lambda_zip" { + type = "zip" + source_file = "${path.module}/../lambda/lambda_function.py" + output_path = "${path.module}/lambda_function.zip" +} + +# ============================================================================ +# MODULE: SNS NOTIFICATIONS +# Creates SNS topics and subscriptions for alerts +# ============================================================================ + +module "sns_notifications" { + source = "./modules/sns_notifications" + + project_name = var.project_name + environment = var.environment + critical_alert_email = var.alert_email + performance_alert_email = var.alert_email + log_alert_email = var.alert_email + critical_alert_sms = var.alert_sms + + tags = local.common_tags +} + +# ============================================================================ +# MODULE: S3 BUCKETS (Created First) +# Creates upload and processed buckets with security configurations +# ============================================================================ + +module "s3_buckets" { + source = "./modules/s3_buckets" + + upload_bucket_name = local.upload_bucket_name + processed_bucket_name = local.processed_bucket_name + environment = var.environment + enable_versioning = var.enable_s3_versioning + + tags = local.common_tags +} + +# ============================================================================ +# MODULE: LAMBDA FUNCTION +# Creates Lambda function with IAM roles and CloudWatch logs +# ============================================================================ + +module "lambda_function" { + source = "./modules/lambda_function" + + function_name = local.lambda_function_name + handler = "lambda_function.lambda_handler" + runtime = var.lambda_runtime + timeout = var.lambda_timeout + memory_size = var.lambda_memory_size + lambda_zip_path = data.archive_file.lambda_zip.output_path + source_code_hash = data.archive_file.lambda_zip.output_base64sha256 + lambda_layers = [aws_lambda_layer_version.pillow_layer.arn] + + # Use actual bucket ARNs from S3 module + upload_bucket_arn = module.s3_buckets.upload_bucket_arn + upload_bucket_id = module.s3_buckets.upload_bucket_id + processed_bucket_arn = module.s3_buckets.processed_bucket_arn + processed_bucket_id = module.s3_buckets.processed_bucket_id + + aws_region = var.aws_region + log_retention_days = var.log_retention_days + log_level = var.log_level + + tags = local.common_tags +} + +# Lambda permission to be invoked by S3 +# This resource grants Amazon S3 permission to invoke the Lambda function. +# It's crucial for allowing S3 bucket events (like object creation) to trigger the Lambda. +# `statement_id`: A unique identifier for this permission statement. +# `action`: Specifies the action that is allowed, in this case, invoking a Lambda function. +# `function_name`: The name of the Lambda function that S3 is allowed to invoke. +# `principal`: The service that is granted permission, which is S3 in this context. +# `source_arn`: The ARN of the S3 bucket from which the invocation requests will originate. +resource "aws_lambda_permission" "allow_s3_invoke" { + statement_id = "AllowExecutionFromS3" + action = "lambda:InvokeFunction" + function_name = module.lambda_function.function_name + principal = "s3.amazonaws.com" + source_arn = module.s3_buckets.upload_bucket_arn +} + +# S3 bucket notification to trigger Lambda +# This resource configures an S3 bucket event notification. +# When an object is created in the `upload_bucket_id` bucket, +# it triggers the specified Lambda function (`function_arn`). +# The `depends_on` ensures the Lambda permission is set before the notification. +# S3 bucket notification to trigger Lambda + +resource "aws_s3_bucket_notification" "upload_trigger" { + bucket = module.s3_buckets.upload_bucket_id + + lambda_function { + lambda_function_arn = module.lambda_function.function_arn + events = ["s3:ObjectCreated:*"] + } + + depends_on = [aws_lambda_permission.allow_s3_invoke] +} + + +# ============================================================================ +# MODULE: CLOUDWATCH METRICS +# Creates custom metrics, metric filters, and dashboard +# ============================================================================ + +module "cloudwatch_metrics" { + source = "./modules/cloudwatch_metrics" + + function_name = module.lambda_function.function_name + log_group_name = module.lambda_function.log_group_name + metric_namespace = var.metric_namespace + aws_region = var.aws_region + enable_dashboard = var.enable_cloudwatch_dashboard + + tags = local.common_tags +} + +# ============================================================================ +# MODULE: CLOUDWATCH ALARMS +# Creates CloudWatch alarms for Lambda monitoring +# ============================================================================ + +module "cloudwatch_alarms" { + source = "./modules/cloudwatch_alarms" + + function_name = module.lambda_function.function_name + critical_alerts_topic_arn = module.sns_notifications.critical_alerts_topic_arn + performance_alerts_topic_arn = module.sns_notifications.performance_alerts_topic_arn + metric_namespace = var.metric_namespace + + # Alarm thresholds + error_threshold = var.error_threshold + duration_threshold_ms = var.duration_threshold_ms + throttle_threshold = var.throttle_threshold + concurrent_executions_threshold = var.concurrent_executions_threshold + log_error_threshold = var.log_error_threshold + enable_no_invocation_alarm = var.enable_no_invocation_alarm + + tags = local.common_tags +} + +# ============================================================================ +# MODULE: LOG ALERTS +# Creates log-based metric filters and alarms for specific error patterns +# ============================================================================ + +module "log_alerts" { + source = "./modules/log_alerts" + + function_name = module.lambda_function.function_name + log_group_name = module.lambda_function.log_group_name + log_alerts_topic_arn = module.sns_notifications.log_alerts_topic_arn + metric_namespace = var.metric_namespace + + tags = local.common_tags +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_alarms/main.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_alarms/main.tf new file mode 100644 index 000000000..2a5f1c5ef --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_alarms/main.tf @@ -0,0 +1,194 @@ +# ============================================================================ +# CLOUDWATCH ALARMS MODULE +# Creates CloudWatch alarms for Lambda function monitoring +# Documentation +# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm +# ============================================================================ + +# Alarm: Lambda Error Rate +resource "aws_cloudwatch_metric_alarm" "lambda_errors" { + alarm_name = "${var.function_name}-high-error-rate" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = var.error_evaluation_periods + metric_name = "Errors" + namespace = "AWS/Lambda" + period = var.alarm_period + statistic = "Sum" + threshold = var.error_threshold + alarm_description = "Triggers when Lambda function has more than ${var.error_threshold} errors" + actions_enabled = true + alarm_actions = [var.critical_alerts_topic_arn] + ok_actions = [var.critical_alerts_topic_arn] + + dimensions = { + FunctionName = var.function_name + } + + tags = merge( + var.tags, + { + Name = "${var.function_name}-error-alarm" + Severity = "Critical" + } + ) +} + +# Alarm: Lambda Duration (Timeout Warning) +resource "aws_cloudwatch_metric_alarm" "lambda_duration" { + alarm_name = "${var.function_name}-high-duration" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = var.duration_evaluation_periods + metric_name = "Duration" + namespace = "AWS/Lambda" + period = var.alarm_period + statistic = "Average" + threshold = var.duration_threshold_ms + alarm_description = "Triggers when Lambda duration exceeds ${var.duration_threshold_ms}ms (approaching timeout)" + actions_enabled = true + alarm_actions = [var.performance_alerts_topic_arn] + ok_actions = [var.performance_alerts_topic_arn] + + dimensions = { + FunctionName = var.function_name + } + + tags = merge( + var.tags, + { + Name = "${var.function_name}-duration-alarm" + Severity = "Warning" + } + ) +} + +# Alarm: Lambda Throttles +resource "aws_cloudwatch_metric_alarm" "lambda_throttles" { + alarm_name = "${var.function_name}-throttles" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = var.throttle_evaluation_periods + metric_name = "Throttles" + namespace = "AWS/Lambda" + period = var.alarm_period + statistic = "Sum" + threshold = var.throttle_threshold + alarm_description = "Triggers when Lambda function is throttled (concurrent execution limit reached)" + actions_enabled = true + alarm_actions = [var.critical_alerts_topic_arn] + ok_actions = [var.critical_alerts_topic_arn] + + dimensions = { + FunctionName = var.function_name + } + + tags = merge( + var.tags, + { + Name = "${var.function_name}-throttle-alarm" + Severity = "Critical" + } + ) +} + +# Alarm: Concurrent Executions +resource "aws_cloudwatch_metric_alarm" "concurrent_executions" { + alarm_name = "${var.function_name}-high-concurrency" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = 2 + metric_name = "ConcurrentExecutions" + namespace = "AWS/Lambda" + period = var.alarm_period + statistic = "Maximum" + threshold = var.concurrent_executions_threshold + alarm_description = "Triggers when concurrent executions exceed ${var.concurrent_executions_threshold}" + actions_enabled = true + alarm_actions = [var.performance_alerts_topic_arn] + + dimensions = { + FunctionName = var.function_name + } + + tags = merge( + var.tags, + { + Name = "${var.function_name}-concurrency-alarm" + Severity = "Warning" + } + ) +} + +# Alarm: Custom Error Metric from Logs +resource "aws_cloudwatch_metric_alarm" "log_errors" { + alarm_name = "${var.function_name}-log-errors" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = 1 + metric_name = "LambdaErrors" + namespace = var.metric_namespace + period = 60 + statistic = "Sum" + threshold = var.log_error_threshold + alarm_description = "Triggers when ERROR logs are detected in Lambda function" + actions_enabled = true + alarm_actions = [var.critical_alerts_topic_arn] + treat_missing_data = "notBreaching" + + tags = merge( + var.tags, + { + Name = "${var.function_name}-log-error-alarm" + Severity = "Critical" + } + ) +} + +# Alarm: No Invocations (Anomaly Detection) +resource "aws_cloudwatch_metric_alarm" "no_invocations" { + count = var.enable_no_invocation_alarm ? 1 : 0 + alarm_name = "${var.function_name}-no-invocations" + comparison_operator = "LessThanThreshold" + evaluation_periods = 3 + metric_name = "Invocations" + namespace = "AWS/Lambda" + period = 300 + statistic = "Sum" + threshold = 1 + alarm_description = "Triggers when Lambda has no invocations for 15 minutes (possible S3 trigger issue)" + actions_enabled = true + alarm_actions = [var.performance_alerts_topic_arn] + treat_missing_data = "breaching" + + dimensions = { + FunctionName = var.function_name + } + + tags = merge( + var.tags, + { + Name = "${var.function_name}-no-invocation-alarm" + Severity = "Info" + } + ) +} + +# Alarm: Successful Processes (Business Metric) +resource "aws_cloudwatch_metric_alarm" "low_success_rate" { + alarm_name = "${var.function_name}-low-success-rate" + comparison_operator = "LessThanThreshold" + evaluation_periods = 2 + metric_name = "SuccessfulProcesses" + namespace = var.metric_namespace + period = 300 + statistic = "Sum" + threshold = var.min_success_threshold + alarm_description = "Triggers when successful image processes are below expected rate" + actions_enabled = true + alarm_actions = [var.performance_alerts_topic_arn] + treat_missing_data = "notBreaching" + + tags = merge( + var.tags, + { + Name = "${var.function_name}-success-rate-alarm" + Severity = "Warning" + } + ) +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_alarms/outputs.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_alarms/outputs.tf new file mode 100644 index 000000000..0a65859eb --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_alarms/outputs.tf @@ -0,0 +1,41 @@ +output "error_alarm_arn" { + description = "ARN of the error rate alarm" + value = aws_cloudwatch_metric_alarm.lambda_errors.arn +} + +output "duration_alarm_arn" { + description = "ARN of the duration alarm" + value = aws_cloudwatch_metric_alarm.lambda_duration.arn +} + +output "throttle_alarm_arn" { + description = "ARN of the throttle alarm" + value = aws_cloudwatch_metric_alarm.lambda_throttles.arn +} + +output "concurrency_alarm_arn" { + description = "ARN of the concurrent executions alarm" + value = aws_cloudwatch_metric_alarm.concurrent_executions.arn +} + +output "log_error_alarm_arn" { + description = "ARN of the log error alarm" + value = aws_cloudwatch_metric_alarm.log_errors.arn +} + +output "success_rate_alarm_arn" { + description = "ARN of the success rate alarm" + value = aws_cloudwatch_metric_alarm.low_success_rate.arn +} + +output "all_alarm_names" { + description = "List of all alarm names" + value = [ + aws_cloudwatch_metric_alarm.lambda_errors.alarm_name, + aws_cloudwatch_metric_alarm.lambda_duration.alarm_name, + aws_cloudwatch_metric_alarm.lambda_throttles.alarm_name, + aws_cloudwatch_metric_alarm.concurrent_executions.alarm_name, + aws_cloudwatch_metric_alarm.log_errors.alarm_name, + aws_cloudwatch_metric_alarm.low_success_rate.alarm_name + ] +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_alarms/variables.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_alarms/variables.tf new file mode 100644 index 000000000..190d0eeb7 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_alarms/variables.tf @@ -0,0 +1,99 @@ +variable "function_name" { + description = "Name of the Lambda function" + type = string +} + +variable "critical_alerts_topic_arn" { + description = "ARN of the SNS topic for critical alerts" + type = string +} + +variable "performance_alerts_topic_arn" { + description = "ARN of the SNS topic for performance alerts" + type = string +} + +variable "metric_namespace" { + description = "CloudWatch custom metrics namespace" + type = string + default = "ImageProcessor/Lambda" +} + +variable "alarm_period" { + description = "Period for alarm evaluation in seconds" + type = number + default = 60 +} + +# Error Alarm Configuration +variable "error_threshold" { + description = "Number of errors to trigger alarm" + type = number + default = 3 +} + +variable "error_evaluation_periods" { + description = "Number of periods to evaluate for error alarm" + type = number + default = 1 +} + +# Duration Alarm Configuration +variable "duration_threshold_ms" { + description = "Duration threshold in milliseconds" + type = number + default = 45000 # 45 seconds (75% of 60s timeout) +} + +variable "duration_evaluation_periods" { + description = "Number of periods to evaluate for duration alarm" + type = number + default = 2 +} + +# Throttle Alarm Configuration +variable "throttle_threshold" { + description = "Number of throttles to trigger alarm" + type = number + default = 5 +} + +variable "throttle_evaluation_periods" { + description = "Number of periods to evaluate for throttle alarm" + type = number + default = 1 +} + +# Concurrent Executions Configuration +variable "concurrent_executions_threshold" { + description = "Concurrent executions threshold" + type = number + default = 50 +} + +# Log Error Threshold +variable "log_error_threshold" { + description = "Number of log errors to trigger alarm" + type = number + default = 1 +} + +# Success Rate Configuration +variable "min_success_threshold" { + description = "Minimum successful processes expected" + type = number + default = 1 +} + +# Optional Alarms +variable "enable_no_invocation_alarm" { + description = "Enable alarm for no invocations" + type = bool + default = false +} + +variable "tags" { + description = "Common tags for all resources" + type = map(string) + default = {} +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_metrics/main.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_metrics/main.tf new file mode 100644 index 000000000..267ecc00f --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_metrics/main.tf @@ -0,0 +1,237 @@ +# ============================================================================ +# CLOUDWATCH METRICS MODULE +# Creates custom metrics, metric filters, and CloudWatch dashboard +# Here is the documentation for reference: +# https://docs.aws.amazon.com/lambda/latest/dg/monitoring-metrics-types.html#deployment-metrics +# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_dashboard +# ============================================================================ + +# Metric Filter: Lambda Errors +resource "aws_cloudwatch_log_metric_filter" "lambda_errors" { + name = "${var.function_name}-error-count" + log_group_name = var.log_group_name + pattern = "[timestamp, request_id, level = ERROR*, ...]" + + metric_transformation { + name = "LambdaErrors" + namespace = var.metric_namespace + value = "1" + default_value = "0" + } +} + +# Metric Filter: Image Processing Time +resource "aws_cloudwatch_log_metric_filter" "processing_time" { + name = "${var.function_name}-processing-time" + log_group_name = var.log_group_name + pattern = "[timestamp, request_id, level, message, processing_time_key = \"processing_time:\", processing_time, ...]" + + metric_transformation { + name = "ImageProcessingTime" + namespace = var.metric_namespace + value = "$processing_time" + unit = "Milliseconds" + default_value = "0" + } +} + +# Metric Filter: Successful Processes +resource "aws_cloudwatch_log_metric_filter" "successful_processes" { + name = "${var.function_name}-success-count" + log_group_name = var.log_group_name + pattern = "\"Successfully processed\"" + + metric_transformation { + name = "SuccessfulProcesses" + namespace = var.metric_namespace + value = "1" + default_value = "0" + } +} + +# Metric Filter: Image Size +resource "aws_cloudwatch_log_metric_filter" "image_size" { + name = "${var.function_name}-image-size" + log_group_name = var.log_group_name + pattern = "[timestamp, request_id, level, message, size_key = \"image_size:\", image_size, ...]" + + metric_transformation { + name = "ImageSizeBytes" + namespace = var.metric_namespace + value = "$image_size" + unit = "Bytes" + default_value = "0" + } +} + +# Metric Filter: S3 Access Denied +resource "aws_cloudwatch_log_metric_filter" "s3_access_denied" { + name = "${var.function_name}-s3-denied" + log_group_name = var.log_group_name + pattern = "AccessDenied" + + metric_transformation { + name = "S3AccessDenied" + namespace = var.metric_namespace + value = "1" + default_value = "0" + } +} + +# CloudWatch Dashboard +resource "aws_cloudwatch_dashboard" "lambda_monitoring" { + count = var.enable_dashboard ? 1 : 0 + dashboard_name = "${var.function_name}-monitoring" + + dashboard_body = jsonencode({ + widgets = [ + { + # Widget for Lambda Invocations and Errors + type = "metric" + properties = { + metrics = [ + // 1. Namespace, 2. Metric Name, 3. Options + ["AWS/Lambda", "Invocations", { stat = "Sum", label = "Total Invocations" }], + [".", "Errors", { stat = "Sum", label = "Errors" }], + [".", "Throttles", { stat = "Sum", label = "Throttles" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "Lambda Invocations & Errors" + period = 300 + dimensions = { + FunctionName = var.function_name + } + } + width = 12 + height = 6 + x = 0 + y = 0 + }, + { + # Widget for Lambda Duration + type = "metric" + properties = { + metrics = [ + // 1. Namespace, 2. Metric Name, 3. Options + ["AWS/Lambda", "Duration", { stat = "Average", label = "Avg Duration" }], + ["...", { stat = "Maximum", label = "Max Duration" }], + ["...", { stat = "p99", label = "P99 Duration" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "Lambda Duration (ms)" + period = 300 + yAxis = { + left = { + min = 0 + } + } + dimensions = { + FunctionName = var.function_name + } + } + width = 12 + height = 6 + x = 12 + y = 0 + }, + { + # Widget for Concurrent Executions + type = "metric" + properties = { + metrics = [ + // 1. Namespace, 2. Metric Name, 3. Options + ["AWS/Lambda", "ConcurrentExecutions", { stat = "Maximum", label = "Concurrent Executions" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "Concurrent Executions" + period = 300 + dimensions = { + FunctionName = var.function_name + } + } + width = 12 + height = 6 + x = 0 + y = 6 + }, + { + # Widget for Custom Metrics: Errors vs Success + type = "metric" + properties = { + metrics = [ + // 1. Namespace, 2. Metric Name, 3. Options + [var.metric_namespace, "LambdaErrors", { stat = "Sum", label = "Log Errors" }], + [".", "SuccessfulProcesses", { stat = "Sum", label = "Successful" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "Custom Metrics: Errors vs Success" + period = 300 + } + width = 12 + height = 6 + x = 12 + y = 6 + }, + { + # Widget for Image Processing Time + type = "metric" + properties = { + metrics = [ + // 1. Namespace, 2. Metric Name, 3. Options + [var.metric_namespace, "ImageProcessingTime", { stat = "Average", label = "Avg Processing Time" }], + ["...", { stat = "Maximum", label = "Max Processing Time" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "Image Processing Time (ms)" + period = 300 + } + width = 12 + height = 6 + x = 0 + y = 12 + }, + { + # Widget for Image Size + type = "metric" + properties = { + metrics = [ + // 1. Namespace, 2. Metric Name, 3. Options + [var.metric_namespace, "ImageSizeBytes", { stat = "Average", label = "Avg Image Size" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "Image Size (Bytes)" + period = 300 + } + width = 12 + height = 6 + x = 12 + y = 12 + }, + { + # Widget for Recent Errors from Log Group + type = "log" + properties = { + query = "SOURCE '${var.log_group_name}'\n| fields @timestamp, @message\n| filter @message like /ERROR/\n| sort @timestamp desc\n| limit 20" + region = var.aws_region + title = "Recent Errors" + } + width = 24 + height = 6 + x = 0 + y = 18 + } + ] + }) +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_metrics/outputs.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_metrics/outputs.tf new file mode 100644 index 000000000..75eba48a2 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_metrics/outputs.tf @@ -0,0 +1,29 @@ +output "dashboard_name" { + description = "Name of the CloudWatch dashboard" + value = var.enable_dashboard ? aws_cloudwatch_dashboard.lambda_monitoring[0].dashboard_name : "" +} + +output "dashboard_arn" { + description = "ARN of the CloudWatch dashboard" + value = var.enable_dashboard ? aws_cloudwatch_dashboard.lambda_monitoring[0].dashboard_arn : "" +} + +output "metric_namespace" { + description = "Custom metrics namespace" + value = var.metric_namespace +} + +output "error_metric_filter_name" { + description = "Name of the error metric filter" + value = aws_cloudwatch_log_metric_filter.lambda_errors.name +} + +output "processing_time_metric_filter_name" { + description = "Name of the processing time metric filter" + value = aws_cloudwatch_log_metric_filter.processing_time.name +} + +output "success_metric_filter_name" { + description = "Name of the success metric filter" + value = aws_cloudwatch_log_metric_filter.successful_processes.name +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_metrics/variables.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_metrics/variables.tf new file mode 100644 index 000000000..ca9f73d48 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/cloudwatch_metrics/variables.tf @@ -0,0 +1,32 @@ +variable "function_name" { + description = "Name of the Lambda function" + type = string +} + +variable "log_group_name" { + description = "Name of the CloudWatch log group" + type = string +} + +variable "metric_namespace" { + description = "CloudWatch custom metrics namespace" + type = string + default = "ImageProcessor/Lambda" +} + +variable "aws_region" { + description = "AWS region" + type = string +} + +variable "enable_dashboard" { + description = "Enable CloudWatch dashboard creation" + type = bool + default = true +} + +variable "tags" { + description = "Common tags for all resources" + type = map(string) + default = {} +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/lambda_function/main.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/lambda_function/main.tf new file mode 100644 index 000000000..3a74bcb0d --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/lambda_function/main.tf @@ -0,0 +1,122 @@ +# ============================================================================ +# LAMBDA FUNCTION MODULE +# Creates Lambda function with IAM roles, policies, and CloudWatch logging +# ============================================================================ + +# IAM role for Lambda function +resource "aws_iam_role" "lambda_role" { + name = "${var.function_name}-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + } + ] + }) + + tags = merge( + var.tags, + { + Name = "${var.function_name}-role" + } + ) +} + +# IAM policy for Lambda function +resource "aws_iam_role_policy" "lambda_policy" { + name = "${var.function_name}-policy" + role = aws_iam_role.lambda_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + Resource = "arn:aws:logs:${var.aws_region}:*:*" + }, + { + Effect = "Allow" + Action = [ + "s3:GetObject", + "s3:GetObjectVersion" + ] + Resource = "${var.upload_bucket_arn}/*" + }, + { + Effect = "Allow" + Action = [ + "s3:PutObject", + "s3:PutObjectAcl" + ] + Resource = "${var.processed_bucket_arn}/*" + }, + { + Effect = "Allow" + Action = [ + "cloudwatch:PutMetricData" + ] + Resource = "*" + } + ] + }) +} + +# CloudWatch Log Group for Lambda +resource "aws_cloudwatch_log_group" "lambda_log_group" { + name = "/aws/lambda/${var.function_name}" + retention_in_days = var.log_retention_days + + tags = merge( + var.tags, + { + Name = "${var.function_name}-logs" + } + ) +} + +# Lambda function +resource "aws_lambda_function" "function" { + filename = var.lambda_zip_path + function_name = var.function_name + role = aws_iam_role.lambda_role.arn + handler = var.handler + source_code_hash = var.source_code_hash + runtime = var.runtime + timeout = var.timeout + memory_size = var.memory_size + + layers = var.lambda_layers + + environment { + variables = merge( + { + PROCESSED_BUCKET = var.processed_bucket_id + LOG_LEVEL = var.log_level + }, + var.environment_variables + ) + } + + tags = merge( + var.tags, + { + Name = var.function_name + } + ) + + depends_on = [ + aws_cloudwatch_log_group.lambda_log_group, + aws_iam_role_policy.lambda_policy + ] +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/lambda_function/outputs.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/lambda_function/outputs.tf new file mode 100644 index 000000000..947963831 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/lambda_function/outputs.tf @@ -0,0 +1,34 @@ +output "function_name" { + description = "Name of the Lambda function" + value = aws_lambda_function.function.function_name +} + +output "function_arn" { + description = "ARN of the Lambda function" + value = aws_lambda_function.function.arn +} + +output "function_invoke_arn" { + description = "Invoke ARN of the Lambda function" + value = aws_lambda_function.function.invoke_arn +} + +output "function_role_arn" { + description = "ARN of the Lambda IAM role" + value = aws_iam_role.lambda_role.arn +} + +output "function_role_name" { + description = "Name of the Lambda IAM role" + value = aws_iam_role.lambda_role.name +} + +output "log_group_name" { + description = "Name of the CloudWatch log group" + value = aws_cloudwatch_log_group.lambda_log_group.name +} + +output "log_group_arn" { + description = "ARN of the CloudWatch log group" + value = aws_cloudwatch_log_group.lambda_log_group.arn +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/lambda_function/variables.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/lambda_function/variables.tf new file mode 100644 index 000000000..47711301d --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/lambda_function/variables.tf @@ -0,0 +1,93 @@ +variable "function_name" { + description = "Name of the Lambda function" + type = string +} + +variable "handler" { + description = "Lambda function handler" + type = string + default = "lambda_function.lambda_handler" +} + +variable "runtime" { + description = "Lambda runtime" + type = string + default = "python3.12" +} + +variable "timeout" { + description = "Lambda function timeout in seconds" + type = number + default = 60 +} + +variable "memory_size" { + description = "Lambda function memory size in MB" + type = number + default = 1024 +} + +variable "lambda_zip_path" { + description = "Path to the Lambda function zip file" + type = string +} + +variable "source_code_hash" { + description = "Base64-encoded SHA256 hash of the Lambda zip" + type = string +} + +variable "lambda_layers" { + description = "List of Lambda layer ARNs" + type = list(string) + default = [] +} + +variable "upload_bucket_arn" { + description = "ARN of the upload S3 bucket" + type = string +} + +variable "upload_bucket_id" { + description = "ID of the upload S3 bucket" + type = string +} + +variable "processed_bucket_arn" { + description = "ARN of the processed S3 bucket" + type = string +} + +variable "processed_bucket_id" { + description = "ID of the processed S3 bucket" + type = string +} + +variable "aws_region" { + description = "AWS region" + type = string +} + +variable "log_retention_days" { + description = "CloudWatch log retention in days" + type = number + default = 7 +} + +variable "log_level" { + description = "Log level for Lambda function (DEBUG, INFO, WARNING, ERROR)" + type = string + default = "INFO" +} + +variable "environment_variables" { + description = "Additional environment variables for Lambda" + type = map(string) + default = {} +} + +variable "tags" { + description = "Common tags for all resources" + type = map(string) + default = {} +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/log_alerts/main.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/log_alerts/main.tf new file mode 100644 index 000000000..458fb74d1 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/log_alerts/main.tf @@ -0,0 +1,241 @@ +# ============================================================================ +# LOG ALERTS MODULE +# Creates log-based metric filters and alarms for specific error patterns +# Documentation +# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_metric_filter +# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm +# ============================================================================ + +# Metric Filter: Timeout Errors +resource "aws_cloudwatch_log_metric_filter" "timeout_errors" { + name = "${var.function_name}-timeout-errors" + log_group_name = var.log_group_name + pattern = "Task timed out" + + metric_transformation { + name = "TimeoutErrors" + namespace = var.metric_namespace + value = "1" + default_value = "0" + } +} + +# Alarm: Timeout Errors +resource "aws_cloudwatch_metric_alarm" "timeout_alarm" { + alarm_name = "${var.function_name}-timeout-errors" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = 1 + metric_name = "TimeoutErrors" + namespace = var.metric_namespace + period = 60 + statistic = "Sum" + threshold = 1 + alarm_description = "Triggers when Lambda function times out" + actions_enabled = true + alarm_actions = [var.log_alerts_topic_arn] + treat_missing_data = "notBreaching" + + tags = merge( + var.tags, + { + Name = "${var.function_name}-timeout-alarm" + Type = "LogBased" + Severity = "Critical" + } + ) +} + +# Metric Filter: Out of Memory Errors +resource "aws_cloudwatch_log_metric_filter" "memory_errors" { + name = "${var.function_name}-memory-errors" + log_group_name = var.log_group_name + pattern = "?\"MemoryError\" ?\"Runtime exited with error: signal: killed\"" + + metric_transformation { + name = "MemoryErrors" + namespace = var.metric_namespace + value = "1" + default_value = "0" + } +} + +# Alarm: Memory Errors +resource "aws_cloudwatch_metric_alarm" "memory_alarm" { + alarm_name = "${var.function_name}-memory-errors" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = 1 + metric_name = "MemoryErrors" + namespace = var.metric_namespace + period = 60 + statistic = "Sum" + threshold = 1 + alarm_description = "Triggers when Lambda runs out of memory" + actions_enabled = true + alarm_actions = [var.log_alerts_topic_arn] + treat_missing_data = "notBreaching" + + tags = merge( + var.tags, + { + Name = "${var.function_name}-memory-alarm" + Type = "LogBased" + Severity = "Critical" + } + ) +} + +# Metric Filter: PIL/Image Processing Errors +resource "aws_cloudwatch_log_metric_filter" "pil_errors" { + name = "${var.function_name}-pil-errors" + log_group_name = var.log_group_name + pattern = "?\"PIL\" ?\"Image processing failed\" ?\"cannot identify image\"" + + metric_transformation { + name = "ImageProcessingErrors" + namespace = var.metric_namespace + value = "1" + default_value = "0" + } +} + +# Alarm: PIL Errors +resource "aws_cloudwatch_metric_alarm" "pil_alarm" { + alarm_name = "${var.function_name}-image-processing-errors" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = 1 + metric_name = "ImageProcessingErrors" + namespace = var.metric_namespace + period = 60 + statistic = "Sum" + threshold = 2 + alarm_description = "Triggers when image processing fails repeatedly" + actions_enabled = true + alarm_actions = [var.log_alerts_topic_arn] + treat_missing_data = "notBreaching" + + tags = merge( + var.tags, + { + Name = "${var.function_name}-pil-alarm" + Type = "LogBased" + Severity = "Warning" + } + ) +} + +# Metric Filter: S3 Permission Errors +resource "aws_cloudwatch_log_metric_filter" "s3_permission_errors" { + name = "${var.function_name}-s3-permission-errors" + log_group_name = var.log_group_name + pattern = "?\"AccessDenied\" ?\"Access Denied\" ?\"403\"" + + metric_transformation { + name = "S3PermissionErrors" + namespace = var.metric_namespace + value = "1" + default_value = "0" + } +} + +# Alarm: S3 Permission Errors +resource "aws_cloudwatch_metric_alarm" "s3_permission_alarm" { + alarm_name = "${var.function_name}-s3-permission-errors" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = 1 + metric_name = "S3PermissionErrors" + namespace = var.metric_namespace + period = 60 + statistic = "Sum" + threshold = 1 + alarm_description = "Triggers when Lambda encounters S3 permission errors" + actions_enabled = true + alarm_actions = [var.log_alerts_topic_arn] + treat_missing_data = "notBreaching" + + tags = merge( + var.tags, + { + Name = "${var.function_name}-s3-permission-alarm" + Type = "LogBased" + Severity = "Critical" + } + ) +} + +# Metric Filter: Critical Application Errors +resource "aws_cloudwatch_log_metric_filter" "critical_errors" { + name = "${var.function_name}-critical-errors" + log_group_name = var.log_group_name + pattern = "[timestamp, request_id, level = CRITICAL*, ...]" + + metric_transformation { + name = "CriticalErrors" + namespace = var.metric_namespace + value = "1" + default_value = "0" + } +} + +# Alarm: Critical Errors +resource "aws_cloudwatch_metric_alarm" "critical_alarm" { + alarm_name = "${var.function_name}-critical-errors" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = 1 + metric_name = "CriticalErrors" + namespace = var.metric_namespace + period = 60 + statistic = "Sum" + threshold = 1 + alarm_description = "Triggers when CRITICAL level errors are logged" + actions_enabled = true + alarm_actions = [var.log_alerts_topic_arn] + treat_missing_data = "notBreaching" + + tags = merge( + var.tags, + { + Name = "${var.function_name}-critical-alarm" + Type = "LogBased" + Severity = "Critical" + } + ) +} + +# Metric Filter: Large Image Warning +resource "aws_cloudwatch_log_metric_filter" "large_images" { + name = "${var.function_name}-large-images" + log_group_name = var.log_group_name + pattern = "\"Large image detected\"" + + metric_transformation { + name = "LargeImageWarnings" + namespace = var.metric_namespace + value = "1" + default_value = "0" + } +} + +# Alarm: Large Images (Performance Warning) +resource "aws_cloudwatch_metric_alarm" "large_image_alarm" { + alarm_name = "${var.function_name}-large-images" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = 1 + metric_name = "LargeImageWarnings" + namespace = var.metric_namespace + period = 300 + statistic = "Sum" + threshold = 5 + alarm_description = "Triggers when multiple large images are processed (performance concern)" + actions_enabled = true + alarm_actions = [var.log_alerts_topic_arn] + treat_missing_data = "notBreaching" + + tags = merge( + var.tags, + { + Name = "${var.function_name}-large-image-alarm" + Type = "LogBased" + Severity = "Info" + } + ) +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/log_alerts/outputs.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/log_alerts/outputs.tf new file mode 100644 index 000000000..c0c08f655 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/log_alerts/outputs.tf @@ -0,0 +1,41 @@ +output "timeout_alarm_arn" { + description = "ARN of the timeout alarm" + value = aws_cloudwatch_metric_alarm.timeout_alarm.arn +} + +output "memory_alarm_arn" { + description = "ARN of the memory alarm" + value = aws_cloudwatch_metric_alarm.memory_alarm.arn +} + +output "pil_alarm_arn" { + description = "ARN of the PIL/image processing alarm" + value = aws_cloudwatch_metric_alarm.pil_alarm.arn +} + +output "s3_permission_alarm_arn" { + description = "ARN of the S3 permission alarm" + value = aws_cloudwatch_metric_alarm.s3_permission_alarm.arn +} + +output "critical_alarm_arn" { + description = "ARN of the critical error alarm" + value = aws_cloudwatch_metric_alarm.critical_alarm.arn +} + +output "large_image_alarm_arn" { + description = "ARN of the large image warning alarm" + value = aws_cloudwatch_metric_alarm.large_image_alarm.arn +} + +output "all_log_alarm_names" { + description = "List of all log-based alarm names" + value = [ + aws_cloudwatch_metric_alarm.timeout_alarm.alarm_name, + aws_cloudwatch_metric_alarm.memory_alarm.alarm_name, + aws_cloudwatch_metric_alarm.pil_alarm.alarm_name, + aws_cloudwatch_metric_alarm.s3_permission_alarm.alarm_name, + aws_cloudwatch_metric_alarm.critical_alarm.alarm_name, + aws_cloudwatch_metric_alarm.large_image_alarm.alarm_name + ] +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/log_alerts/variables.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/log_alerts/variables.tf new file mode 100644 index 000000000..7f959cd9d --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/log_alerts/variables.tf @@ -0,0 +1,26 @@ +variable "function_name" { + description = "Name of the Lambda function" + type = string +} + +variable "log_group_name" { + description = "Name of the CloudWatch log group" + type = string +} + +variable "log_alerts_topic_arn" { + description = "ARN of the SNS topic for log-based alerts" + type = string +} + +variable "metric_namespace" { + description = "CloudWatch custom metrics namespace" + type = string + default = "ImageProcessor/Lambda" +} + +variable "tags" { + description = "Common tags for all resources" + type = map(string) + default = {} +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/s3_buckets/main.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/s3_buckets/main.tf new file mode 100644 index 000000000..30be9bffe --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/s3_buckets/main.tf @@ -0,0 +1,86 @@ +# ============================================================================ +# S3 BUCKETS MODULE +# Creates upload (source) and processed (destination) buckets with security +# ============================================================================ + +# S3 Bucket for uploading original images (SOURCE) +resource "aws_s3_bucket" "upload_bucket" { + bucket = var.upload_bucket_name + + tags = merge( + var.tags, + { + Name = var.upload_bucket_name + Purpose = "Image Upload Source" + Environment = var.environment + } + ) +} + +resource "aws_s3_bucket_versioning" "upload_bucket" { + bucket = aws_s3_bucket.upload_bucket.id + + versioning_configuration { + status = var.enable_versioning ? "Enabled" : "Suspended" + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "upload_bucket" { + bucket = aws_s3_bucket.upload_bucket.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} + +resource "aws_s3_bucket_public_access_block" "upload_bucket" { + bucket = aws_s3_bucket.upload_bucket.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +# S3 Bucket for processed images (DESTINATION) +resource "aws_s3_bucket" "processed_bucket" { + bucket = var.processed_bucket_name + + tags = merge( + var.tags, + { + Name = var.processed_bucket_name + Purpose = "Processed Images Destination" + Environment = var.environment + } + ) +} + +resource "aws_s3_bucket_versioning" "processed_bucket" { + bucket = aws_s3_bucket.processed_bucket.id + + versioning_configuration { + status = var.enable_versioning ? "Enabled" : "Suspended" + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "processed_bucket" { + bucket = aws_s3_bucket.processed_bucket.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} + +resource "aws_s3_bucket_public_access_block" "processed_bucket" { + bucket = aws_s3_bucket.processed_bucket.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/s3_buckets/outputs.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/s3_buckets/outputs.tf new file mode 100644 index 000000000..e8be07f2f --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/s3_buckets/outputs.tf @@ -0,0 +1,29 @@ +output "upload_bucket_id" { + description = "ID of the upload S3 bucket" + value = aws_s3_bucket.upload_bucket.id +} + +output "upload_bucket_arn" { + description = "ARN of the upload S3 bucket" + value = aws_s3_bucket.upload_bucket.arn +} + +output "processed_bucket_id" { + description = "ID of the processed S3 bucket" + value = aws_s3_bucket.processed_bucket.id +} + +output "processed_bucket_arn" { + description = "ARN of the processed S3 bucket" + value = aws_s3_bucket.processed_bucket.arn +} + +output "upload_bucket_domain_name" { + description = "Domain name of the upload bucket" + value = aws_s3_bucket.upload_bucket.bucket_domain_name +} + +output "processed_bucket_domain_name" { + description = "Domain name of the processed bucket" + value = aws_s3_bucket.processed_bucket.bucket_domain_name +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/s3_buckets/variables.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/s3_buckets/variables.tf new file mode 100644 index 000000000..e01a60c74 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/s3_buckets/variables.tf @@ -0,0 +1,26 @@ +variable "upload_bucket_name" { + description = "Name of the S3 bucket for uploading images" + type = string +} + +variable "processed_bucket_name" { + description = "Name of the S3 bucket for processed images" + type = string +} + +variable "environment" { + description = "Environment name (dev, staging, prod)" + type = string +} + +variable "enable_versioning" { + description = "Enable versioning on S3 buckets" + type = bool + default = true +} + +variable "tags" { + description = "Common tags for all resources" + type = map(string) + default = {} +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/sns_notifications/main.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/sns_notifications/main.tf new file mode 100644 index 000000000..303c7c615 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/sns_notifications/main.tf @@ -0,0 +1,139 @@ +# ============================================================================ +# SNS NOTIFICATIONS MODULE +# Creates SNS topics and subscriptions for different alert types +# ============================================================================ + +# SNS Topic for Critical Alerts (Errors, Failures) +resource "aws_sns_topic" "critical_alerts" { + name = "${var.project_name}-${var.environment}-critical-alerts" + display_name = "Critical Lambda Alerts - ${var.project_name}" + + tags = merge( + var.tags, + { + Name = "${var.project_name}-critical-alerts" + AlertType = "Critical" + } + ) +} + +# SNS Topic for Performance Alerts (Duration, Memory) +resource "aws_sns_topic" "performance_alerts" { + name = "${var.project_name}-${var.environment}-performance-alerts" + display_name = "Performance Alerts - ${var.project_name}" + + tags = merge( + var.tags, + { + Name = "${var.project_name}-performance-alerts" + AlertType = "Performance" + } + ) +} + +# SNS Topic for Log-based Alerts +resource "aws_sns_topic" "log_alerts" { + name = "${var.project_name}-${var.environment}-log-alerts" + display_name = "Log Pattern Alerts - ${var.project_name}" + + tags = merge( + var.tags, + { + Name = "${var.project_name}-log-alerts" + AlertType = "Logs" + } + ) +} + +# Email subscription for Critical Alerts +resource "aws_sns_topic_subscription" "critical_email" { + count = var.critical_alert_email != "" ? 1 : 0 + topic_arn = aws_sns_topic.critical_alerts.arn + protocol = "email" + endpoint = var.critical_alert_email +} + +# Email subscription for Performance Alerts +resource "aws_sns_topic_subscription" "performance_email" { + count = var.performance_alert_email != "" ? 1 : 0 + topic_arn = aws_sns_topic.performance_alerts.arn + protocol = "email" + endpoint = var.performance_alert_email +} + +# Email subscription for Log Alerts +resource "aws_sns_topic_subscription" "log_email" { + count = var.log_alert_email != "" ? 1 : 0 + topic_arn = aws_sns_topic.log_alerts.arn + protocol = "email" + endpoint = var.log_alert_email +} + +# Optional SMS subscription for Critical Alerts +resource "aws_sns_topic_subscription" "critical_sms" { + count = var.critical_alert_sms != "" ? 1 : 0 + topic_arn = aws_sns_topic.critical_alerts.arn + protocol = "sms" + endpoint = var.critical_alert_sms +} + +# SNS Topic Policy (allows CloudWatch to publish) +resource "aws_sns_topic_policy" "critical_alerts_policy" { + arn = aws_sns_topic.critical_alerts.arn + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" # 1. Allow access + Principal = { + Service = "cloudwatch.amazonaws.com" # 2. WHO: The CloudWatch Service + } + Action = [ + "SNS:Publish" # 3. WHAT: Permission to publish messages. + ] + Resource = aws_sns_topic.critical_alerts.arn # 4. TO WHICH RESOURCE: This SNS Topic + } + ] + }) +} + +resource "aws_sns_topic_policy" "performance_alerts_policy" { + arn = aws_sns_topic.performance_alerts.arn + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "cloudwatch.amazonaws.com" + } + Action = [ + "SNS:Publish" + ] + Resource = aws_sns_topic.performance_alerts.arn + } + ] + }) +} + +resource "aws_sns_topic_policy" "log_alerts_policy" { + arn = aws_sns_topic.log_alerts.arn + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "cloudwatch.amazonaws.com" + } + Action = [ + "SNS:Publish" + ] + Resource = aws_sns_topic.log_alerts.arn + } + ] + }) +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/sns_notifications/outputs.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/sns_notifications/outputs.tf new file mode 100644 index 000000000..97e9c5ffa --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/sns_notifications/outputs.tf @@ -0,0 +1,29 @@ +output "critical_alerts_topic_arn" { + description = "ARN of the critical alerts SNS topic" + value = aws_sns_topic.critical_alerts.arn +} + +output "performance_alerts_topic_arn" { + description = "ARN of the performance alerts SNS topic" + value = aws_sns_topic.performance_alerts.arn +} + +output "log_alerts_topic_arn" { + description = "ARN of the log alerts SNS topic" + value = aws_sns_topic.log_alerts.arn +} + +output "critical_alerts_topic_name" { + description = "Name of the critical alerts SNS topic" + value = aws_sns_topic.critical_alerts.name +} + +output "performance_alerts_topic_name" { + description = "Name of the performance alerts SNS topic" + value = aws_sns_topic.performance_alerts.name +} + +output "log_alerts_topic_name" { + description = "Name of the log alerts SNS topic" + value = aws_sns_topic.log_alerts.name +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/sns_notifications/variables.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/sns_notifications/variables.tf new file mode 100644 index 000000000..5a3c3af81 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/modules/sns_notifications/variables.tf @@ -0,0 +1,39 @@ +variable "project_name" { + description = "Project name for resource naming" + type = string +} + +variable "environment" { + description = "Environment name (dev, staging, prod)" + type = string +} + +variable "critical_alert_email" { + description = "Email address for critical alerts (errors, failures)" + type = string + default = "" +} + +variable "performance_alert_email" { + description = "Email address for performance alerts (duration, memory)" + type = string + default = "" +} + +variable "log_alert_email" { + description = "Email address for log-based alerts" + type = string + default = "" +} + +variable "critical_alert_sms" { + description = "Phone number for critical SMS alerts (format: +1234567890)" + type = string + default = "" +} + +variable "tags" { + description = "Common tags for all resources" + type = map(string) + default = {} +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/outputs.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/outputs.tf new file mode 100644 index 000000000..9d4141791 --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/outputs.tf @@ -0,0 +1,137 @@ +# ============================================================================ +# S3 BUCKET OUTPUTS +# ============================================================================ + +output "upload_bucket_name" { + description = "S3 bucket for uploading images (SOURCE)" + value = module.s3_buckets.upload_bucket_id +} + +output "processed_bucket_name" { + description = "S3 bucket for processed images (DESTINATION)" + value = module.s3_buckets.processed_bucket_id +} + +output "upload_bucket_arn" { + description = "ARN of the upload bucket" + value = module.s3_buckets.upload_bucket_arn +} + +output "processed_bucket_arn" { + description = "ARN of the processed bucket" + value = module.s3_buckets.processed_bucket_arn +} + +# ============================================================================ +# LAMBDA FUNCTION OUTPUTS +# ============================================================================ + +output "lambda_function_name" { + description = "Name of the Lambda function" + value = module.lambda_function.function_name +} + +output "lambda_function_arn" { + description = "ARN of the Lambda function" + value = module.lambda_function.function_arn +} + +output "lambda_log_group_name" { + description = "CloudWatch Log Group name for Lambda" + value = module.lambda_function.log_group_name +} + +# ============================================================================ +# SNS TOPICS OUTPUTS +# ============================================================================ + +output "critical_alerts_topic_arn" { + description = "ARN of the critical alerts SNS topic" + value = module.sns_notifications.critical_alerts_topic_arn +} + +output "performance_alerts_topic_arn" { + description = "ARN of the performance alerts SNS topic" + value = module.sns_notifications.performance_alerts_topic_arn +} + +output "log_alerts_topic_arn" { + description = "ARN of the log alerts SNS topic" + value = module.sns_notifications.log_alerts_topic_arn +} + +# ============================================================================ +# CLOUDWATCH MONITORING OUTPUTS +# ============================================================================ + +output "cloudwatch_dashboard_name" { + description = "Name of the CloudWatch dashboard" + value = module.cloudwatch_metrics.dashboard_name +} + +output "cloudwatch_dashboard_url" { + description = "URL to access the CloudWatch dashboard" + value = var.enable_cloudwatch_dashboard ? "https://console.aws.amazon.com/cloudwatch/home?region=${var.aws_region}#dashboards:name=${module.cloudwatch_metrics.dashboard_name}" : "Dashboard not enabled" +} + +output "metric_namespace" { + description = "Custom CloudWatch metrics namespace" + value = var.metric_namespace +} + +# ============================================================================ +# ALARM OUTPUTS +# ============================================================================ + +output "cloudwatch_alarms" { + description = "List of all CloudWatch alarm names" + value = { + error_alarm = module.cloudwatch_alarms.all_alarm_names[0] + duration_alarm = module.cloudwatch_alarms.all_alarm_names[1] + throttle_alarm = module.cloudwatch_alarms.all_alarm_names[2] + concurrency_alarm = module.cloudwatch_alarms.all_alarm_names[3] + log_error_alarm = module.cloudwatch_alarms.all_alarm_names[4] + success_rate_alarm = module.cloudwatch_alarms.all_alarm_names[5] + } +} + +output "log_based_alarms" { + description = "List of all log-based alarm names" + value = { + timeout_alarm = module.log_alerts.all_log_alarm_names[0] + memory_alarm = module.log_alerts.all_log_alarm_names[1] + pil_alarm = module.log_alerts.all_log_alarm_names[2] + s3_permission_alarm = module.log_alerts.all_log_alarm_names[3] + critical_alarm = module.log_alerts.all_log_alarm_names[4] + large_image_alarm = module.log_alerts.all_log_alarm_names[5] + } +} + +# ============================================================================ +# USAGE INFORMATION +# ============================================================================ + +output "aws_region" { + description = "AWS Region where resources are deployed" + value = var.aws_region +} + +output "upload_command_example" { + description = "Example AWS CLI command to upload an image" + value = "aws s3 cp your-image.jpg s3://${module.s3_buckets.upload_bucket_id}/" +} + +output "view_logs_command" { + description = "AWS CLI command to view Lambda logs" + value = "aws logs tail ${module.lambda_function.log_group_name} --follow" +} + +output "test_alarm_command" { + description = "AWS CLI command to test alarms by setting alarm state" + value = "aws cloudwatch set-alarm-state --alarm-name ${module.cloudwatch_alarms.all_alarm_names[0]} --state-value ALARM --state-reason 'Testing alarm'" +} + +output "sns_subscription_note" { + description = "Important note about SNS email subscriptions" + value = var.alert_email != "" ? "โš ๏ธ IMPORTANT: Check your email (${var.alert_email}) and confirm SNS subscription(s) to receive alerts!" : "No email configured for alerts" +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/provider.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/provider.tf new file mode 100644 index 000000000..c1894764a --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/provider.tf @@ -0,0 +1,31 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + archive = { + source = "hashicorp/archive" + version = "~> 2.4" + } + random = { + source = "hashicorp/random" + version = "~> 3.6" + } + } +} + +provider "aws" { + region = var.aws_region + profile = "468284643560" + + default_tags { + tags = { + Project = "ImageProcessingApp" + Environment = var.environment + ManagedBy = "Terraform" + } + } +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/variables.tf b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/variables.tf new file mode 100644 index 000000000..35c930f8f --- /dev/null +++ b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/terraform/variables.tf @@ -0,0 +1,138 @@ +# ============================================================================ +# GENERAL CONFIGURATION +# ============================================================================ + +variable "aws_region" { + description = "AWS region for resources" + type = string + default = "us-east-1" +} + +variable "environment" { + description = "Environment name (dev, staging, prod)" + type = string + default = "dev" +} + +variable "project_name" { + description = "Project name used for resource naming" + type = string + default = "image-processor" +} + +# ============================================================================ +# LAMBDA CONFIGURATION +# ============================================================================ + +variable "lambda_runtime" { + description = "Lambda runtime version" + type = string + default = "python3.12" +} + +variable "lambda_timeout" { + description = "Lambda function timeout in seconds" + type = number + default = 60 +} + +variable "lambda_memory_size" { + description = "Lambda function memory size in MB" + type = number + default = 1024 +} + +variable "log_level" { + description = "Lambda function log level (DEBUG, INFO, WARNING, ERROR)" + type = string + default = "INFO" +} + +variable "log_retention_days" { + description = "CloudWatch Logs retention period in days" + type = number + default = 7 +} + +# ============================================================================ +# S3 CONFIGURATION +# ============================================================================ + +variable "enable_s3_versioning" { + description = "Enable versioning on S3 buckets" + type = bool + default = true +} + +# ============================================================================ +# MONITORING & ALERTING CONFIGURATION +# ============================================================================ + +variable "alert_email" { + description = "Email address for receiving CloudWatch alerts" + type = string + default = "" + + validation { + condition = can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", var.alert_email)) || var.alert_email == "" + error_message = "Must be a valid email address or empty string." + } +} + +variable "alert_sms" { + description = "Phone number for critical SMS alerts (format: +1234567890)" + type = string + default = "" +} + +variable "metric_namespace" { + description = "CloudWatch custom metrics namespace" + type = string + default = "ImageProcessor/Lambda" +} + +variable "enable_cloudwatch_dashboard" { + description = "Enable CloudWatch dashboard creation" + type = bool + default = true +} + +# ============================================================================ +# ALARM THRESHOLDS +# ============================================================================ + +variable "error_threshold" { + description = "Number of Lambda errors to trigger critical alarm" + type = number + default = 3 +} + +variable "duration_threshold_ms" { + description = "Lambda duration threshold in milliseconds (75% of timeout recommended)" + type = number + default = 45000 +} + +variable "throttle_threshold" { + description = "Number of throttles to trigger alarm" + type = number + default = 5 +} + +variable "concurrent_executions_threshold" { + description = "Concurrent executions threshold for performance warning" + type = number + default = 50 +} + +variable "log_error_threshold" { + description = "Number of log errors to trigger alarm" + type = number + default = 1 +} + +variable "enable_no_invocation_alarm" { + description = "Enable alarm for detecting when Lambda has no invocations" + type = bool + default = false +} diff --git a/AWS-Tf-Handson/Day23/aws-lamda-monitoring/test-image.jpg b/AWS-Tf-Handson/Day23/aws-lamda-monitoring/test-image.jpg new file mode 100644 index 000000000..e69de29bb diff --git a/AWS-Tf-Handson/Day23/test-folder/README.md b/AWS-Tf-Handson/Day23/test-folder/README.md new file mode 100644 index 000000000..ad001527d --- /dev/null +++ b/AWS-Tf-Handson/Day23/test-folder/README.md @@ -0,0 +1,11 @@ +# Directory Structure Placeholder + +This file serves as a placeholder to fix a visual issue in your file explorer or IDE (like VS Code). + +## The Problem +When a folder (e.g., `Day23`) contains only one subfolder (e.g., `aws-lambda-monitoring`), many code editors "flatten" the view to save space, displaying it as `Day23/aws-lambda-monitoring` on a single line. This can make it hard to right-click specifically on `Day23` or just looks different from what you expect. + +## The Solution +By creating the `test` folder alongside `aws-lambda-monitoring`, `Day23` now has two children. This forces the editor to display `Day23` as a parent folder that expands to show both subfolders separately. + +In short, this file exists solely to keep your folder structure expanded and organized in your view. \ No newline at end of file diff --git a/AWS-Tf-Handson/Day24/LOGICAL_FLOW.md b/AWS-Tf-Handson/Day24/LOGICAL_FLOW.md new file mode 100644 index 000000000..020b27006 --- /dev/null +++ b/AWS-Tf-Handson/Day24/LOGICAL_FLOW.md @@ -0,0 +1,44 @@ +# Logical Flow of Infrastructure Code + +To understand how this infrastructure is built, follow this sequence of files. This order respects the dependencies: you can't build servers without a network, and you can't load balance without servers. + +## 1. The Foundation: `main.tf` & `vpc.tf` +**Start here.** Before you can build servers, you need a network. +* **`main.tf`**: Configures the AWS Provider and sets global tags. +* **`vpc.tf`**: Builds the "Virtual Data Center". + * **VPC**: The isolated network container. + * **Public Subnets**: Where the Load Balancer and NAT Gateways live (they need to talk to the internet). + * **Private Subnets**: Where your actual Application Servers will live (hidden from the internet for security). + * **NAT Gateways**: Allow your private servers to download updates without letting hackers in. + +## 2. The Firewall: `security_groups.tf` +**Read this next.** This defines *who* is allowed to talk to *whom*. +* **`alb_sg`**: The "Doorman". It allows HTTP/HTTPS traffic from **Anywhere** (`0.0.0.0/0`). +* **`app_sg`**: The "VIP Section". It **only** allows traffic coming from the `alb_sg`. This is crucialโ€”it prevents anyone from bypassing the Load Balancer to hit your servers directly. + +## 3. The Front Door: `alb.tf` +**Now we connect the Network and Security.** +* **`aws_lb`**: The Load Balancer. It sits in the **Public Subnets** (from `vpc.tf`) and wears the **`alb_sg`** security badge (from `security_groups.tf`). +* **`aws_lb_target_group`**: The "Waiting Room". The Load Balancer sends valid requests here. +* **`aws_lb_listener`**: Listens on Port 80 and points traffic to that Target Group. + +## 4. The Application Fleet: `asg.tf` +**This is where the actual servers are created.** +* **`aws_launch_template`**: The "Blueprint". It says "Create servers using this AMI, this Instance Type, and attach the **`app_sg`** security group." +* **`aws_autoscaling_group`**: The "Manager". + * It launches servers into the **Private Subnets** (from `vpc.tf`). + * It registers them with the **Target Group** (from `alb.tf`) so they start receiving traffic. + * It watches **CloudWatch Alarms** (High/Low CPU) to decide if it should add or remove servers. + +## 5. The Storage: `s3.tf` +**The Sidecar.** +* This creates a secure bucket for your application to store files. While it doesn't "depend" on the networking files like the others do, your application running on the EC2 instances will likely use IAM roles to read/write to this bucket. + +--- + +## Summary of the Data Flow +1. **User** -> hits **Load Balancer** (in Public Subnet, allowed by `alb_sg`). +2. **Load Balancer** -> forwards to **Target Group**. +3. **Target Group** -> routes to an **EC2 Instance** (in Private Subnet, allowed by `app_sg`). +4. **EC2 Instance** -> processes request (and maybe saves a file to **S3**). +5. **EC2 Instance** -> sends response back through the Load Balancer to the User. diff --git a/AWS-Tf-Handson/Day24/README.md b/AWS-Tf-Handson/Day24/README.md new file mode 100644 index 000000000..6f57ccb81 --- /dev/null +++ b/AWS-Tf-Handson/Day24/README.md @@ -0,0 +1,98 @@ +# Day 24: High Available/Scalable Infrastructure Deployment (Mini Project 10) + +## ๐Ÿ“‹ Project Overview + +Production-grade, highly available Django application deployed on AWS using Terraform. Infrastructure spans multiple Availability Zones with automatic scaling. + +### ๐ŸŽฏ Key Features + +- **High Availability**: Multi-AZ deployment, no single point of failure +- **Auto Scaling**: Dynamic scaling (1-5 instances) based on CPU +- **Security**: Private instances with ALB-only access +- **Load Balancing**: Application Load Balancer distributes traffic +- **Containerized**: Django app running in Docker +- **Infrastructure as Code**: Fully automated with Terraform + +## ๐Ÿ—๏ธ Architecture + +``` +Internet โ†’ ALB (Public) โ†’ EC2 Instances (Private) โ†’ NAT Gateways โ†’ Internet + โ†“ + Django Docker App +``` + +### Components + +- **VPC**: `10.0.0.0/16` across 2 AZs (us-east-1a, us-east-1b) +- **Public Subnets**: `10.0.1.0/24`, `10.0.2.0/24` +- **Private Subnets**: `10.0.11.0/24`, `10.0.12.0/24` +- **NAT Gateways**: 2 (one per AZ for HA) +- **ALB**: Internet-facing, spans both public subnets +- **ASG**: Min:1, Desired:2, Max:5 instances +- **Container**: `itsbaivab/django-app` (Port 8000โ†’80) + +## ๐Ÿ“ Code Structure + +``` +code/ +โ”œโ”€โ”€ main.tf # Provider & Terraform configuration +โ”œโ”€โ”€ variables.tf # Input variables +โ”œโ”€โ”€ outputs.tf # Output values (ALB DNS, NAT IPs, etc.) +โ”œโ”€โ”€ vpc.tf # VPC, Subnets, NAT Gateways, Route Tables +โ”œโ”€โ”€ security_groups.tf # Security group definitions +โ”œโ”€โ”€ alb.tf # Load Balancer, Target Group, Listener +โ”œโ”€โ”€ asg.tf # Launch Template, ASG, Scaling Policies +โ”œโ”€โ”€ s3.tf # S3 bucket (optional) +โ””โ”€โ”€ scripts/ + โ””โ”€โ”€ user_data.sh # EC2 initialization script +``` + +## ๐Ÿš€ Quick Start + +### Prerequisites + +- AWS Account with appropriate permissions +- Terraform installed (v1.0+) +- AWS CLI configured + +### Deploy + +```bash +cd code/ +terraform init +terraform plan +terraform apply -auto-approve +``` + +Deployment takes ~5-8 minutes. + +### Access Application + +```bash +terraform output load_balancer_dns +# Visit the DNS in your browser +``` + +### Cleanup + +```bash +terraform destroy -auto-approve +``` + +## ๐Ÿ’ฐ Cost Estimate + +~$103-108/month (us-east-1): +- EC2 t2.micro (2 avg): ~$17 +- ALB: $16.20 +- NAT Gateway (2): $64.80 +- Data Transfer: ~$5-10 + +## ๐Ÿ“š Resources + +- [Detailed Demo Guide](./DEMO_GUIDE.md) - Complete walkthrough with troubleshooting +- [AWS Well-Architected Framework](https://aws.amazon.com/architecture/well-architected/) +- [Terraform AWS Provider Docs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) + +--- + +**Status**: โœ… Production-Ready HA Architecture | **Updated**: December 1, 2025 diff --git a/AWS-Tf-Handson/Day24/code/alb.tf b/AWS-Tf-Handson/Day24/code/alb.tf new file mode 100644 index 000000000..2019d4b13 --- /dev/null +++ b/AWS-Tf-Handson/Day24/code/alb.tf @@ -0,0 +1,44 @@ +resource "aws_lb" "app_lb" { + name = "app-load-balancer" + internal = false + load_balancer_type = "application" + security_groups = [aws_security_group.alb_sg.id] + subnets = aws_subnet.public[*].id + + enable_deletion_protection = false + idle_timeout = 60 + + tags = { + Name = "app-load-balancer" + } +} + +resource "aws_lb_target_group" "app_tg" { + name = "app-target-group" + port = 80 + protocol = "HTTP" + vpc_id = aws_vpc.main.id + + health_check { + path = "/" + interval = 30 + timeout = 5 + healthy_threshold = 2 + unhealthy_threshold = 2 + } + + tags = { + Name = "app-target-group" + } +} + +resource "aws_lb_listener" "http_listener" { + load_balancer_arn = aws_lb.app_lb.arn + port = 80 + protocol = "HTTP" + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.app_tg.arn + } +} diff --git a/AWS-Tf-Handson/Day24/code/asg.tf b/AWS-Tf-Handson/Day24/code/asg.tf new file mode 100644 index 000000000..8df0e7c3f --- /dev/null +++ b/AWS-Tf-Handson/Day24/code/asg.tf @@ -0,0 +1,119 @@ +resource "aws_launch_template" "app" { + name_prefix = "app-launch-template-" + image_id = var.ami_id + instance_type = var.instance_type + + vpc_security_group_ids = [ + aws_security_group.app_sg.id, + aws_security_group.allow_ssh.id + ] + + user_data = filebase64("${path.module}/scripts/user_data.sh") + + metadata_options { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 1 + } + + monitoring { + enabled = true + } + + tag_specifications { + resource_type = "instance" + tags = { + Name = "app-instance" + } + } +} + +resource "aws_autoscaling_group" "app_asg" { + name = "app-asg" + min_size = var.min_size + max_size = var.max_size + desired_capacity = var.desired_capacity + vpc_zone_identifier = aws_subnet.private[*].id + target_group_arns = [aws_lb_target_group.app_tg.arn] + health_check_type = "ELB" + health_check_grace_period = 300 + + launch_template { + id = aws_launch_template.app.id + version = "$Latest" + } + + tag { + key = "Name" + value = "app-instance" + propagate_at_launch = true + } +} + +# Simple Scaling Policy - Scale Out +resource "aws_autoscaling_policy" "scale_out" { + name = "scale-out" + scaling_adjustment = 1 + adjustment_type = "ChangeInCapacity" + cooldown = 300 + autoscaling_group_name = aws_autoscaling_group.app_asg.name +} + +# Simple Scaling Policy - Scale In +resource "aws_autoscaling_policy" "scale_in" { + name = "scale-in" + scaling_adjustment = -1 + adjustment_type = "ChangeInCapacity" + cooldown = 300 + autoscaling_group_name = aws_autoscaling_group.app_asg.name +} + +# Target Tracking Scaling Policy - CPU Utilization +resource "aws_autoscaling_policy" "target_tracking" { + name = "target-tracking-policy" + autoscaling_group_name = aws_autoscaling_group.app_asg.name + policy_type = "TargetTrackingScaling" + + target_tracking_configuration { + predefined_metric_specification { + predefined_metric_type = "ASGAverageCPUUtilization" + } + target_value = 70.0 + } +} + +# CloudWatch Alarm for High CPU +resource "aws_cloudwatch_metric_alarm" "high_cpu" { + alarm_name = "high-cpu-utilization" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = "2" + metric_name = "CPUUtilization" + namespace = "AWS/EC2" + period = "120" + statistic = "Average" + threshold = "80" + alarm_description = "This metric monitors ec2 cpu utilization" + alarm_actions = [aws_autoscaling_policy.scale_out.arn] + + dimensions = { + AutoScalingGroupName = aws_autoscaling_group.app_asg.name + } +} + +# CloudWatch Alarm for Low CPU +resource "aws_cloudwatch_metric_alarm" "low_cpu" { + alarm_name = "low-cpu-utilization" + comparison_operator = "LessThanThreshold" + evaluation_periods = "2" + metric_name = "CPUUtilization" + namespace = "AWS/EC2" + period = "120" + statistic = "Average" + threshold = "20" + alarm_description = "This metric monitors ec2 cpu utilization" + alarm_actions = [aws_autoscaling_policy.scale_in.arn] + + dimensions = { + AutoScalingGroupName = aws_autoscaling_group.app_asg.name + } +} diff --git a/AWS-Tf-Handson/Day24/code/main.tf b/AWS-Tf-Handson/Day24/code/main.tf new file mode 100644 index 000000000..5e15acf02 --- /dev/null +++ b/AWS-Tf-Handson/Day24/code/main.tf @@ -0,0 +1,26 @@ +terraform { + required_version = ">= 1.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + } +} + +provider "aws" { + region = var.region + profile = "468284643560" + + default_tags { + tags = { + Environment = var.environment + Project = "AWS-Production-Infrastructure" + ManagedBy = "Terraform" + } + } +} diff --git a/AWS-Tf-Handson/Day24/code/outputs.tf b/AWS-Tf-Handson/Day24/code/outputs.tf new file mode 100644 index 000000000..f5437d9a9 --- /dev/null +++ b/AWS-Tf-Handson/Day24/code/outputs.tf @@ -0,0 +1,44 @@ +output "vpc_id" { + description = "The ID of the VPC" + value = aws_vpc.main.id +} + +output "public_subnet_ids" { + description = "List of public subnet IDs" + value = aws_subnet.public[*].id +} + +output "private_subnet_ids" { + description = "List of private subnet IDs" + value = aws_subnet.private[*].id +} + +output "load_balancer_dns" { + description = "DNS name of the Application Load Balancer" + value = aws_lb.app_lb.dns_name +} + +output "load_balancer_arn" { + description = "ARN of the Application Load Balancer" + value = aws_lb.app_lb.arn +} + +output "s3_bucket_name" { + description = "Name of the S3 bucket" + value = aws_s3_bucket.my_bucket.bucket +} + +output "s3_bucket_arn" { + description = "ARN of the S3 bucket" + value = aws_s3_bucket.my_bucket.arn +} + +output "autoscaling_group_name" { + description = "Name of the Auto Scaling Group" + value = aws_autoscaling_group.app_asg.name +} + +output "nat_gateway_ips" { + description = "Elastic IPs of the NAT Gateways" + value = aws_eip.main[*].public_ip +} diff --git a/AWS-Tf-Handson/Day24/code/s3.tf b/AWS-Tf-Handson/Day24/code/s3.tf new file mode 100644 index 000000000..d32ad124b --- /dev/null +++ b/AWS-Tf-Handson/Day24/code/s3.tf @@ -0,0 +1,54 @@ +resource "random_id" "bucket_suffix" { + byte_length = 4 +} + +resource "aws_s3_bucket" "my_bucket" { + bucket = "terraform-day15-prod-bucket-${random_id.bucket_suffix.hex}" + + tags = { + Name = "MyBucket" + Environment = "Production" + } +} + +resource "aws_s3_bucket_versioning" "my_bucket" { + bucket = aws_s3_bucket.my_bucket.id + + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_acl" "my_bucket" { + bucket = aws_s3_bucket.my_bucket.id + acl = "private" + + depends_on = [aws_s3_bucket_ownership_controls.my_bucket] +} + +resource "aws_s3_bucket_ownership_controls" "my_bucket" { + bucket = aws_s3_bucket.my_bucket.id + + rule { + object_ownership = "BucketOwnerPreferred" + } +} + +resource "aws_s3_bucket_public_access_block" "my_bucket" { + bucket = aws_s3_bucket.my_bucket.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "my_bucket" { + bucket = aws_s3_bucket.my_bucket.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} diff --git a/AWS-Tf-Handson/Day24/code/scripts/user_data.sh b/AWS-Tf-Handson/Day24/code/scripts/user_data.sh new file mode 100644 index 000000000..4acf4c869 --- /dev/null +++ b/AWS-Tf-Handson/Day24/code/scripts/user_data.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Update the package repository +apt-get update -y + +# Install Docker +apt-get install -y docker.io + +# Start and enable Docker service +systemctl start docker +systemctl enable docker + +# Add ubuntu user to docker group (optional, but good practice) +usermod -aG docker ubuntu + +# Pull and run the Django application container +# Mapping Host Port 80 (ALB traffic) to Container Port 8000 (Django default) +docker run -d \ + --name django-app \ + --restart always \ + -p 80:8000 \ + itsbaivab/django-app \ No newline at end of file diff --git a/AWS-Tf-Handson/Day24/code/security_groups.tf b/AWS-Tf-Handson/Day24/code/security_groups.tf new file mode 100644 index 000000000..1b064cb80 --- /dev/null +++ b/AWS-Tf-Handson/Day24/code/security_groups.tf @@ -0,0 +1,145 @@ +# Security Group for Application Load Balancer +resource "aws_security_group" "alb_sg" { + name = "alb-security-group" + description = "Security group for Application Load Balancer" + vpc_id = aws_vpc.main.id + + ingress { + description = "HTTP from Internet" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "HTTPS from Internet" + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + description = "Allow all outbound" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "alb-security-group" + } +} + +# Security Group for EC2 Instances (App Tier) +resource "aws_security_group" "app_sg" { + name = "app-security-group" + description = "Security group for application instances - only allow traffic from ALB" + vpc_id = aws_vpc.main.id + + ingress { + description = "HTTP from ALB only" + from_port = 80 + to_port = 80 + protocol = "tcp" + security_groups = [aws_security_group.alb_sg.id] + } + + ingress { + description = "HTTPS from ALB only" + from_port = 443 + to_port = 443 + protocol = "tcp" + security_groups = [aws_security_group.alb_sg.id] + } + + egress { + description = "Allow all outbound (for updates via NAT)" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "app-security-group" + } +} + +# Security Group for SSH access (optional - restrict to your IP) +resource "aws_security_group" "allow_ssh" { + name = "allow-ssh" + description = "Allow SSH access - RESTRICT THIS TO YOUR IP IN PRODUCTION" + vpc_id = aws_vpc.main.id + + ingress { + description = "SSH from anywhere - CHANGE THIS" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] # TODO: Change to your IP: ["YOUR_IP/32"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "ssh-security-group" + } +} + +# Deprecated - kept for reference, use app_sg and alb_sg instead +resource "aws_security_group" "allow_http" { + name = "allow_http" + description = "Allow HTTP traffic" + vpc_id = aws_vpc.main.id + + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "allow-http-deprecated" + } +} + +# Deprecated - use alb_sg instead +resource "aws_security_group" "allow_https" { + name = "allow_https" + description = "Allow HTTPS traffic" + vpc_id = aws_vpc.main.id + + ingress { + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "allow-https-deprecated" + } +} diff --git a/AWS-Tf-Handson/Day24/code/tfplan b/AWS-Tf-Handson/Day24/code/tfplan new file mode 100644 index 000000000..135cae289 Binary files /dev/null and b/AWS-Tf-Handson/Day24/code/tfplan differ diff --git a/AWS-Tf-Handson/Day24/code/variables.tf b/AWS-Tf-Handson/Day24/code/variables.tf new file mode 100644 index 000000000..1e2087d8e --- /dev/null +++ b/AWS-Tf-Handson/Day24/code/variables.tf @@ -0,0 +1,83 @@ +variable "region" { + description = "The AWS region to deploy the infrastructure" + type = string + default = "us-east-1" +} + +variable "environment" { + description = "Environment name (e.g., dev, staging, production)" + type = string + default = "production" +} + +variable "vpc_cidr" { + description = "The CIDR block for the VPC" + type = string + default = "10.0.0.0/16" +} + +variable "public_subnet_cidrs" { + description = "List of CIDR blocks for public subnets" + type = list(string) + default = ["10.0.1.0/24", "10.0.2.0/24"] +} + +variable "private_subnet_cidrs" { + description = "List of CIDR blocks for private subnets" + type = list(string) + default = ["10.0.11.0/24", "10.0.12.0/24"] +} + +variable "public_subnet_count" { + description = "Number of public subnets to create" + type = number + default = 2 +} + +variable "private_subnet_count" { + description = "Number of private subnets to create" + type = number + default = 2 +} + +variable "availability_zones" { + description = "List of availability zones" + type = list(string) + default = ["us-east-1a", "us-east-1b"] +} + +variable "ami_id" { + description = "AMI ID for EC2 instances" + type = string + default = "ami-0c398cb65a93047f2" # Ubuntu 22.04 LTS +} + +variable "instance_type" { + description = "The EC2 instance type" + type = string + default = "t2.micro" +} + +variable "desired_capacity" { + description = "The desired number of EC2 instances in the Auto Scaling Group" + type = number + default = 2 +} + +variable "max_size" { + description = "The maximum number of EC2 instances in the Auto Scaling Group" + type = number + default = 5 +} + +variable "min_size" { + description = "The minimum number of EC2 instances in the Auto Scaling Group" + type = number + default = 1 +} + +variable "s3_bucket_name" { + description = "The name of the S3 bucket (prefix)" + type = string + default = "terraform-day15-prod-bucket" +} diff --git a/AWS-Tf-Handson/Day24/code/vpc.tf b/AWS-Tf-Handson/Day24/code/vpc.tf new file mode 100644 index 000000000..a3bbf60a5 --- /dev/null +++ b/AWS-Tf-Handson/Day24/code/vpc.tf @@ -0,0 +1,101 @@ +resource "aws_vpc" "main" { + cidr_block = var.vpc_cidr + enable_dns_support = true + enable_dns_hostnames = true + tags = { + Name = "main-vpc" + } +} + +resource "aws_subnet" "public" { + count = var.public_subnet_count + vpc_id = aws_vpc.main.id + cidr_block = element(var.public_subnet_cidrs, count.index) + availability_zone = element(var.availability_zones, count.index) + map_public_ip_on_launch = true + tags = { + Name = "public-subnet-${count.index + 1}" + } +} + +resource "aws_subnet" "private" { + count = var.private_subnet_count + vpc_id = aws_vpc.main.id + cidr_block = element(var.private_subnet_cidrs, count.index) + availability_zone = element(var.availability_zones, count.index) + tags = { + Name = "private-subnet-${count.index + 1}" + } +} + +resource "aws_route_table" "public" { + vpc_id = aws_vpc.main.id + tags = { + Name = "public-route-table" + } +} + +resource "aws_route" "public" { + route_table_id = aws_route_table.public.id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.main.id +} + +resource "aws_route_table_association" "public" { + count = var.public_subnet_count + subnet_id = aws_subnet.public[count.index].id + route_table_id = aws_route_table.public.id +} + +resource "aws_internet_gateway" "main" { + vpc_id = aws_vpc.main.id + tags = { + Name = "main-internet-gateway" + } +} + +# Create one route table per private subnet for HA +resource "aws_route_table" "private" { + count = var.private_subnet_count + vpc_id = aws_vpc.main.id + tags = { + Name = "private-route-table-${count.index + 1}" + } +} + +# Route each private subnet's traffic through its corresponding NAT Gateway +resource "aws_route" "private" { + count = var.private_subnet_count + route_table_id = aws_route_table.private[count.index].id + destination_cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.main[count.index].id +} + +# Associate each private subnet with its own route table +resource "aws_route_table_association" "private" { + count = var.private_subnet_count + subnet_id = aws_subnet.private[count.index].id + route_table_id = aws_route_table.private[count.index].id +} + +# Create one NAT Gateway per Availability Zone +resource "aws_nat_gateway" "main" { + count = var.private_subnet_count + allocation_id = aws_eip.main[count.index].id + subnet_id = aws_subnet.public[count.index].id + tags = { + Name = "nat-gateway-az-${count.index + 1}" + } +} + +# Create one Elastic IP per NAT Gateway +resource "aws_eip" "main" { + count = var.private_subnet_count + domain = "vpc" + + tags = { + Name = "nat-eip-az-${count.index + 1}" + } + + depends_on = [aws_internet_gateway.main] +} diff --git a/AWS-Tf-Handson/Day24/task.md b/AWS-Tf-Handson/Day24/task.md new file mode 100644 index 000000000..4803c0789 --- /dev/null +++ b/AWS-Tf-Handson/Day24/task.md @@ -0,0 +1,25 @@ +# Day 24 - Quick Tasks (Mini Project 10: High Available/Scalable Infrastructure) + +## What to Do + +- [ ] Watch the Day 24 video and take notes +- [ ] Deploy the highly available Django application with multi-AZ setup +- [ ] Test the application and verify auto-scaling +- [ ] Write a short blog (1000-1200 words) about your implementation + - Include architecture diagram showing VPC, AZs, ALB, ASG, NAT Gateways + - Add screenshots of deployment and running application + - Document HA design principles and cost breakdown + - Embed the video +- [ ] Share your blog on LinkedIn and Twitter/X with **#30daysofawsterraform** +- [ ] Tag me (Piyush Sachdeva) +- [ ] Submit your work (by creating an issue in this repo) + +## Tips + +- Keep it simple and clear, feel free to take AI assistance but do not overuse it +- Use your own diagrams +- Screenshot your work to document your implementation +- Monitor costs actively - destroy resources when done +- Engage with others' posts + +Good luck! ๐Ÿš€ diff --git a/AWS-Tf-Handson/Day25/LOGICAL_FLOW.md b/AWS-Tf-Handson/Day25/LOGICAL_FLOW.md new file mode 100644 index 000000000..020b27006 --- /dev/null +++ b/AWS-Tf-Handson/Day25/LOGICAL_FLOW.md @@ -0,0 +1,44 @@ +# Logical Flow of Infrastructure Code + +To understand how this infrastructure is built, follow this sequence of files. This order respects the dependencies: you can't build servers without a network, and you can't load balance without servers. + +## 1. The Foundation: `main.tf` & `vpc.tf` +**Start here.** Before you can build servers, you need a network. +* **`main.tf`**: Configures the AWS Provider and sets global tags. +* **`vpc.tf`**: Builds the "Virtual Data Center". + * **VPC**: The isolated network container. + * **Public Subnets**: Where the Load Balancer and NAT Gateways live (they need to talk to the internet). + * **Private Subnets**: Where your actual Application Servers will live (hidden from the internet for security). + * **NAT Gateways**: Allow your private servers to download updates without letting hackers in. + +## 2. The Firewall: `security_groups.tf` +**Read this next.** This defines *who* is allowed to talk to *whom*. +* **`alb_sg`**: The "Doorman". It allows HTTP/HTTPS traffic from **Anywhere** (`0.0.0.0/0`). +* **`app_sg`**: The "VIP Section". It **only** allows traffic coming from the `alb_sg`. This is crucialโ€”it prevents anyone from bypassing the Load Balancer to hit your servers directly. + +## 3. The Front Door: `alb.tf` +**Now we connect the Network and Security.** +* **`aws_lb`**: The Load Balancer. It sits in the **Public Subnets** (from `vpc.tf`) and wears the **`alb_sg`** security badge (from `security_groups.tf`). +* **`aws_lb_target_group`**: The "Waiting Room". The Load Balancer sends valid requests here. +* **`aws_lb_listener`**: Listens on Port 80 and points traffic to that Target Group. + +## 4. The Application Fleet: `asg.tf` +**This is where the actual servers are created.** +* **`aws_launch_template`**: The "Blueprint". It says "Create servers using this AMI, this Instance Type, and attach the **`app_sg`** security group." +* **`aws_autoscaling_group`**: The "Manager". + * It launches servers into the **Private Subnets** (from `vpc.tf`). + * It registers them with the **Target Group** (from `alb.tf`) so they start receiving traffic. + * It watches **CloudWatch Alarms** (High/Low CPU) to decide if it should add or remove servers. + +## 5. The Storage: `s3.tf` +**The Sidecar.** +* This creates a secure bucket for your application to store files. While it doesn't "depend" on the networking files like the others do, your application running on the EC2 instances will likely use IAM roles to read/write to this bucket. + +--- + +## Summary of the Data Flow +1. **User** -> hits **Load Balancer** (in Public Subnet, allowed by `alb_sg`). +2. **Load Balancer** -> forwards to **Target Group**. +3. **Target Group** -> routes to an **EC2 Instance** (in Private Subnet, allowed by `app_sg`). +4. **EC2 Instance** -> processes request (and maybe saves a file to **S3**). +5. **EC2 Instance** -> sends response back through the Load Balancer to the User. diff --git a/AWS-Tf-Handson/Day25/code/main.tf b/AWS-Tf-Handson/Day25/code/main.tf new file mode 100644 index 000000000..5e15acf02 --- /dev/null +++ b/AWS-Tf-Handson/Day25/code/main.tf @@ -0,0 +1,26 @@ +terraform { + required_version = ">= 1.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + } +} + +provider "aws" { + region = var.region + profile = "468284643560" + + default_tags { + tags = { + Environment = var.environment + Project = "AWS-Production-Infrastructure" + ManagedBy = "Terraform" + } + } +} diff --git a/AWS-Tf-Handson/Day25/code/tfplan b/AWS-Tf-Handson/Day25/code/tfplan new file mode 100644 index 000000000..135cae289 Binary files /dev/null and b/AWS-Tf-Handson/Day25/code/tfplan differ diff --git a/AWS-Tf-Handson/Day25/code/variables.tf b/AWS-Tf-Handson/Day25/code/variables.tf new file mode 100644 index 000000000..1e2087d8e --- /dev/null +++ b/AWS-Tf-Handson/Day25/code/variables.tf @@ -0,0 +1,83 @@ +variable "region" { + description = "The AWS region to deploy the infrastructure" + type = string + default = "us-east-1" +} + +variable "environment" { + description = "Environment name (e.g., dev, staging, production)" + type = string + default = "production" +} + +variable "vpc_cidr" { + description = "The CIDR block for the VPC" + type = string + default = "10.0.0.0/16" +} + +variable "public_subnet_cidrs" { + description = "List of CIDR blocks for public subnets" + type = list(string) + default = ["10.0.1.0/24", "10.0.2.0/24"] +} + +variable "private_subnet_cidrs" { + description = "List of CIDR blocks for private subnets" + type = list(string) + default = ["10.0.11.0/24", "10.0.12.0/24"] +} + +variable "public_subnet_count" { + description = "Number of public subnets to create" + type = number + default = 2 +} + +variable "private_subnet_count" { + description = "Number of private subnets to create" + type = number + default = 2 +} + +variable "availability_zones" { + description = "List of availability zones" + type = list(string) + default = ["us-east-1a", "us-east-1b"] +} + +variable "ami_id" { + description = "AMI ID for EC2 instances" + type = string + default = "ami-0c398cb65a93047f2" # Ubuntu 22.04 LTS +} + +variable "instance_type" { + description = "The EC2 instance type" + type = string + default = "t2.micro" +} + +variable "desired_capacity" { + description = "The desired number of EC2 instances in the Auto Scaling Group" + type = number + default = 2 +} + +variable "max_size" { + description = "The maximum number of EC2 instances in the Auto Scaling Group" + type = number + default = 5 +} + +variable "min_size" { + description = "The minimum number of EC2 instances in the Auto Scaling Group" + type = number + default = 1 +} + +variable "s3_bucket_name" { + description = "The name of the S3 bucket (prefix)" + type = string + default = "terraform-day15-prod-bucket" +} diff --git a/AWS-Tf-Handson/Day25/code/vpc.tf b/AWS-Tf-Handson/Day25/code/vpc.tf new file mode 100644 index 000000000..a3bbf60a5 --- /dev/null +++ b/AWS-Tf-Handson/Day25/code/vpc.tf @@ -0,0 +1,101 @@ +resource "aws_vpc" "main" { + cidr_block = var.vpc_cidr + enable_dns_support = true + enable_dns_hostnames = true + tags = { + Name = "main-vpc" + } +} + +resource "aws_subnet" "public" { + count = var.public_subnet_count + vpc_id = aws_vpc.main.id + cidr_block = element(var.public_subnet_cidrs, count.index) + availability_zone = element(var.availability_zones, count.index) + map_public_ip_on_launch = true + tags = { + Name = "public-subnet-${count.index + 1}" + } +} + +resource "aws_subnet" "private" { + count = var.private_subnet_count + vpc_id = aws_vpc.main.id + cidr_block = element(var.private_subnet_cidrs, count.index) + availability_zone = element(var.availability_zones, count.index) + tags = { + Name = "private-subnet-${count.index + 1}" + } +} + +resource "aws_route_table" "public" { + vpc_id = aws_vpc.main.id + tags = { + Name = "public-route-table" + } +} + +resource "aws_route" "public" { + route_table_id = aws_route_table.public.id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.main.id +} + +resource "aws_route_table_association" "public" { + count = var.public_subnet_count + subnet_id = aws_subnet.public[count.index].id + route_table_id = aws_route_table.public.id +} + +resource "aws_internet_gateway" "main" { + vpc_id = aws_vpc.main.id + tags = { + Name = "main-internet-gateway" + } +} + +# Create one route table per private subnet for HA +resource "aws_route_table" "private" { + count = var.private_subnet_count + vpc_id = aws_vpc.main.id + tags = { + Name = "private-route-table-${count.index + 1}" + } +} + +# Route each private subnet's traffic through its corresponding NAT Gateway +resource "aws_route" "private" { + count = var.private_subnet_count + route_table_id = aws_route_table.private[count.index].id + destination_cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.main[count.index].id +} + +# Associate each private subnet with its own route table +resource "aws_route_table_association" "private" { + count = var.private_subnet_count + subnet_id = aws_subnet.private[count.index].id + route_table_id = aws_route_table.private[count.index].id +} + +# Create one NAT Gateway per Availability Zone +resource "aws_nat_gateway" "main" { + count = var.private_subnet_count + allocation_id = aws_eip.main[count.index].id + subnet_id = aws_subnet.public[count.index].id + tags = { + Name = "nat-gateway-az-${count.index + 1}" + } +} + +# Create one Elastic IP per NAT Gateway +resource "aws_eip" "main" { + count = var.private_subnet_count + domain = "vpc" + + tags = { + Name = "nat-eip-az-${count.index + 1}" + } + + depends_on = [aws_internet_gateway.main] +} diff --git a/AWS-Tf-Handson/day03/README.md b/AWS-Tf-Handson/day03/README.md new file mode 100644 index 000000000..2cc3f8138 --- /dev/null +++ b/AWS-Tf-Handson/day03/README.md @@ -0,0 +1,149 @@ +# Day 3: S3 Bucket + +## Topics Covered +- Authentication and Authorization to AWS resources +- S3 bucket management + +## Key Learning Points + +### AWS Authentication +Before creating resources, you need to configure AWS credentials for Terraform to authenticate with AWS APIs. + +### Authentication Methods +1. **AWS CLI Configuration**: `aws configure` +2. **Environment Variables**: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` +3. **IAM Roles**: For EC2 instances or AWS services +4. **AWS Profiles**: Named credential profiles + +### S3 (Simple Storage Service) +Object storage service that offers scalability, data availability, security, and performance. + +## Tasks for Practice + +### Prerequisites +1. **Create AWS Account**: Sign up for AWS free tier if you don't have an account +2. **Install AWS CLI**: Download and install from AWS official website +3. **Configure Credentials**: Set up your AWS access keys + +#### AWS CLI Installation + +**Check your system architecture first:** +```bash +# Linux/macOS +uname -m + +# Windows PowerShell +$env:PROCESSOR_ARCHITECTURE +``` + +**Official Website**: https://aws.amazon.com/cli/ + +**Windows:** +```powershell +# Using MSI installer (recommended) +# Download from: https://awscli.amazonaws.com/AWSCLIV2.msi + +# Using winget +winget install Amazon.AWSCLI + +# Using chocolatey +choco install awscli +``` + +**macOS:** +```bash +# Using official installer +curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg" +sudo installer -pkg AWSCLIV2.pkg -target / + +# Using Homebrew +brew install awscli +``` + +**Ubuntu/Debian:** +```bash +# Update package index +sudo apt update + +# Install AWS CLI v2 (choose based on your architecture) +# For x86_64 +curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" + +# For ARM64 +curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip" + +unzip awscliv2.zip +sudo ./aws/install + +# Verify installation +aws --version +``` + +### Authentication Setup + +#### Method 1: AWS CLI Configuration +```bash +aws configure +``` +Enter your: +- AWS Access Key ID +- AWS Secret Access Key +- Default region (e.g., us-east-1) +- Default output format (json) + +#### Method 2: Environment Variables +```bash +export AWS_ACCESS_KEY_ID="your-access-key" +export AWS_SECRET_ACCESS_KEY="your-secret-key" +export AWS_DEFAULT_REGION="us-east-1" +``` + +### Tasks to Complete +1. **Get familiar with Terraform AWS documentation** + - Visit: https://registry.terraform.io/providers/hashicorp/aws/latest + - Explore S3 resource documentation + +2. **Create AWS resources using terraform** + - S3 bucket with unique name + +3. **Practice Terraform commands** + - Initialize the working directory + - Plan the infrastructure changes + - Apply the configuration + - Verify resources in AWS Console + +### Important Notes +- **Resource Names**: S3 bucket names must be globally unique +- **Regions**: Ensure you're working in your intended AWS region +- **Costs**: Monitor AWS costs, even in free tier +- **Cleanup**: Always destroy resources when done practicing + +### Common Commands +```bash +# Initialize Terraform +terraform init + +# Validate configuration +terraform validate + +# Plan changes +terraform plan + +# Apply changes +terraform apply + +# Show current state +terraform show + +# Destroy resources +terraform destroy +``` + +### Troubleshooting Tips +- Check AWS credentials are properly configured +- Verify region settings match your intended deployment location +- Ensure S3 bucket names are unique and follow naming conventions +- Review AWS CloudTrail for API call logs if needed + +## Next Steps +Proceed to Day 4 to learn about Terraform state file management and remote backends using S3 and DynamoDB. diff --git a/AWS-Tf-Handson/day03/main.tf b/AWS-Tf-Handson/day03/main.tf new file mode 100644 index 000000000..f465753b2 --- /dev/null +++ b/AWS-Tf-Handson/day03/main.tf @@ -0,0 +1,24 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + # Configuration options + region = "us-east-1" +} + +# Create a S3 bucket +resource "aws_s3_bucket" "tf_test_baivab_bucket" { + bucket = "my-tf-test-baiv-bucket-98534" + + tags = { + Name = "My bucket" + Environment = "Dev" + } +} + diff --git a/AWS-Tf-Handson/day03/outputs.tf b/AWS-Tf-Handson/day03/outputs.tf new file mode 100644 index 000000000..13a72a4a9 --- /dev/null +++ b/AWS-Tf-Handson/day03/outputs.tf @@ -0,0 +1,3 @@ +output "aws_s3_bucket" { + value = aws_s3_bucket.tf_test_baivab_bucket.id +} \ No newline at end of file diff --git a/AWS-Tf-Handson/day03/task.md b/AWS-Tf-Handson/day03/task.md new file mode 100644 index 000000000..bf228bd80 --- /dev/null +++ b/AWS-Tf-Handson/day03/task.md @@ -0,0 +1,20 @@ +# Day 03 - Quick Tasks + +## What to Do + +- [ ] Watch the Day 03 video and take notes +- [ ] Create a terraform file to provision VPC and S3 bucket and create implicit dependency between them, don't worry about the variables, files etc(we will do that later) +- [ ] Write a short blog (500-800 words) about what you learned + - Include your own diagrams and code examples + - Embed the video in the blog +- [ ] Share your blog on LinkedIn and Twitter/X with **#30daysofawsterraform** +- [ ] Tag me [Piyush Sachdeva](https://www.linkedin.com/in/piyush-sachdeva/) and [The CloudOps Community](https://www.linkedin.com/company/thecloudopscomm/?viewAsMember=true) +- [ ] Submit your work (by creating an issue in this repo) + +## Tips + +- Keep it simple and clear, feel free to take AI assistance but do not overuse it +- Use your own diagrams +- Engage with othersโ€™ posts + +Good luck! ๐Ÿš€ diff --git a/AWSCLIV2.pkg b/AWSCLIV2.pkg new file mode 100644 index 000000000..0c78262c6 Binary files /dev/null and b/AWSCLIV2.pkg differ diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index be7508be5..000000000 --- a/Jenkinsfile +++ /dev/null @@ -1,121 +0,0 @@ -pipeline { - - agent any -/* - tools { - maven "maven3" - } -*/ - environment { - NEXUS_VERSION = "nexus3" - NEXUS_PROTOCOL = "http" - NEXUS_URL = "172.31.40.209:8081" - NEXUS_REPOSITORY = "vprofile-release" - NEXUS_REPO_ID = "vprofile-release" - NEXUS_CREDENTIAL_ID = "nexuslogin" - ARTVERSION = "${env.BUILD_ID}" - } - - stages{ - - stage('BUILD'){ - steps { - sh 'mvn clean install -DskipTests' - } - post { - success { - echo 'Now Archiving...' - archiveArtifacts artifacts: '**/target/*.war' - } - } - } - - stage('UNIT TEST'){ - steps { - sh 'mvn test' - } - } - - stage('INTEGRATION TEST'){ - steps { - sh 'mvn verify -DskipUnitTests' - } - } - - stage ('CODE ANALYSIS WITH CHECKSTYLE'){ - steps { - sh 'mvn checkstyle:checkstyle' - } - post { - success { - echo 'Generated Analysis Result' - } - } - } - - stage('CODE ANALYSIS with SONARQUBE') { - - environment { - scannerHome = tool 'sonarscanner4' - } - - steps { - withSonarQubeEnv('sonar-pro') { - sh '''${scannerHome}/bin/sonar-scanner -Dsonar.projectKey=vprofile \ - -Dsonar.projectName=vprofile-repo \ - -Dsonar.projectVersion=1.0 \ - -Dsonar.sources=src/ \ - -Dsonar.java.binaries=target/test-classes/com/visualpathit/account/controllerTest/ \ - -Dsonar.junit.reportsPath=target/surefire-reports/ \ - -Dsonar.jacoco.reportsPath=target/jacoco.exec \ - -Dsonar.java.checkstyle.reportPaths=target/checkstyle-result.xml''' - } - - timeout(time: 10, unit: 'MINUTES') { - waitForQualityGate abortPipeline: true - } - } - } - - stage("Publish to Nexus Repository Manager") { - steps { - script { - pom = readMavenPom file: "pom.xml"; - filesByGlob = findFiles(glob: "target/*.${pom.packaging}"); - echo "${filesByGlob[0].name} ${filesByGlob[0].path} ${filesByGlob[0].directory} ${filesByGlob[0].length} ${filesByGlob[0].lastModified}" - artifactPath = filesByGlob[0].path; - artifactExists = fileExists artifactPath; - if(artifactExists) { - echo "*** File: ${artifactPath}, group: ${pom.groupId}, packaging: ${pom.packaging}, version ${pom.version} ARTVERSION"; - nexusArtifactUploader( - nexusVersion: NEXUS_VERSION, - protocol: NEXUS_PROTOCOL, - nexusUrl: NEXUS_URL, - groupId: pom.groupId, - version: ARTVERSION, - repository: NEXUS_REPOSITORY, - credentialsId: NEXUS_CREDENTIAL_ID, - artifacts: [ - [artifactId: pom.artifactId, - classifier: '', - file: artifactPath, - type: pom.packaging], - [artifactId: pom.artifactId, - classifier: '', - file: "pom.xml", - type: "pom"] - ] - ); - } - else { - error "*** File: ${artifactPath}, could not be found"; - } - } - } - } - - - } - - -} diff --git a/README.md b/README.md deleted file mode 100644 index 88fd3cbba..000000000 --- a/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Prerequisites -# -- JDK 11 -- Maven 3 -- MySQL 8 - -# Technologies -- Spring MVC -- Spring Security -- Spring Data JPA -- Maven -- JSP -- Tomcat -- MySQL -- Memcached -- Rabbitmq -- ElasticSearch -# Database -Here,we used Mysql DB -sql dump file: -- /src/main/resources/db_backup.sql -- db_backup.sql file is a mysql dump file.we have to import this dump to mysql db server -- > mysql -u -p accounts < db_backup.sql - - diff --git a/Terraform-Full-Course-Aws b/Terraform-Full-Course-Aws new file mode 160000 index 000000000..3e556757f --- /dev/null +++ b/Terraform-Full-Course-Aws @@ -0,0 +1 @@ +Subproject commit 3e556757f85bd39374da041c697f7c4e9c2d0448 diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg deleted file mode 100644 index 6d2dcd6a9..000000000 --- a/ansible/ansible.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[defaults] -host_key_checking = False -timeout = 30 diff --git a/ansible/site.yml b/ansible/site.yml deleted file mode 100644 index 59aebc9bf..000000000 --- a/ansible/site.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- import_playbook: tomcat_setup.yml -- import_playbook: vpro-app-setup.yml - -#### diff --git a/ansible/templates/application.j2 b/ansible/templates/application.j2 deleted file mode 100644 index d930446bb..000000000 --- a/ansible/templates/application.j2 +++ /dev/null @@ -1,25 +0,0 @@ -#JDBC Configutation for Database Connection -jdbc.driverClassName=com.mysql.jdbc.Driver -jdbc.url=jdbc:mysql://dbhost:3306/accounts?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull -jdbc.username=db_user -jdbc.password=db_password - -#Memcached Configuration For Active and StandBy Host -#For Active Host -memcached.active.host=127.0.0.1 -memcached.active.port=11211 -#For StandBy Host -memcached.standBy.host=127.0.0.2 -memcached.standBy.port=11211 - -#RabbitMq Configuration -rabbitmq.address=18.220.62.126 -rabbitmq.port=5672 -rabbitmq.username=test -rabbitmq.password=test - -#Elasticesearch Configuration -elasticsearch.host =192.168.1.85 -elasticsearch.port =9300 -elasticsearch.cluster=vprofile -elasticsearch.node=vprofilenode diff --git a/ansible/templates/epel6-svcfile.j2 b/ansible/templates/epel6-svcfile.j2 deleted file mode 100644 index 379d55164..000000000 --- a/ansible/templates/epel6-svcfile.j2 +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -### BEGIN INIT INFO -# Provides: tomcat7 -# Required-Start: $network -# Required-Stop: $network -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Start/Stop Tomcat server -### END INIT INFO - -PATH=/sbin:/bin:/usr/sbin:/usr/bin - -start() { -sh /usr/local/tomcat8/bin/startup.sh -} - -stop() { -sh /usr/local/tomcat8/bin/shutdown.sh -} - -status() { -pid=$(ps -fe | grep '/usr/local/tomcat8' | grep -v grep | tr -s " " | cut -d" " -f2) - if [ -n "$pid" ]; then - echo -e "\e[00;32mTomcat is running with pid: $pid\e[00m" - else - echo -e "\e[00;31mTomcat is not running\e[00m" - fi -} - -case $1 in -start|stop|status) $1;; -restart) stop; start;; -*) echo "Run as $0 "; exit 1;; -esac -exit 0 - - diff --git a/ansible/templates/epel7-svcfile.j2 b/ansible/templates/epel7-svcfile.j2 deleted file mode 100644 index feb317ccd..000000000 --- a/ansible/templates/epel7-svcfile.j2 +++ /dev/null @@ -1,18 +0,0 @@ -[Unit] -Description=Tomcat -After=network.target - -[Service] -User=tomcat -WorkingDirectory=/usr/local/tomcat8 -Environment=JRE_HOME=/usr/lib/jvm/jre -Environment=JAVA_HOME=/usr/lib/jvm/jre -Environment=CATALINA_HOME=/usr/local/tomcat8 -Environment=CATALINE_BASE=/usr/local/tomcat8 -ExecStart=/usr/local/tomcat8/bin/catalina.sh run -ExecStop=/usr/local/tomcat8/bin/shutdown.sh -SyslogIdentifier=tomcat-%i - -[Install] -WantedBy=multi-user.target - diff --git a/ansible/templates/ubuntu14_15-svcfile.j2 b/ansible/templates/ubuntu14_15-svcfile.j2 deleted file mode 100644 index 379d55164..000000000 --- a/ansible/templates/ubuntu14_15-svcfile.j2 +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -### BEGIN INIT INFO -# Provides: tomcat7 -# Required-Start: $network -# Required-Stop: $network -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Start/Stop Tomcat server -### END INIT INFO - -PATH=/sbin:/bin:/usr/sbin:/usr/bin - -start() { -sh /usr/local/tomcat8/bin/startup.sh -} - -stop() { -sh /usr/local/tomcat8/bin/shutdown.sh -} - -status() { -pid=$(ps -fe | grep '/usr/local/tomcat8' | grep -v grep | tr -s " " | cut -d" " -f2) - if [ -n "$pid" ]; then - echo -e "\e[00;32mTomcat is running with pid: $pid\e[00m" - else - echo -e "\e[00;31mTomcat is not running\e[00m" - fi -} - -case $1 in -start|stop|status) $1;; -restart) stop; start;; -*) echo "Run as $0 "; exit 1;; -esac -exit 0 - - diff --git a/ansible/templates/ubuntu16-svcfile.j2 b/ansible/templates/ubuntu16-svcfile.j2 deleted file mode 100644 index 423b00d60..000000000 --- a/ansible/templates/ubuntu16-svcfile.j2 +++ /dev/null @@ -1,18 +0,0 @@ -[Unit] -Description=Tomcat -After=network.target - -[Service] -User=tomcat -WorkingDirectory=/usr/local/tomcat8 -Environment=JRE_HOME=/usr/lib/jvm/java-1.8.0-openjdk-amd64/jre -Environment=JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-amd64/jre -Environment=CATALINA_HOME=/usr/local/tomcat8 -Environment=CATALINE_BASE=/usr/local/tomcat8 -ExecStart=/usr/local/tomcat8/bin/catalina.sh run -ExecStop=/usr/local/tomcat8/bin/shutdown.sh -SyslogIdentifier=tomcat-%i - -[Install] -WantedBy=multi-user.target - diff --git a/ansible/tomcat_setup.yml b/ansible/tomcat_setup.yml deleted file mode 100644 index 66dff8904..000000000 --- a/ansible/tomcat_setup.yml +++ /dev/null @@ -1,113 +0,0 @@ -- name: Common tool setup on all the servers - hosts: appsrvgrp - become: yes - vars: - tom_url: https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.37/bin/apache-tomcat-8.5.37.tar.gz - - tasks: - - name: Install JDK on Centos 6/7 - yum: - name: java-1.8.0-openjdk.x86_64 - state: present - when: ansible_distribution == 'CentOS' - - - name: Install JDK on Ubuntu 14/15/16/18 - apt: - name: openjdk-8-jdk - state: present - update_cache: yes - when: ansible_distribution == 'Ubuntu' - - - name: Download Tomcat Tar Ball/Binaries - get_url: - url: "{{tom_url}}" - dest: /tmp/tomcat-8.tar.gz - - - name: Add tomcat group - group: - name: tomcat - state: present - - - name: Add tomcat user - user: - name: tomcat - group: tomcat - shell: /bin/nologin - home: /usr/local/tomcat8 - - - file: - path: /tmp/tomcat8 - state: directory - - - name: Extract tomcat - unarchive: - src: /tmp/tomcat-8.tar.gz - dest: /tmp/tomcat8/ - remote_src: yes - list_files: yes - register: unarchive_info - - - debug: - msg: "{{unarchive_info.files[0].split('/')[0]}}" - - - name: Synchronize /tmp/tomcat8/tomcat_cont /usr/local/tomcat8. - synchronize: - src: "/tmp/tomcat8/{{unarchive_info.files[0].split('/')[0]}}/" - dest: /usr/local/tomcat8/ - delegate_to: "{{ inventory_hostname }}" - - - name: Change ownership of /usr/local/tomcat8 - file: - path: /usr/local/tomcat8 - owner: tomcat - group: tomcat - recurse: yes - - - name: Setup tomcat SVC file on Centos 7 - template: - src: templates/epel7-svcfile.j2 - dest: /etc/systemd/system/tomcat.service - mode: "a+x" - when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7' - - - name: Setup tomcat SVC file on Centos 6 - template: - src: templates/epel6-svcfile.j2 - dest: /etc/init.d/tomcat - mode: "a+x" - when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6' - - - name: Setup tomcat SVC file on ubuntu 14/15 - template: - src: templates/ubuntu14_15-svcfile.j2 - dest: /etc/init.d/tomcat - mode: "a+x" - when: ansible_distribution == 'Ubuntu' and ansible_distribution_major_version < '16' - - - name: Setup tomcat SVC file on ubuntu 16 and 18 - template: - src: templates/ubuntu16-svcfile.j2 - dest: /etc/systemd/system/tomcat.service - mode: "a+x" - when: ansible_distribution == 'Ubuntu' and ansible_distribution_major_version >= '16' - - - name: Reload tomcat svc config in ubuntu 14/15 - command: update-rc.d tomcat defaults - when: ansible_distribution == 'Ubuntu' and ansible_distribution_major_version < '16' - - - name: Reload tomcat svc config in Centos 6 - command: chkconfig --add tomcat - when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6' - - - name: just force systemd to reread configs (2.4 and above) - systemd: - daemon_reload: yes - when: ansible_distribution_major_version > '6' or ansible_distribution_major_version > '15' - - - name: Start & Enable TOmcat 8 - service: - name: tomcat - state: started - enabled: yes - - diff --git a/ansible/vpro-app-setup.yml b/ansible/vpro-app-setup.yml deleted file mode 100644 index 0c3f5d4a5..000000000 --- a/ansible/vpro-app-setup.yml +++ /dev/null @@ -1,105 +0,0 @@ - -- name: Setup Tomcat8 & Deploy Artifact - hosts: appsrvgrp - become: yes - vars: - timestamp: "{{ansible_date_time.date}}_{{ansible_date_time.hour}}_{{ansible_date_time.minute}}" - tasks: - - name: Download latest VProfile.war from nexus - get_url: - url: "http://{{USER}}:{{PASS}}@{{nexusip}}:8081/repository/{{reponame}}/{{groupid}}/{{time}}/{{build}}/{{vprofile_version}}" - dest: "/tmp/vproapp-{{vprofile_version}}" - register: wardeploy - tags: - - deploy - - - stat: - path: /usr/local/tomcat8/webapps/ROOT - register: artifact_stat - tags: - - deploy - - - name: Stop tomcat svc - service: - name: tomcat - state: stopped - tags: - - deploy - - - name: Try Backup and Deploy - block: - - name: Archive ROOT dir with timestamp - archive: - path: /usr/local/tomcat8/webapps/ROOT - dest: "/opt/ROOT_{{timestamp}}.tgz" - when: artifact_stat.stat.exists - register: archive_info - tags: - - deploy - - - name: copy ROOT dir with old_ROOT name - shell: cp -r ROOT old_ROOT - args: - chdir: /usr/local/tomcat8/webapps/ - - - name: Delete current artifact - file: - path: "{{item}}" - state: absent - when: archive_info.changed - loop: - - /usr/local/tomcat8/webapps/ROOT - - /usr/local/tomcat8/webapps/ROOT.war - tags: - - deploy - - - name: Try deploy artifact else restore from previos old_ROOT - block: - - name: Deploy vprofile artifact - copy: - src: "/tmp/vproapp-{{vprofile_version}}" - dest: /usr/local/tomcat8/webapps/ROOT.war - remote_src: yes - register: deploy_info - tags: - - deploy - rescue: - - shell: cp -r old_ROOT ROOT - args: - chdir: /usr/local/tomcat8/webapps/ - - rescue: - - name: Start tomcat svc - service: - name: tomcat - state: started - - - name: Start tomcat svc - service: - name: tomcat - state: started - when: deploy_info.changed - tags: - - deploy - - - name: Wait until ROOT.war is extracted to ROOT directory - wait_for: - path: /usr/local/tomcat8/webapps/ROOT - tags: - - deploy - -# - name: Deploy web configuration file -# template: -# src: templates/application.j2 -# dest: /usr/local/tomcat8/webapps/ROOT/WEB-INF/classes/application.properties -# force: yes -# notify: -# - Restart Tomcat -# tags: -# - deploy - - handlers: - - name: Restart Tomcat - service: - name: tomcat - state: restarted diff --git a/argocd-example-apps b/argocd-example-apps new file mode 160000 index 000000000..0d521c6e0 --- /dev/null +++ b/argocd-example-apps @@ -0,0 +1 @@ +Subproject commit 0d521c6e049889134f3122eb32d7ed342f43ca0d diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 03bba333b..000000000 --- a/pom.xml +++ /dev/null @@ -1,211 +0,0 @@ - - 4.0.0 - com.visualpathit - vprofile - war - v2 - Visualpathit VProfile Webapp - http://maven.apache.org - - 4.2.0.RELEASE - 4.0.2.RELEASE - 1.8.2.RELEASE - 4.3.11.Final - 5.2.1.Final - 8.0.32 - 1.4 - 1.2 - 4.10 - 1.1.3 - 1.8 - 1.8 - - - - - org.springframework - spring-web - ${spring.version} - - - - org.springframework - spring-webmvc - ${spring.version} - - - - org.springframework.security - spring-security-web - ${spring-security.version} - - - - org.springframework.security - spring-security-config - ${spring-security.version} - - - - org.hibernate - hibernate-validator - ${hibernate-validator.version} - - - - org.springframework.data - spring-data-jpa - ${spring-data-jpa.version} - - - - org.hibernate - hibernate-entitymanager - ${hibernate.version} - - - - mysql - mysql-connector-java - ${mysql-connector.version} - - - - commons-dbcp - commons-dbcp - ${commons-dbcp.version} - - - - javax.servlet - jstl - ${jstl.version} - - - - junit - junit - ${junit.version} - test - - - org.mockito - mockito-core - 1.9.5 - test - - - org.springframework - spring-test - 3.2.3.RELEASE - test - - - javax.servlet - javax.servlet-api - 3.1.0 - provided - - - ch.qos.logback - logback-classic - ${logback.version} - - - org.hamcrest - hamcrest-all - 1.3 - test - - - commons-fileupload - commons-fileupload - 1.3.1 - - - - net.spy - spymemcached - 2.12.3 - - - commons-io - commons-io - 2.4 - - - - org.springframework.amqp - spring-rabbit - 1.7.1.RELEASE - - - - com.rabbitmq - amqp-client - 4.0.2 - - - - org.elasticsearch - elasticsearch - 5.6.4 - - - - org.elasticsearch.client - transport - 5.6.4 - - - - com.google.code.gson - gson - 2.8.2 - - - - - - org.eclipse.jetty - jetty-maven-plugin - 9.2.11.v20150529 - - 10 - - / - - - - - - org.apache.maven.plugins - maven-war-plugin - 3.2.2 - - - org.jacoco - jacoco-maven-plugin - 0.8.4 - - - jacoco-initialize - process-resources - - prepare-agent - - - - jacoco-site - post-integration-test - - report - - - - - - - - diff --git a/src/main/java/com/visualpathit/account/beans/Components.java b/src/main/java/com/visualpathit/account/beans/Components.java deleted file mode 100644 index f58b21fa9..000000000 --- a/src/main/java/com/visualpathit/account/beans/Components.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.visualpathit.account.beans; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -@Component -public class Components { - - @Value("${memcached.active.host}") - private String activeHost; - @Value("${memcached.active.port}") - private String activePort; - @Value("${memcached.standBy.host}") - private String standByHost; - @Value("${memcached.standBy.port}") - private String standByPort; - - @Value("${rabbitmq.address}") - private String rabbitMqHost; - @Value("${rabbitmq.port}") - private String rabbitMqPort; - @Value("${rabbitmq.username}") - private String rabbitMqUser; - @Value("${rabbitmq.password}") - private String rabbitMqPassword; - - @Value("${elasticsearch.host}") - private String elasticsearchHost; - @Value("${elasticsearch.port}") - private String elasticsearchPort; - @Value("${elasticsearch.cluster}") - private String elasticsearchCluster; - @Value("${elasticsearch.node}") - private String elasticsearchNode; - - - public String getActiveHost() { - return activeHost; - } - public String getActivePort() { - return activePort; - } - public String getStandByHost() { - return standByHost; - } - public String getStandByPort() { - return standByPort; - } - public void setActiveHost(String activeHost) { - this.activeHost = activeHost; - } - public void setActivePort(String activePort) { - this.activePort = activePort; - } - public void setStandByHost(String standByHost) { - this.standByHost = standByHost; - } - public void setStandByPort(String standByPort) { - this.standByPort = standByPort; - } - public String getRabbitMqHost() { - return rabbitMqHost; - } - public void setRabbitMqHost(String rabbitMqHost) { - this.rabbitMqHost = rabbitMqHost; - } - public String getRabbitMqPort() { - return rabbitMqPort; - } - public void setRabbitMqPort(String rabbitMqPort) { - this.rabbitMqPort = rabbitMqPort; - } - public String getRabbitMqUser() { - return rabbitMqUser; - } - public void setRabbitMqUser(String rabbitMqUser) { - this.rabbitMqUser = rabbitMqUser; - } - public String getRabbitMqPassword() { - return rabbitMqPassword; - } - public void setRabbitMqPassword(String rabbitMqPassword) { - this.rabbitMqPassword = rabbitMqPassword; - } - public String getElasticsearchHost() { - return elasticsearchHost; - } - public void setElasticsearchHost(String elasticsearchHost) { - this.elasticsearchHost = elasticsearchHost; - } - public String getElasticsearchPort() { - return elasticsearchPort; - } - public void setElasticsearchPort(String elasticsearchPort) { - this.elasticsearchPort = elasticsearchPort; - } - public String getElasticsearchCluster() { - return elasticsearchCluster; - } - public void setElasticsearchCluster(String elasticsearchCluster) { - this.elasticsearchCluster = elasticsearchCluster; - } - public String getElasticsearchNode() { - return elasticsearchNode; - } - public void setElasticsearchNode(String elasticsearchNode) { - this.elasticsearchNode = elasticsearchNode; - } - - -} diff --git a/src/main/java/com/visualpathit/account/controller/ElasticSearchController.java b/src/main/java/com/visualpathit/account/controller/ElasticSearchController.java deleted file mode 100644 index 6fe7f4104..000000000 --- a/src/main/java/com/visualpathit/account/controller/ElasticSearchController.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.visualpathit.account.controller; - -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; - -import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; -import org.elasticsearch.action.delete.DeleteResponse; -import org.elasticsearch.action.get.GetResponse; -import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.action.index.IndexResponse; -import org.elasticsearch.action.update.UpdateRequest; -import org.elasticsearch.action.update.UpdateResponse; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; - -import com.google.gson.Gson; -import com.visualpathit.account.model.User; -import com.visualpathit.account.service.UserService; -import com.visualpathit.account.utils.ElasticsearchUtil; - -import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; - -@Controller -public class ElasticSearchController { - @Autowired - private UserService userService; - - @RequestMapping(value="/user/elasticsearch", method=RequestMethod.GET) - public String insert(final Model model) throws IOException { - List users = userService.getList(); - //contextMapping(); - - /* for (User user : users) { - //IndexRequest indexRequest = new IndexRequest("users","user", String.valueOf(user.getId())); - //indexRequest.source(new Gson().toJson(user)); - //IndexResponse response = ElasticsearchUtil.trannsportClient().index(indexRequest).actionGet(); - System.out.println("User" +new Gson().toJson(user)); - }*/ - String result =""; - for (User user : users) { - IndexResponse response = ElasticsearchUtil.trannsportClient().prepareIndex("users","user", String.valueOf(user.getId())) - .setSource(jsonBuilder() - .startObject() - .field("name", user.getUsername()) - .field("DOB",user.getDateOfBirth()) - .field("fatherName",user.getFatherName()) - .field("motherName",user.getMotherName()) - .field("gender",user.getGender()) - .field("nationality",user.getNationality()) - .field("phoneNumber", user.getPhoneNumber()) - .endObject() - ) - .get(); - String res =response.getResult().toString(); - System.out.println(res); - result="Users"; - } - model.addAttribute(result); - return "elasticeSearchRes"; - - } - - @RequestMapping(value="/rest/users/view/{id}", method=RequestMethod.GET) - public String view(@PathVariable final String id,final Model model) { - GetResponse getResponse = ElasticsearchUtil.trannsportClient().prepareGet("users", "user", id).get(); - System.out.println(getResponse.getSource()); - - model.addAttribute("res", getResponse.getSource().get("name")); - - return "elasticeSearchRes"; - } - /*@RequestMapping(value = "/get_user_list", method = RequestMethod.GET) - public @ResponseBody List getTagList(@RequestParam("term") String query) { - List users = userService.getList(); - List tagList = null; - for (User user : users) { - GetResponse getResponse = ElasticsearchUtil.trannsportClient().prepareGet("users", "user" ,String.valueOf(user.getId())).get(); - System.out.println(getResponse.getSource()); - - tagList.add(getResponse.getSource()); - } - return tagList; - }*/ - - @RequestMapping(value="/rest/users/update/{id}", method=RequestMethod.GET) - public String update(@PathVariable final String id,final Model model) throws IOException { - - UpdateRequest updateRequest = new UpdateRequest(); - updateRequest.index("employee") - .type("id") - .id(id) - .doc(jsonBuilder() - .startObject() - .field("gender", "male") - .endObject()); - try { - UpdateResponse updateResponse = ElasticsearchUtil.trannsportClient().update(updateRequest).get(); - System.out.println(updateResponse.status()); - model.addAttribute("res", updateResponse.status()); - return "elasticeSearchRes"; - } catch (InterruptedException | ExecutionException e) { - System.out.println(e); - } - return "elasticeSearchRes"; - } - @RequestMapping(value="/rest/users/delete/{id}", method=RequestMethod.GET) - public String delete(@PathVariable final String id,final Model model) { - - DeleteResponse deleteResponse =ElasticsearchUtil.trannsportClient().prepareDelete("employee", "id", id).get(); - System.out.println(deleteResponse.getResult().toString()); - model.addAttribute("res", deleteResponse.getResult().toString()); - return "elasticeSearchRes"; - } - /*public void contextMapping() throws IOException{ - String json ="{" - + "\"mappings\":{" - + "\"users\":\" {" - + "\"properties\" : {" - + "\"name\" : { \"type\" : \"string\" }," - + " \"city\" : { \"type\" : \"string\" }," - + "\"name_suggest\" : {" - + "\"type\" : \"completion\"" - + "}}" - + "}"; - IndexResponse response = ElasticsearchUtil.trannsportClient().prepareIndex("users", "data") - .setSource(json).execute().actionGet(); - - }*/ -} diff --git a/src/main/java/com/visualpathit/account/controller/FileUploadController.java b/src/main/java/com/visualpathit/account/controller/FileUploadController.java deleted file mode 100644 index 0de040a2d..000000000 --- a/src/main/java/com/visualpathit/account/controller/FileUploadController.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.visualpathit.account.controller; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.multipart.MultipartFile; - -import com.visualpathit.account.model.User; -import com.visualpathit.account.service.UserService; - -@Controller -public class FileUploadController { - @Autowired - private UserService userService; - private static final Logger logger = LoggerFactory - .getLogger(FileUploadController.class); - - /** - * Upload single file using Spring Controller - */ - @RequestMapping(value = { "/upload"} , method = RequestMethod.GET) - public final String upload(final Model model) { - return "upload"; - } - @RequestMapping(value = "/uploadFile", method = RequestMethod.POST) - public @ResponseBody - String uploadFileHandler(@RequestParam("name") String name,@RequestParam("userName") String userName, - @RequestParam("file") MultipartFile file) { - - System.out.println("Called the upload file :::" ); - if (!file.isEmpty()) { - try { - byte[] bytes = file.getBytes(); - - // Creating the directory to store file - String rootPath = System.getProperty("catalina.home"); - System.out.println("Path ::::" +rootPath); - File dir = new File(rootPath + File.separator + "tmpFiles"); - if (!dir.exists()) - dir.mkdirs(); - - // Create the file on server - File serverFile = new File(dir.getAbsolutePath() - + File.separator + name+".png"); - //image saving - User user = userService.findByUsername(userName); - user.setProfileImg(name +".png"); - user.setProfileImgPath(serverFile.getAbsolutePath()); - userService.save(user); - - BufferedOutputStream stream = new BufferedOutputStream( - new FileOutputStream(serverFile)); - stream.write(bytes); - stream.close(); - - logger.info("Server File Location=" - + serverFile.getAbsolutePath()); - - return "You successfully uploaded file=" + name +".png"; - } catch (Exception e) { - return "You failed to upload " + name +".png" + " => " + e.getMessage(); - } - } else { - return "You failed to upload " + name +".png" - + " because the file was empty."; - } - } - -} diff --git a/src/main/java/com/visualpathit/account/controller/UserController.java b/src/main/java/com/visualpathit/account/controller/UserController.java deleted file mode 100644 index c370682e2..000000000 --- a/src/main/java/com/visualpathit/account/controller/UserController.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.visualpathit.account.controller; - -import com.visualpathit.account.model.User; -import com.visualpathit.account.service.ProducerService; -import com.visualpathit.account.service.SecurityService; -import com.visualpathit.account.service.UserService; -import com.visualpathit.account.utils.MemcachedUtils; -import com.visualpathit.account.validator.UserValidator; - -import java.util.List; -import java.util.UUID; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -/**{@author imrant}*/ -@Controller -public class UserController { - @Autowired - private UserService userService; - - @Autowired - private SecurityService securityService; - - @Autowired - private UserValidator userValidator; - - @Autowired - private ProducerService producerService; - - /** {@inheritDoc} */ - @RequestMapping(value = "/registration", method = RequestMethod.GET) - public final String registration(final Model model) { - model.addAttribute("userForm", new User()); - return "registration"; - } - /** {@inheritDoc} */ - @RequestMapping(value = "/registration", method = RequestMethod.POST) - public final String registration(final @ModelAttribute("userForm") User userForm, - final BindingResult bindingResult, final Model model) { - - userValidator.validate(userForm, bindingResult); - if (bindingResult.hasErrors()) { - return "registration"; - } - System.out.println("User PWD:"+userForm.getPassword()); - userService.save(userForm); - - securityService.autologin(userForm.getUsername(), userForm.getPasswordConfirm()); - - return "redirect:/welcome"; - } - /** {@inheritDoc} */ - @RequestMapping(value = "/login", method = RequestMethod.GET) - public final String login(final Model model, final String error, final String logout) { - System.out.println("Model data"+model.toString()); - if (error != null){ - model.addAttribute("error", "Your username and password is invalid."); - } - if (logout != null){ - model.addAttribute("message", "You have been logged out successfully."); - } - return "login"; - } - /** {@inheritDoc} */ - @RequestMapping(value = { "/", "/welcome"}, method = RequestMethod.GET) - public final String welcome(final Model model) { - return "welcome"; - } - /** {@inheritDoc} */ - @RequestMapping(value = { "/index"} , method = RequestMethod.GET) - public final String indexHome(final Model model) { - return "index_home"; - } - @RequestMapping(value = "/users", method = RequestMethod.GET) - public String getAllUsers(Model model) - { - - List users = userService.getList(); - //JSONObject jsonObject - System.out.println("All User Data:::" + users); - model.addAttribute("users", users); - return "userList"; - } - - @RequestMapping(value = "/users/{id}", method = RequestMethod.GET) - public String getOneUser(@PathVariable(value="id") String id,Model model) - { - String Result =""; - try{ - if( id != null && MemcachedUtils.memcachedGetData(id)!= null){ - User userData = MemcachedUtils.memcachedGetData(id); - Result ="Data is From Cache"; - System.out.println("--------------------------------------------"); - System.out.println("Data is From Cache !!"); - System.out.println("--------------------------------------------"); - System.out.println("Father ::: "+userData.getFatherName()); - model.addAttribute("user", userData); - model.addAttribute("Result", Result); - } - else{ - User user = userService.findById(Long.parseLong(id)); - Result = MemcachedUtils.memcachedSetData(user,id); - if(Result == null ){ - Result ="Memcached Connection Failure !!"; - } - System.out.println("--------------------------------------------"); - System.out.println("Data is From Database"); - System.out.println("--------------------------------------------"); - System.out.println("Result ::: "+ Result); - model.addAttribute("user", user); - model.addAttribute("Result", Result); - } - } catch (Exception e) { - System.out.println( e.getMessage() ); - } - return "user"; - } - - /** {@inheritDoc} */ - @RequestMapping(value = { "/user/{username}"} , method = RequestMethod.GET) - public final String userUpdate(@PathVariable(value="username") String username,final Model model) { - User user = userService.findByUsername(username); - System.out.println("User Data:::" + user); - model.addAttribute("user", user); - return "userUpdate"; - } - @RequestMapping(value = { "/user/{username}"} , method = RequestMethod.POST) - public final String userUpdateProfile(@PathVariable(value="username") String username,final @ModelAttribute("user") User userForm,final Model model) { - User user = userService.findByUsername(username); - user.setUsername(userForm.getUsername()); - user.setUserEmail(userForm.getUserEmail()); - user.setDateOfBirth(userForm.getDateOfBirth()); - user.setFatherName(userForm.getFatherName()); - user.setMotherName(userForm.getMotherName()); - user.setGender(userForm.getGender()); - user.setLanguage(userForm.getLanguage()); - user.setMaritalStatus(userForm.getMaritalStatus()); - user.setNationality(userForm.getNationality()); - user.setPermanentAddress(userForm.getPermanentAddress()); - user.setTempAddress(userForm.getTempAddress()); - user.setPhoneNumber(userForm.getPhoneNumber()); - user.setSecondaryPhoneNumber(userForm.getSecondaryPhoneNumber()); - user.setPrimaryOccupation(userForm.getPrimaryOccupation()); - user.setSecondaryOccupation(userForm.getSecondaryOccupation()); - user.setSkills(userForm.getSkills()); - user.setWorkingExperience(userForm.getWorkingExperience()); - userService.save(user); - /*model.addAttribute("user", user);*/ - return "welcome"; - } - - @RequestMapping(value={"/user/rabbit"}, method={RequestMethod.GET}) - public String rabbitmqSetUp() { - System.out.println("Rabbit mq method is callled!!!"); - for (int i = 0; i < 20; i++) { - producerService.produceMessage(generateString()); - } - return "rabbitmq"; - } - - private static String generateString() { - String uuid = UUID.randomUUID().toString(); - return "uuid = " + uuid; - } - - - -} diff --git a/src/main/java/com/visualpathit/account/model/Role.java b/src/main/java/com/visualpathit/account/model/Role.java deleted file mode 100644 index af821ad0e..000000000 --- a/src/main/java/com/visualpathit/account/model/Role.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.visualpathit.account.model; - -import javax.persistence.*; -import java.util.Set; -/**{@author imrant} !*/ -@Entity -@Table(name = "role") -public class Role { - /** the id field !*/ - private Long id; - /** the name field !*/ - private String name; - /** the user field !*/ - private Set users; - /** {@inheritDoc}} !*/ - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - /** - * {@link Role#id} - !*/ - public Long getId() { - return id; - } - /** {@inheritDoc}} !*/ - public void setId(final Long id) { - this.id = id; - } - /** - * {@link Role#name} - !*/ - public String getName() { - return name; - } - /** {@inheritDoc}} !*/ - public void setName(final String name) { - this.name = name; - } - /** - * {@inheritDoc}} - !*/ - @ManyToMany(fetch = FetchType.EAGER, mappedBy = "roles",cascade = CascadeType.ALL) - /** - * {@link Role#id} - !*/ - public Set getUsers() { - return users; - } - /** - * {@inheritDoc}} - !*/ - public final void setUsers(Set users) { - this.users = users; - } -} diff --git a/src/main/java/com/visualpathit/account/model/User.java b/src/main/java/com/visualpathit/account/model/User.java deleted file mode 100644 index 23050ce94..000000000 --- a/src/main/java/com/visualpathit/account/model/User.java +++ /dev/null @@ -1,215 +0,0 @@ -package com.visualpathit.account.model; - - -import javax.persistence.*; - -import java.io.Serializable; -import java.util.Set; -/**{@author imrant} !*/ -@Entity -@Table(name = "user") -public class User implements Serializable { - /** the id field !*/ - private Long id; - /** the user name field !*/ - private String username; - /** the password field !*/ - private String password; - /** the userEmail field !*/ - private String userEmail; - /** the passwordConfirm field !*/ - private String passwordConfirm; - /** the profileImg field !*/ - private String profileImg; - /** the profileImgPath field !*/ - private String profileImgPath; - private String dateOfBirth; - private String fatherName; - private String motherName; - private String gender; - private String maritalStatus; - private String permanentAddress; - private String tempAddress; - private String primaryOccupation; - private String secondaryOccupation; - private String skills; - private String phoneNumber; - private String secondaryPhoneNumber; - private String nationality; - private String language; - private String workingExperience; - - - /** the roles field !*/ - private Set roles; - /** {@inheritDoc}} !*/ - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - /** {@link User#id} */ - public Long getId() { - return id; - } - /** {@inheritDoc}} !*/ - public void setId(final Long id) { - this.id = id; - } - /**{@inheritDoc}} !*/ - public String getUsername() { - return username; - } - /** {@inheritDoc}} !*/ - public void setUsername(final String username) { - this.username = username; - } - /** - * {@link User#password} - * @return The {@link String} instance representing password - !*/ - public String getPassword() { - return password; - } - /** - * {@inheritDoc}} - !*/ - public void setPassword(final String password) { - this.password = password; - } - /** - * {@link User#userEmail} - * @return The {@link String} instance representing userEmail. - !*/ - public String getUserEmail() { - return userEmail; - } - /** {@inheritDoc}} !*/ - public void setUserEmail(final String userEmail) { - this.userEmail = userEmail; - } - - /** {@inheritDoc}} !*/ - @Transient - /** - * {@link User#passwordConfirm} - !*/ - public String getPasswordConfirm() { - return passwordConfirm; - } - /** {@inheritDoc}} !*/ - public void setPasswordConfirm(final String passwordConfirm) { - this.passwordConfirm = passwordConfirm; - } - /** {@inheritDoc}} !*/ - @ManyToMany - @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) - public Set getRoles() { - return roles; - } - /** {@inheritDoc}} !*/ - public void setRoles(final Set roles) { - this.roles = roles; - } - public String getProfileImg() { - return profileImg; - } - public void setProfileImg(String profileImg) { - this.profileImg = profileImg; - } - public String getProfileImgPath() { - return profileImgPath; - } - public void setProfileImgPath(String profileImgPath) { - this.profileImgPath = profileImgPath; - } - public String getDateOfBirth() { - return dateOfBirth; - } - public void setDateOfBirth(String dateOfBirth) { - this.dateOfBirth = dateOfBirth; - } - public String getFatherName() { - return fatherName; - } - public void setFatherName(String fatherName) { - this.fatherName = fatherName; - } - public String getMotherName() { - return motherName; - } - public void setMotherName(String motherName) { - this.motherName = motherName; - } - public String getGender() { - return gender; - } - public void setGender(String gender) { - this.gender = gender; - } - public String getMaritalStatus() { - return maritalStatus; - } - public void setMaritalStatus(String maritalStatus) { - this.maritalStatus = maritalStatus; - } - public String getPermanentAddress() { - return permanentAddress; - } - public void setPermanentAddress(String permanentAddress) { - this.permanentAddress = permanentAddress; - } - public String getTempAddress() { - return tempAddress; - } - public void setTempAddress(String tempAddress) { - this.tempAddress = tempAddress; - } - public String getPrimaryOccupation() { - return primaryOccupation; - } - public void setPrimaryOccupation(String primaryOccupation) { - this.primaryOccupation = primaryOccupation; - } - public String getSecondaryOccupation() { - return secondaryOccupation; - } - public void setSecondaryOccupation(String secondaryOccupation) { - this.secondaryOccupation = secondaryOccupation; - } - public String getSkills() { - return skills; - } - public void setSkills(String skills) { - this.skills = skills; - } - public String getPhoneNumber() { - return phoneNumber; - } - public void setPhoneNumber(String phoneNumber) { - this.phoneNumber = phoneNumber; - } - public String getSecondaryPhoneNumber() { - return secondaryPhoneNumber; - } - public void setSecondaryPhoneNumber(String secondaryPhoneNumber) { - this.secondaryPhoneNumber = secondaryPhoneNumber; - } - public String getNationality() { - return nationality; - } - public void setNationality(String nationality) { - this.nationality = nationality; - } - public String getLanguage() { - return language; - } - public void setLanguage(String language) { - this.language = language; - } - public String getWorkingExperience() { - return workingExperience; - } - public void setWorkingExperience(String workingExperience) { - this.workingExperience = workingExperience; - } - - -} diff --git a/src/main/java/com/visualpathit/account/repository/RoleRepository.java b/src/main/java/com/visualpathit/account/repository/RoleRepository.java deleted file mode 100644 index c091709a5..000000000 --- a/src/main/java/com/visualpathit/account/repository/RoleRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.visualpathit.account.repository; - -import org.springframework.data.jpa.repository.JpaRepository; - -import com.visualpathit.account.model.Role; - -public interface RoleRepository extends JpaRepository{ -} diff --git a/src/main/java/com/visualpathit/account/repository/UserRepository.java b/src/main/java/com/visualpathit/account/repository/UserRepository.java deleted file mode 100644 index 149b656cf..000000000 --- a/src/main/java/com/visualpathit/account/repository/UserRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.visualpathit.account.repository; - -import java.util.List; - -import org.springframework.data.jpa.repository.JpaRepository; - -import com.visualpathit.account.model.User; - -public interface UserRepository extends JpaRepository { - User findByUsername(String username); - User findById(long id); - /*public void updateUser(User user)*/; - -} diff --git a/src/main/java/com/visualpathit/account/service/ConsumerService.java b/src/main/java/com/visualpathit/account/service/ConsumerService.java deleted file mode 100644 index a638bf0d6..000000000 --- a/src/main/java/com/visualpathit/account/service/ConsumerService.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.visualpathit.account.service; - -public interface ConsumerService { - - void consumerMessage(byte[] data); -} diff --git a/src/main/java/com/visualpathit/account/service/ConsumerServiceImpl.java b/src/main/java/com/visualpathit/account/service/ConsumerServiceImpl.java deleted file mode 100644 index ecbd1b6b1..000000000 --- a/src/main/java/com/visualpathit/account/service/ConsumerServiceImpl.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.visualpathit.account.service; - -import org.springframework.amqp.core.ExchangeTypes; -import org.springframework.amqp.rabbit.annotation.Exchange; -import org.springframework.amqp.rabbit.annotation.Queue; -import org.springframework.amqp.rabbit.annotation.QueueBinding; -import org.springframework.amqp.rabbit.annotation.RabbitListener; -import org.springframework.stereotype.Service; - -@Service -public class ConsumerServiceImpl implements ConsumerService { - - /** - The name of the exchange. - */ - private static final String EXCHANGE_NAME = "messages"; - - /** - * The function that consumes messages from the broker(RabbitMQ) - * @param data - */ - @Override - @RabbitListener(bindings = @QueueBinding( value = @Queue(), - exchange = @Exchange(value = EXCHANGE_NAME, type = ExchangeTypes.FANOUT))) - public void consumerMessage(byte[] data) { - String consumedMessage = new String(data); - System.out.println(" [x] Consumed '" + consumedMessage + "'"); - } -} diff --git a/src/main/java/com/visualpathit/account/service/ProducerService.java b/src/main/java/com/visualpathit/account/service/ProducerService.java deleted file mode 100644 index ac89af238..000000000 --- a/src/main/java/com/visualpathit/account/service/ProducerService.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.visualpathit.account.service; - -public interface ProducerService { - - public String produceMessage(String message); -} diff --git a/src/main/java/com/visualpathit/account/service/ProducerServiceImpl.java b/src/main/java/com/visualpathit/account/service/ProducerServiceImpl.java deleted file mode 100644 index 46970e609..000000000 --- a/src/main/java/com/visualpathit/account/service/ProducerServiceImpl.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.visualpathit.account.service; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.visualpathit.account.utils.RabbitMqUtil; - -import org.springframework.stereotype.Service; -import com.rabbitmq.client.Channel; - -import java.io.IOException; -import java.util.concurrent.TimeoutException; - -@Service -public class ProducerServiceImpl implements ProducerService { - - /** - * The name of the Exchange - */ - private static final String EXCHANGE_NAME = "messages"; - - /** - * This method publishes a message - * @param message - */ - @Override - public String produceMessage(String message) { - try { - ConnectionFactory factory = new ConnectionFactory(); - /** - * System.out.println("Rabitmq host: ::" + RabbitMqUtil.getRabbitMqHost()); - * System.out.println("Rabitmq port: ::" + RabbitMqUtil.getRabbitMqPort()); - * System.out.println("Rabitmq user: ::" + RabbitMqUtil.getRabbitMqUser()); - * System.out.println("Rabitmq password: ::" + RabbitMqUtil.getRabbitMqPassword()); - **/ - factory.setHost(RabbitMqUtil.getRabbitMqHost()); - factory.setPort(Integer.parseInt(RabbitMqUtil.getRabbitMqPort())); - factory.setUsername(RabbitMqUtil.getRabbitMqUser()); - factory.setPassword(RabbitMqUtil.getRabbitMqPassword()); - Connection connection = factory.newConnection(); - System.out.println("Connection open status"+connection.isOpen()); - Channel channel = connection.createChannel(); - channel.exchangeDeclare(EXCHANGE_NAME, "fanout"); - channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes()); - System.out.println(" [x] Sent '" + message + "'"); - channel.close(); - connection.close(); - } catch (IOException io) { - System.out.println("IOException"); - io.printStackTrace(); - } catch (TimeoutException toe) { - System.out.println("TimeoutException : " + toe.getMessage()); - toe.printStackTrace(); - } - return "response"; - } -} diff --git a/src/main/java/com/visualpathit/account/service/SecurityService.java b/src/main/java/com/visualpathit/account/service/SecurityService.java deleted file mode 100644 index dbd4d9bc5..000000000 --- a/src/main/java/com/visualpathit/account/service/SecurityService.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.visualpathit.account.service; - -/** method for finding already added user !*/ -public interface SecurityService { - /** {@inheritDoc}} !*/ - String findLoggedInUsername(); - - void autologin(String username, String password); -} diff --git a/src/main/java/com/visualpathit/account/service/SecurityServiceImpl.java b/src/main/java/com/visualpathit/account/service/SecurityServiceImpl.java deleted file mode 100644 index 14fee640d..000000000 --- a/src/main/java/com/visualpathit/account/service/SecurityServiceImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.visualpathit.account.service; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication - .UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.stereotype.Service; -/** {@author imrant} !*/ -@Service -public class SecurityServiceImpl implements SecurityService { - /** authenticationManager !*/ - @Autowired - private AuthenticationManager authenticationManager; - /** userDetailsService !*/ - @Autowired - private UserDetailsService userDetailsService; - - /** Logger creation !*/ - private static final Logger logger = LoggerFactory - .getLogger(SecurityServiceImpl.class); - - @Override - public String findLoggedInUsername() { - Object userDetails = SecurityContextHolder.getContext() - .getAuthentication().getDetails(); - if (userDetails instanceof UserDetails) { - return ((UserDetails) userDetails).getUsername(); - } - - return null; - } - - @Override - public void autologin(final String username, final String password) { - UserDetails userDetails = userDetailsService.loadUserByUsername(username); - UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = - new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities()); - - authenticationManager.authenticate(usernamePasswordAuthenticationToken); - - if (usernamePasswordAuthenticationToken.isAuthenticated()) { - SecurityContextHolder.getContext() - .setAuthentication(usernamePasswordAuthenticationToken); - logger.debug(String.format("Auto login %s successfully!", username)); - } - } -} diff --git a/src/main/java/com/visualpathit/account/service/UserDetailsServiceImpl.java b/src/main/java/com/visualpathit/account/service/UserDetailsServiceImpl.java deleted file mode 100644 index 04c68ae80..000000000 --- a/src/main/java/com/visualpathit/account/service/UserDetailsServiceImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.visualpathit.account.service; - -import com.visualpathit.account.model.Role; -import com.visualpathit.account.model.User; -import com.visualpathit.account.repository.UserRepository; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.transaction.annotation.Transactional; - -import java.util.HashSet; -import java.util.Set; -/** {@author imrant} !*/ -public class UserDetailsServiceImpl implements UserDetailsService { - @Autowired - /** userRepository !*/ - private UserRepository userRepository; - - @Override - @Transactional(readOnly = true) - public UserDetails loadUserByUsername(final String username) - throws UsernameNotFoundException { - User user = userRepository.findByUsername(username); - - Set grantedAuthorities = new HashSet<>(); - for (Role role : user.getRoles()) { - grantedAuthorities.add(new SimpleGrantedAuthority(role.getName())); - } - - return new org.springframework.security.core - .userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities); - } -} diff --git a/src/main/java/com/visualpathit/account/service/UserService.java b/src/main/java/com/visualpathit/account/service/UserService.java deleted file mode 100644 index c85351e86..000000000 --- a/src/main/java/com/visualpathit/account/service/UserService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.visualpathit.account.service; - -import java.util.List; - -import com.visualpathit.account.model.User; - -/** {@author imrant}!*/ -public interface UserService { - /** {@inheritDoc}} !*/ - void save(User user); - /** {@inheritDoc}} !*/ - User findByUsername(String username); - User findById(long id); - /*public void updateUser(User user);*/ - public List getList(); -} diff --git a/src/main/java/com/visualpathit/account/service/UserServiceImpl.java b/src/main/java/com/visualpathit/account/service/UserServiceImpl.java deleted file mode 100644 index 2426b853f..000000000 --- a/src/main/java/com/visualpathit/account/service/UserServiceImpl.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.visualpathit.account.service; - -import com.visualpathit.account.model.User; -import com.visualpathit.account.repository.RoleRepository; -import com.visualpathit.account.repository.UserRepository; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.stereotype.Service; - -import java.util.HashSet; -import java.util.List; - -/** {@author imrant}!*/ -@Service -public class UserServiceImpl implements UserService { - @Autowired - /** userRepository !*/ - private UserRepository userRepository; - @Autowired - /** roleRepository !*/ - private RoleRepository roleRepository; - @Autowired - /** bCryptPasswordEncoder !*/ - private BCryptPasswordEncoder bCryptPasswordEncoder; - - @Override - public void save(final User user) { - user.setPassword(bCryptPasswordEncoder.encode(user.getPassword())); - user.setRoles(new HashSet<>(roleRepository.findAll())); - userRepository.save(user); - } - - @Override - public User findByUsername(final String username) { - return userRepository.findByUsername(username); - } - - @Override - public List getList() { - return userRepository.findAll(); - } - @Override - public User findById(long id){ - return userRepository.findOne(id); - } -} diff --git a/src/main/java/com/visualpathit/account/utils/ElasticsearchUtil.java b/src/main/java/com/visualpathit/account/utils/ElasticsearchUtil.java deleted file mode 100644 index 838fa536e..000000000 --- a/src/main/java/com/visualpathit/account/utils/ElasticsearchUtil.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.visualpathit.account.utils; - -import java.net.InetSocketAddress; - -import org.elasticsearch.client.transport.TransportClient; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.transport.InetSocketTransportAddress; -import org.elasticsearch.transport.client.PreBuiltTransportClient; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import com.visualpathit.account.beans.Components; -@Service -public class ElasticsearchUtil { - - private static Components object; - @Autowired - public void setComponents(Components object){ - ElasticsearchUtil.object = object; - - } - public static TransportClient trannsportClient() { - System.out.println(" elasticsearch client"); - String elasticsearchHost =object.getElasticsearchHost(); - String elasticsearchPort =object.getElasticsearchPort(); - String elasticsearchCluster =object.getElasticsearchCluster(); - String elasticsearchNode =object.getElasticsearchNode(); - System.out.println(" elasticsearchHost ........"+ elasticsearchHost); - System.out.println(" elasticsearchHost ........"+ elasticsearchPort); - TransportClient client = null; - try { - Settings settings = Settings.builder() - .put("cluster.name",elasticsearchCluster) - .put("node.name",elasticsearchNode) - .build(); - client = new PreBuiltTransportClient(settings) - .addTransportAddress( - new InetSocketTransportAddress( - new InetSocketAddress(elasticsearchHost, Integer.parseInt(elasticsearchPort)))); - - - } - catch (Exception e) { - e.printStackTrace(); - } - return client; - } -} diff --git a/src/main/java/com/visualpathit/account/utils/MemcachedUtils.java b/src/main/java/com/visualpathit/account/utils/MemcachedUtils.java deleted file mode 100644 index 98e539154..000000000 --- a/src/main/java/com/visualpathit/account/utils/MemcachedUtils.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.visualpathit.account.utils; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.concurrent.Future; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import com.visualpathit.account.beans.Components; -import com.visualpathit.account.model.User; - -import net.spy.memcached.MemcachedClient; -@Service -public class MemcachedUtils { - - private static Components object; - @Autowired - public void setComponents(Components object){ - MemcachedUtils.object = object; - } - public static String memcachedSetData(User user,String key){ - String Result = ""; - int expireTime = 900; - try{ - MemcachedClient mactiveClient = memcachedConnection(); - System.out.println("--------------------------------------------"); - System.out.println("Client is ::"+ mactiveClient.getStats()); - System.out.println("--------------------------------------------"); - Future future = mactiveClient.set(key,expireTime, user); - System.out.println("set status:" + future.get()); - Result =" Data is From DB and Data Inserted In Cache !!"; - mactiveClient.shutdown(); - - - } catch (Exception e) { - System.out.println( e.getMessage() ); - } - return Result; - } - public static User memcachedGetData(String key){ - String Result = ""; - User userData = null; - try{ - MemcachedClient mclient = memcachedConnection(); - System.out.println("--------------------------------------------"); - System.out.println("Client Status :: "+mclient.getStats()); - System.out.println("--------------------------------------------"); - userData = (User) mclient.get(key); - System.out.println("user value in cache - " + mclient.get(key)); - Result =" Data Retrieval From Cache !!"; - System.out.println(Result); - mclient.shutdown(); - - } catch (Exception e) { - System.out.println( e.getMessage() ); - } - return userData; - } - public static MemcachedClient memcachedConnection(){ - MemcachedClient mcconn = null; - boolean active = true; - String key="pid"; - String port = ""; - String activeHost =object.getActiveHost(); - String activePort =object.getActivePort(); - try{ - if(!activeHost.isEmpty() && !activePort.isEmpty() && active){ - mcconn = new MemcachedClient(new InetSocketAddress(activeHost,Integer.parseInt(activePort))); - for(SocketAddress innerKey:mcconn.getStats().keySet()){ - System.out.println("Connection SocketAddress ::" + innerKey); - //System.out.println("Connection port ::" + mcconn.getStats().get(innerKey).get(key)); - port = mcconn.getStats().get(innerKey).get(key); - } - if(port == null){ - System.out.println("Port::"+ port); - mcconn.shutdown(); - System.out.println("--------------------------------------------"); - System.out.println("Connection Failure By Active Host ::" + activeHost); - System.out.println("--------------------------------------------"); - mcconn = null; - active =false; - return mcconn = standByMemcachedConn(); - } - if(!port.isEmpty()){ - System.out.println("--------------------------------------------"); - System.out.println("Connection to server sucessfull for active Host ::"+activeHost); - System.out.println("--------------------------------------------"); - active =true; - return mcconn; - } - }else if(!activeHost.isEmpty() && !activePort.isEmpty() && !active){ - return mcconn = standByMemcachedConn(); - }else { - System.out.println("--------------------------------------------"); - System.out.println("Connection to Failure Due to Incorrect or Empty Host:: "); - System.out.println("--------------------------------------------"); - } - } - catch (Exception e) { - System.out.println( e.getMessage() ); - } - return mcconn; - } - public static MemcachedClient standByMemcachedConn(){ - MemcachedClient mcconn = null; - String port = ""; - String key="pid"; - String standByHost = object.getStandByHost(); - String standByPort = object.getStandByPort(); - try{ - if(!standByHost.isEmpty() && !standByPort.isEmpty() && mcconn == null && port.isEmpty()){ - mcconn = new MemcachedClient(new InetSocketAddress(standByHost,Integer.parseInt(standByPort))); - for(SocketAddress innerKey:mcconn.getStats().keySet()){ - port = mcconn.getStats().get(innerKey).get(key); - } - if(!port.isEmpty()){ - System.out.println("--------------------------------------------"); - System.out.println("Connection to server sucessful by StandBy Host::" + standByHost); - System.out.println("--------------------------------------------"); - return mcconn; - }else { - mcconn.shutdown(); - System.out.println("--------------------------------------------"); - System.out.println("Connection Failure By StandBy Host ::" +standByHost); - System.out.println("--------------------------------------------"); - } - } - }catch (Exception e) { - System.out.println( e.getMessage() ); - } - return mcconn; - } -} diff --git a/src/main/java/com/visualpathit/account/utils/RabbitMqUtil.java b/src/main/java/com/visualpathit/account/utils/RabbitMqUtil.java deleted file mode 100644 index cbef39120..000000000 --- a/src/main/java/com/visualpathit/account/utils/RabbitMqUtil.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.visualpathit.account.utils; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import com.visualpathit.account.beans.Components; - -@Service -public class RabbitMqUtil { - private static Components object; - - public RabbitMqUtil() {} - - @Autowired - public void setComponents(Components object) { - RabbitMqUtil.object = object; - } - - public static String getRabbitMqHost() { return object.getRabbitMqHost(); } - - public static String getRabbitMqPort() { - return object.getRabbitMqPort(); - } - - public static String getRabbitMqUser() { return object.getRabbitMqUser(); } - - public static String getRabbitMqPassword() { - return object.getRabbitMqPassword(); - } -} \ No newline at end of file diff --git a/src/main/java/com/visualpathit/account/validator/UserValidator.java b/src/main/java/com/visualpathit/account/validator/UserValidator.java deleted file mode 100644 index a6185b33a..000000000 --- a/src/main/java/com/visualpathit/account/validator/UserValidator.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.visualpathit.account.validator; - -import com.visualpathit.account.model.User; -import com.visualpathit.account.service.UserService; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.validation.Errors; -import org.springframework.validation.ValidationUtils; -import org.springframework.validation.Validator; - -@Component -public class UserValidator implements Validator { - @Autowired - private UserService userService; - - @Override - public boolean supports(Class aClass) { - return User.class.equals(aClass); - } - - @Override - public void validate(Object o, Errors errors) { - User user = (User) o; - - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "NotEmpty"); - if (user.getUsername().length() < 6 || user.getUsername().length() > 32) { - errors.rejectValue("username", "Size.userForm.username"); - } - if (userService.findByUsername(user.getUsername()) != null) { - errors.rejectValue("username", "Duplicate.userForm.username"); - } - - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "NotEmpty"); - if (user.getPassword().length() < 8 || user.getPassword().length() > 32) { - errors.rejectValue("password", "Size.userForm.password"); - } - - if (!user.getPasswordConfirm().equals(user.getPassword())) { - errors.rejectValue("passwordConfirm", "Diff.userForm.passwordConfirm"); - } - } -} diff --git a/src/main/resources/accountsdb.sql b/src/main/resources/accountsdb.sql deleted file mode 100644 index d224d810f..000000000 --- a/src/main/resources/accountsdb.sql +++ /dev/null @@ -1,104 +0,0 @@ --- MySQL dump 10.13 Distrib 5.7.18, for Linux (x86_64) --- --- Host: localhost Database: accounts --- ------------------------------------------------------ --- Server version 5.7.18-0ubuntu0.16.10.1 - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; - --- --- Table structure for table `role` --- - -DROP TABLE IF EXISTS `role`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `role` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(45) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `role` --- - -LOCK TABLES `role` WRITE; -/*!40000 ALTER TABLE `role` DISABLE KEYS */; -INSERT INTO `role` VALUES (1,'ROLE_USER'); -/*!40000 ALTER TABLE `role` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `user` --- - -DROP TABLE IF EXISTS `user`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `user` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `username` varchar(255) DEFAULT NULL, - `userEmail` varchar(255) DEFAULT NULL, - `password` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `user` --- - -LOCK TABLES `user` WRITE; -/*!40000 ALTER TABLE `user` DISABLE KEYS */; -INSERT INTO `user` VALUES (4,'admin_vp','admin@visualpathit.com','$2a$11$DSEIKJNrgPjG.iCYUwErvOkREtC67mqzQ.ogkZbc/KOW1OPOpZfY6'); -/*!40000 ALTER TABLE `user` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `user_role` --- - -DROP TABLE IF EXISTS `user_role`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `user_role` ( - `user_id` int(11) NOT NULL, - `role_id` int(11) NOT NULL, - PRIMARY KEY (`user_id`,`role_id`), - KEY `fk_user_role_roleid_idx` (`role_id`), - CONSTRAINT `fk_user_role_roleid` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `fk_user_role_userid` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `user_role` --- - -LOCK TABLES `user_role` WRITE; -/*!40000 ALTER TABLE `user_role` DISABLE KEYS */; -INSERT INTO `user_role` VALUES (4,1); -/*!40000 ALTER TABLE `user_role` ENABLE KEYS */; -UNLOCK TABLES; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; - -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; - --- Dump completed on 2017-08-28 10:50:51 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index c04343d72..000000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,25 +0,0 @@ -#JDBC Configutation for Database Connection -jdbc.driverClassName=com.mysql.jdbc.Driver -jdbc.url=jdbc:mysql://db01:3306/accounts?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull -jdbc.username=admin -jdbc.password=admin123 - -#Memcached Configuration For Active and StandBy Host -#For Active Host -memcached.active.host=mc01 -memcached.active.port=11211 -#For StandBy Host -memcached.standBy.host=127.0.0.2 -memcached.standBy.port=11211 - -#RabbitMq Configuration -rabbitmq.address=rmq01 -rabbitmq.port=5672 -rabbitmq.username=test -rabbitmq.password=test - -#Elasticesearch Configuration -elasticsearch.host =192.168.1.85 -elasticsearch.port =9300 -elasticsearch.cluster=vprofile -elasticsearch.node=vprofilenode diff --git a/src/main/resources/db_backup.sql b/src/main/resources/db_backup.sql deleted file mode 100644 index 2f17a4df3..000000000 --- a/src/main/resources/db_backup.sql +++ /dev/null @@ -1,133 +0,0 @@ --- MySQL dump 10.13 Distrib 5.7.18, for Linux (x86_64) --- --- Host: localhost Database: accounts --- ------------------------------------------------------ --- Server version 5.7.18-0ubuntu0.16.10.1 - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; - --- --- Table structure for table `role` --- - -DROP TABLE IF EXISTS `role`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `role` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(45) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `role` --- - -LOCK TABLES `role` WRITE; -/*!40000 ALTER TABLE `role` DISABLE KEYS */; -INSERT INTO `role` VALUES (1,'ROLE_USER'); -/*!40000 ALTER TABLE `role` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `user` --- - -DROP TABLE IF EXISTS `user`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `user` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `username` varchar(255) DEFAULT NULL, - `userEmail` varchar(255) DEFAULT NULL, - `profileImg` varchar(255) DEFAULT NULL, - `profileImgPath` varchar(255) DEFAULT NULL, - `dateOfBirth` varchar(255) DEFAULT NULL, - `fatherName` varchar(255) DEFAULT NULL, - `motherName` varchar(255) DEFAULT NULL, - `gender` varchar(255) DEFAULT NULL, - `maritalStatus` varchar(255) DEFAULT NULL, - `permanentAddress` varchar(255) DEFAULT NULL, - `tempAddress` varchar(255) DEFAULT NULL, - `primaryOccupation` varchar(255) DEFAULT NULL, - `secondaryOccupation` varchar(255) DEFAULT NULL, - `skills` varchar(255) DEFAULT NULL, - `phoneNumber` varchar(255) DEFAULT NULL, - `secondaryPhoneNumber` varchar(255) DEFAULT NULL, - `nationality` varchar(255) DEFAULT NULL, - `language` varchar(255) DEFAULT NULL, - `workingExperience` varchar(255) DEFAULT NULL, - `password` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `user` --- - -LOCK TABLES `user` WRITE; -/*!40000 ALTER TABLE `user` DISABLE KEYS */; - -INSERT INTO `user` VALUES (7,'admin_vp','admin@hkhinfo.com',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'$2a$11$0a7VdTr4rfCQqtsvpng6GuJnzUmQ7gZiHXgzGPgm5hkRa3avXgBLK') -,(8,'Abrar Nirban','abrar.nirban74@gmail.com',NULL,NULL,'27/01/2002','A nirban','T nirban','male','unMarried','Dubai,UAE','Dubai,UAE','Software Engineer','Software Engineer','Java HTML CSS ','8888888888','8888888888','Indian','english','2 ','$2a$11$UgG9TkHcgl02LxlqxRHYhOf7Xv4CxFmFEgS0FpUdk42OeslI.6JAW'), -(9,'Amayra Fatima','amayra@gmail.com',NULL,NULL,'20/06/1993','K','L','female','unMarried','Dubai,UAE','Dubai,UAE','Software Engineer','Software Engineer','Java HTML CSS ','9999999999','9999999999','India','english','5','$2a$11$gwvsvUrFU.YirMM1Yb7NweFudLUM91AzH5BDFnhkNzfzpjG.FplYO'), -(10,'Aron','aron.DSilva@gmail.com',NULL,NULL,'27/01/2002','M nirban','R nirban','male','unMarried','Dubai,UAE','Dubai,UAE','Software Engineer','Software Engineer','Java HTML CSS ','7777777777','777777777','India','english','7','$2a$11$6oZEgfGGQAH23EaXLVZ2WOSKxcEJFnBSw2N2aghab0s2kcxSQwjhC'), -(11,'Kiran Kumar','kiran@gmail.com',NULL,NULL,'8/12/1993','K K','RK','male','unMarried','SanFrancisco','James Street','Software Engineer','Software Engineer','Java HTML CSS ','1010101010','1010101010','India','english','10','$2a$11$EXwpna1MlFFlKW5ut1iVi.AoeIulkPPmcOHFO8pOoQt1IYU9COU0m'), -(12,'Balbir Singh','balbir@gmail.com',NULL,NULL,'20/06/1993','balbir RK','balbir AK','male','unMarried','SanFrancisco','US','Software Engineer','Software Engineer','Java HTML CSS AWS','8888888111','8888888111','India','english','8','$2a$11$pzWNzzR.HUkHzz2zhAgqOeCl0WaTgY33NxxJ7n0l.rnEqjB9JO7vy'), -(4,'Hibo Prince','hibo.prince@gmail.com',NULL,NULL,'6/09/2000','Abara','Queen','male','unMarried','Electronic City,UAE','Electronic City,UAE','Tester','Freelancing','Python PHP ','9146389863','9146389871','Indian','hindi','3 ','$2a$11$UgG9TkHcgl02LxlqxRHYhOf7Xv4CxFmFEgS0FpUdk42OeslI.6JAR'), -(5,'Aejaaz Habeeb','aejaaz.habeeb@gmail.com',NULL,NULL,'16/02/2001','Imran','Ziya','male','unMarried','AbuDhabi,UAE','AbuDhabi,UAE','Developer','Developer','Azure Devops ','9566489863','9566489863','Indian','hindi','4 ','$2a$11$UgG9TkHcgl02LxlqxRHYhOf7Xv4CxFmFEgS0FpUdk42OeslI.6JAR'), -(6,'Jackie','jackie.chan@gmail.com',NULL,NULL,'28/09/1992','Charles','Chan','male','Married','HongKong,China','HongKong,China','MartialArtist','MartialArtist','KungFu ','9246488863','9246488863','Chinese','Mandrian','1 ','$2a$11$UgG9TkHcgl02LxlqxRHYhOf7Xv4CxFmFEgS0FpUdk42OeslI.6RAR'), -(13,'Srinath Goud','sgoud@gmail.com',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'$2a$11$6BSmYPrT8I8b9yHmx.uTRu/QxnQM2vhZYQa8mR33aReWA4WFihyGK'); - - -/*!40000 ALTER TABLE `user` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `user_role` --- - -DROP TABLE IF EXISTS `user_role`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `user_role` ( - `user_id` int(11) NOT NULL, - `role_id` int(11) NOT NULL, - PRIMARY KEY (`user_id`,`role_id`), - KEY `fk_user_role_roleid_idx` (`role_id`), - CONSTRAINT `fk_user_role_roleid` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `fk_user_role_userid` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `user_role` --- - -LOCK TABLES `user_role` WRITE; -/*!40000 ALTER TABLE `user_role` DISABLE KEYS */; -INSERT INTO `user_role` VALUES (4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1); -/*!40000 ALTER TABLE `user_role` ENABLE KEYS */; -UNLOCK TABLES; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; - -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; - --- Dump completed on 2023-21-06 05:49:31 diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml deleted file mode 100644 index 35b81df4d..000000000 --- a/src/main/resources/logback.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - %date{HH:mm:ss.SSS} [%thread] %-5level %logger{15}#%line %msg\n - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/validation.properties b/src/main/resources/validation.properties deleted file mode 100644 index 0453cdd3a..000000000 --- a/src/main/resources/validation.properties +++ /dev/null @@ -1,5 +0,0 @@ -NotEmpty=This field is required. -Size.userForm.username=Please use between 6 and 32 characters. -Duplicate.userForm.username= User has already taken this Username. -Size.userForm.password=Try one with at least 8 characters. -Diff.userForm.passwordConfirm=These passwords don't match. \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/appconfig-data.xml b/src/main/webapp/WEB-INF/appconfig-data.xml deleted file mode 100644 index 7be0032b5..000000000 --- a/src/main/webapp/WEB-INF/appconfig-data.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - org.hibernate.dialect.MySQL5Dialect - true - - - - - - - - - - - - - - - - - diff --git a/src/main/webapp/WEB-INF/appconfig-mvc.xml b/src/main/webapp/WEB-INF/appconfig-mvc.xml deleted file mode 100644 index 58f404dc5..000000000 --- a/src/main/webapp/WEB-INF/appconfig-mvc.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - classpath:validation - - - - - - /WEB-INF/views/ - - - .jsp - - - - - - - - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/appconfig-rabbitmq.xml b/src/main/webapp/WEB-INF/appconfig-rabbitmq.xml deleted file mode 100644 index 989faec37..000000000 --- a/src/main/webapp/WEB-INF/appconfig-rabbitmq.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/appconfig-root.xml b/src/main/webapp/WEB-INF/appconfig-root.xml deleted file mode 100644 index 064cc5e7a..000000000 --- a/src/main/webapp/WEB-INF/appconfig-root.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/appconfig-security.xml b/src/main/webapp/WEB-INF/appconfig-security.xml deleted file mode 100644 index 5e2acf137..000000000 --- a/src/main/webapp/WEB-INF/appconfig-security.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/webapp/WEB-INF/views/elasticeSearchRes.jsp b/src/main/webapp/WEB-INF/views/elasticeSearchRes.jsp deleted file mode 100644 index 7a3161618..000000000 --- a/src/main/webapp/WEB-INF/views/elasticeSearchRes.jsp +++ /dev/null @@ -1,19 +0,0 @@ - -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> - <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> - - - - - -vp-elasticsearch - - -

Data is ${result} into Elasticsearch

-

Please go to elastic search dash board and verify link ip:9200/users/user/id

- - - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/index_home.jsp b/src/main/webapp/WEB-INF/views/index_home.jsp deleted file mode 100644 index 4579f61fe..000000000 --- a/src/main/webapp/WEB-INF/views/index_home.jsp +++ /dev/null @@ -1,156 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> - - - - - - - - - -
-
- -
-
- -
- Architecture -
-

DevOps

-
-
-
-

-

Keep Learning ..

-

Learning is a Treasure that will follow it's Owner Everywhere..

-
- -
- - -
-

TECHNOLOGIES

-
- -
-
-
- DevOps -
-
-
-
- DevOps -
-
-
-
- DevOps -
-
-
-
- DevOps -
-
-
- -
-
-
- DevOps -
-
-
-
- DevOps -
-
-
-
- DevOps -
-
-
-
- DevOps -
-
-
- - -
-

ABOUT

-
-

VisualPath is an IT Educational Institute.Established in 2001,and Institute offers world class quality of education and wide range of courses.VisualPath Institute has a dedicated placement team to help students get job placement in various IT job roles with major companies. -

-

Address: Flat no: 205, 2nd Floor,NILGIRI Block,Aditya Encalve,Ameerpet, Hyderabad-16

-

Ph No: +91-9704455959,9618245689

-

E-Mail ID : visualpath999@gmail.com

-
-
- - -
-

CONTACT

-

Lets get in touch and talk about your and our next project.

-
- - - - - -
-
- - -
- - - - - - - - - - - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/login.jsp b/src/main/webapp/WEB-INF/views/login.jsp deleted file mode 100644 index 0a41ac1c4..000000000 --- a/src/main/webapp/WEB-INF/views/login.jsp +++ /dev/null @@ -1,95 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> - - - - - - - - - - - - - - LOGIN - - - - - - - - Welcome - - - - - - - - -
-
- -
-
-
- - - -
- - - - - diff --git a/src/main/webapp/WEB-INF/views/rabbitmq.jsp b/src/main/webapp/WEB-INF/views/rabbitmq.jsp deleted file mode 100644 index 2220694d4..000000000 --- a/src/main/webapp/WEB-INF/views/rabbitmq.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> - - - - -Rabbitmq - - -

Rabbitmq initiated

-

Generated 2 Connections

-

6 Chanels 1 Exchage and 2 Que

- - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/registration.jsp b/src/main/webapp/WEB-INF/views/registration.jsp deleted file mode 100644 index be726ccd9..000000000 --- a/src/main/webapp/WEB-INF/views/registration.jsp +++ /dev/null @@ -1,112 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> - - - - - - - - - - - - - - SIGNUP - - - - - - - - - - - - -
-
- -
-
- -
- - - -
- - - - - diff --git a/src/main/webapp/WEB-INF/views/upload.jsp b/src/main/webapp/WEB-INF/views/upload.jsp deleted file mode 100644 index 3e52f833a..000000000 --- a/src/main/webapp/WEB-INF/views/upload.jsp +++ /dev/null @@ -1,56 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ page session="false" %> - - - -Upload File Request Page - - - - - - -
-
-

Upload Image

-
- ${pageContext.request.userPrincipal.name}
-
- - -
-
- - -
-
- - - -
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/user.jsp b/src/main/webapp/WEB-INF/views/user.jsp deleted file mode 100644 index 480bf6e24..000000000 --- a/src/main/webapp/WEB-INF/views/user.jsp +++ /dev/null @@ -1,163 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> - <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - -UserData - - - - - - - - - - - -
-
- -
-
- - -
-
-

${{Result}} Back

-

User Primary Details

- - - - - - - - - - - - - - - - - -
IdNameFather's NameMother's NameEmailPhone Number
- - - - - - - - - - - -
-

User Extra Details

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Date Of BirthGenderMarital StatusPermanent AddressTemporary AddressPrimary OccupationSecondary OccupationSkillsSecondary PhoneNumberNationalityLanguageWorking Experience
- - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/userList.jsp b/src/main/webapp/WEB-INF/views/userList.jsp deleted file mode 100644 index 1973e91be..000000000 --- a/src/main/webapp/WEB-INF/views/userList.jsp +++ /dev/null @@ -1,93 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - allUser - - - - - - - - - - - - -
-
- -
-
- -
-
-

Users List

- - - - - - - - - - - - -
User NameUser Id
- - - -
-
-
- - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/userUpdate.jsp b/src/main/webapp/WEB-INF/views/userUpdate.jsp deleted file mode 100644 index 7ae381668..000000000 --- a/src/main/webapp/WEB-INF/views/userUpdate.jsp +++ /dev/null @@ -1,314 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> - - - - - - - - - - - - - - update user - - - - - - - - - -
-
-
- -
- -
- - Name : -
-
-
- -
- -
-
-
-
-
- - Email : -
-
-
- -
- -
-
-
-
-
- - Date Of Birth : -
-
-
- -
- -
-
-
-
-
- - Father's Name : -
-
-
- -
- -
-
-
-
-
- - Mother's Name : -
-
-
- -
- -
-
-
-
-
- - Gender -
- - - Male - - - - Female - - - - Other - -
-
-
-
- - Marital Status: -
- - - Married - - - - Unmarried - -
-
-
- -
- - Permanent Address : -
-
-
- -
- -
-
-
-
-
- - Temporary Address : -
-
-
- -
- -
-
-
-
-
- - Primary Occupation : -
-
-
- -
- -
-
-
-
-
- - Secondary Occupation : -
-
-
- -
- -
-
-
-
-
- - Skills : -
-
-
- -
- -
-
-
-
-
- - Phone Number : -
-
-
- -
- -
-
-
-
-
- - Secondary PhoneNumber : -
-
-
- -
- -
-
-
-
-
- - Nationality : -
-
-
- -
- -
-
-
-
-
- - Mother Tongue -
- - - English - - - - Spanish - - - - German - - - - Hindi - - - - Other - -
-
-
-
- - Work Experience : -
-
-
- -
- -
-
-
-
-
- -
- - Cancel -
-
-
-
-
-
-
- - - - diff --git a/src/main/webapp/WEB-INF/views/welcome.jsp b/src/main/webapp/WEB-INF/views/welcome.jsp deleted file mode 100644 index 1d5d193ff..000000000 --- a/src/main/webapp/WEB-INF/views/welcome.jsp +++ /dev/null @@ -1,486 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - - - - - - - Welcome - - - - - - - -
-
- -
.
- -
-
-
- -

${pageContext.request.userPrincipal.name}   ${pageContext.request.userPrincipal.name}@visualpath.co.in

- -
-

- #DevOps #Continuous Integration #Continuous Delivery #Automation - - All Users - - - RabbitMq - - - Elasticsearch - -


- - Posts - Photos 42 - Contacts 42 - - - - - - -
-
-
- -
-
-
- - - -
-

${pageContext.request.userPrincipal.name} 42 minutes ago

- - - -
-
-

"The Key to DevOps Success."

-

The Key to DevOps Success" Collaboration". Collaboration is essential to DevOps,yet how to do it is often unclear with many teams falling back on ineffective conference calls, instant messaging, documents, and SharePoint sites. In this keynote,we will share a vision for a next generation DevOps where collaboration, continuous documentation, and knowledge capture are combined with automation toolchains to enable rapid innovation and deployment.

-
-
-
- -
-

Public

-
-
-
-
-
-
- - - -
-
- -
-
-
-
- -
-
-
- - - -
-

${pageContext.request.userPrincipal.name} 42 minutes ago

- - - -
-
-
-
-
- - - -
-

Abrar nirban about 10 hours ago

-
-
-

What are DevOps skills?

-

Our respondents identified the top three skill areas for DevOps staff:

-

1) Coding or scripting 2)Process re-engineering 3)Communicating and collaborating with others Extensive knowledge of software build cycles 4)Experience deploying code 5)Experience in software architecture 6)Familiarity with application programming 7)Database management 8)System design.

-

These skills all point to a growing recognition that software is not written in the old way anymore. Where software used to be written from scratch in a highly complex and lengthy process, creating new products is now often a matter of choosing open source components and stitching them together with code. The complexity of todays software lies less in the authoring, and more in ensuring that the new software will work across a diverse set of operating systems and platforms right away. Likewise, testing and deployment are now done much more frequently. That is, they can be more frequent,if developers communicate early and regularly with the operations team, and if ops people bring their knowledge of the production environment to design of testing and staging environments.

-

Demand for people with DevOps skills is growing rapidly because businesses get great results from DevOps. Organizations using DevOps practices are overwhelmingly high-functioning: They deploy code up to 30 times more frequently than their competitors.

-
-
-
-
-
-
- -
-

Public

-
-
-
-
-
-
- - - -
-
- -
-
-
-
- -
-
-
- - - -
-

${pageContext.request.userPrincipal.name} 42 minutes ago

- - - -
-
-

" Manager Reaction On Your Work without DevOps "

- -


# I want DevOps # DevOps..

-
-
-
- -
-

Public via mobile

-
-
-
-
-
-
- - - -
-
- -
-
-
-
- -
-
-
- - - -
-

${pageContext.request.userPrincipal.name} 42 minutes ago

- - - -
-
-

"Feeling Happy to be a DevOps."

-
-
-
- -
-

Limited

-
-
-
-
-
- Show 12 more comments -
-
-
-
-
- - - -
-

Kiran Kumar

-
-
-

DevOps has significant importance to any company delivering software or technical services today.Defining DevOps is trickier than you would think, primarily because of its wide usage. It is essentially shorthand, and nothing more than that, for a lean approach to software delivery.

-
12 minutes ago -
-
-
-
-
-
-
-
-
- - - -
-

Mi Chleen

-
-
-

The secret to DevOps maturity is not technology or process, but people. It takes engaged leadership and all for one cooperation to achieve the kind of results that lead companies to superior IT performance. High-performing DevOps teams can recover 168 times faster from failures and have 60 times fewer failures due to changes, according to the 2015 State of DevOps Report by Puppet Labs. High-performing teams also release code at significantly increasing velocity as their teams grow in size, approaching three deploys per day per developer, for teams of around 1000 developers.

-
9 minutes ago -
-
-
-
-
-
-
-
-
- - - -
-

${pageContext.request.userPrincipal.name}

-
-
-

At a time when the speed of application development is vital to commercial success, the DevOps methodology based on communication, collaboration, integration and automation has become one of the biggest IT moves around. However, it is more than just a business philosophy;to do it right requires genuine infrastructure investment and development.

-
2 minutes ago -
-
-
-
-
-
-
- - - -
-
- -
-
-
-
-
-
-
-
- -
- -
-
-
- - - - - - diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 0f82cbdbf..000000000 --- a/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - Account Registration Web Application - - contextConfigLocation - /WEB-INF/appconfig-root.xml - - - springSecurityFilterChain - org.springframework.web.filter.DelegatingFilterProxy - - - springSecurityFilterChain - /* - - - dispatcher - org.springframework.web.servlet.DispatcherServlet - - contextConfigLocation - - - 1 - - - dispatcher - / - - - org.springframework.web.context.ContextLoaderListener - - \ No newline at end of file diff --git a/src/main/webapp/resources/Images/background.png b/src/main/webapp/resources/Images/background.png deleted file mode 100644 index e2d5dd3a9..000000000 Binary files a/src/main/webapp/resources/Images/background.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/header.jpg b/src/main/webapp/resources/Images/header.jpg deleted file mode 100644 index 702a1c3f3..000000000 Binary files a/src/main/webapp/resources/Images/header.jpg and /dev/null differ diff --git a/src/main/webapp/resources/Images/hkh-infotech-logo.png b/src/main/webapp/resources/Images/hkh-infotech-logo.png deleted file mode 100644 index 81e4ff1d5..000000000 Binary files a/src/main/webapp/resources/Images/hkh-infotech-logo.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/login-background.png b/src/main/webapp/resources/Images/login-background.png deleted file mode 100644 index 284d84a02..000000000 Binary files a/src/main/webapp/resources/Images/login-background.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/technologies/Ansible_logo.png b/src/main/webapp/resources/Images/technologies/Ansible_logo.png deleted file mode 100644 index c72d308c4..000000000 Binary files a/src/main/webapp/resources/Images/technologies/Ansible_logo.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/technologies/Vagrant.png b/src/main/webapp/resources/Images/technologies/Vagrant.png deleted file mode 100644 index 863621614..000000000 Binary files a/src/main/webapp/resources/Images/technologies/Vagrant.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/technologies/aws.png b/src/main/webapp/resources/Images/technologies/aws.png deleted file mode 100644 index 205c991ef..000000000 Binary files a/src/main/webapp/resources/Images/technologies/aws.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/technologies/docker.png b/src/main/webapp/resources/Images/technologies/docker.png deleted file mode 100644 index 580f2a1da..000000000 Binary files a/src/main/webapp/resources/Images/technologies/docker.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/technologies/git.jpg b/src/main/webapp/resources/Images/technologies/git.jpg deleted file mode 100644 index 037bb9aa0..000000000 Binary files a/src/main/webapp/resources/Images/technologies/git.jpg and /dev/null differ diff --git a/src/main/webapp/resources/Images/technologies/jenkins.png b/src/main/webapp/resources/Images/technologies/jenkins.png deleted file mode 100644 index 356ec31f2..000000000 Binary files a/src/main/webapp/resources/Images/technologies/jenkins.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/technologies/puppet.jpg b/src/main/webapp/resources/Images/technologies/puppet.jpg deleted file mode 100644 index e4d3fed6d..000000000 Binary files a/src/main/webapp/resources/Images/technologies/puppet.jpg and /dev/null differ diff --git a/src/main/webapp/resources/Images/technologies/python-logo.png b/src/main/webapp/resources/Images/technologies/python-logo.png deleted file mode 100644 index 738f6ed41..000000000 Binary files a/src/main/webapp/resources/Images/technologies/python-logo.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/user.png b/src/main/webapp/resources/Images/user.png deleted file mode 100644 index 6a3c4e24b..000000000 Binary files a/src/main/webapp/resources/Images/user.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/user/giphy.gif b/src/main/webapp/resources/Images/user/giphy.gif deleted file mode 100644 index 8cf166f7c..000000000 Binary files a/src/main/webapp/resources/Images/user/giphy.gif and /dev/null differ diff --git a/src/main/webapp/resources/Images/user/logo.png b/src/main/webapp/resources/Images/user/logo.png deleted file mode 100644 index c18fc6eba..000000000 Binary files a/src/main/webapp/resources/Images/user/logo.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/user/user.png b/src/main/webapp/resources/Images/user/user.png deleted file mode 100644 index 6a3c4e24b..000000000 Binary files a/src/main/webapp/resources/Images/user/user.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/user/user2.png b/src/main/webapp/resources/Images/user/user2.png deleted file mode 100644 index 9c600c3a6..000000000 Binary files a/src/main/webapp/resources/Images/user/user2.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/user/user3.png b/src/main/webapp/resources/Images/user/user3.png deleted file mode 100644 index 079ecba2e..000000000 Binary files a/src/main/webapp/resources/Images/user/user3.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/visualpath.png b/src/main/webapp/resources/Images/visualpath.png deleted file mode 100644 index 9c86eddbb..000000000 Binary files a/src/main/webapp/resources/Images/visualpath.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/visualpathlogo2.png b/src/main/webapp/resources/Images/visualpathlogo2.png deleted file mode 100644 index 4bd9e01fb..000000000 Binary files a/src/main/webapp/resources/Images/visualpathlogo2.png and /dev/null differ diff --git a/src/main/webapp/resources/Images/visualpathlogo3.png b/src/main/webapp/resources/Images/visualpathlogo3.png deleted file mode 100644 index 16ce5de36..000000000 Binary files a/src/main/webapp/resources/Images/visualpathlogo3.png and /dev/null differ diff --git a/src/main/webapp/resources/css/bootstrap.min.css b/src/main/webapp/resources/css/bootstrap.min.css deleted file mode 100644 index 5fd698e7b..000000000 --- a/src/main/webapp/resources/css/bootstrap.min.css +++ /dev/null @@ -1,7199 +0,0 @@ -/*! - * Bootstrap v3.3.5 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ -html { - font-family: sans-serif; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100% -} - -body { - margin: 0 -} - -article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { - display: block -} - -audio, canvas, progress, video { - display: inline-block; - vertical-align: baseline -} - -audio:not([controls]) { - display: none; - height: 0 -} - -[hidden], template { - display: none -} - -a { - background-color: transparent -} - -a:active, a:hover { - outline: 0 -} - -abbr[title] { - border-bottom: 1px dotted -} - -b, strong { - font-weight: 700 -} - -dfn { - font-style: italic -} - -h1 { - margin: .67em 0; - font-size: 2em -} - -mark { - color: #000; - background: #ff0 -} - -small { - font-size: 80% -} - -sub, sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline -} - -sup { - top: -.5em -} - -sub { - bottom: -.25em -} - -img { - border: 0 -} - -svg:not(:root) { - overflow: hidden -} - -figure { - margin: 1em 40px -} - -hr { - height: 0; - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box -} - -pre { - overflow: auto -} - -code, kbd, pre, samp { - font-family: monospace, monospace; - font-size: 1em -} - -button, input, optgroup, select, textarea { - margin: 0; - font: inherit; - color: inherit -} - -button { - overflow: visible -} - -button, select { - text-transform: none -} - -button, html input[type=button], input[type=reset], input[type=submit] { - -webkit-appearance: button; - cursor: pointer -} - -button[disabled], html input[disabled] { - cursor: default -} - -button::-moz-focus-inner, input::-moz-focus-inner { - padding: 0; - border: 0 -} - -input { - line-height: normal -} - -input[type=checkbox], input[type=radio] { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - padding: 0 -} - -input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer-spin-button { - height: auto -} - -input[type=search] { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; - -webkit-appearance: textfield -} - -input[type=search]::-webkit-search-cancel-button, input[type=search]::-webkit-search-decoration { - -webkit-appearance: none -} - -fieldset { - padding: .35em .625em .75em; - margin: 0 2px; - border: 1px solid silver -} - -legend { - padding: 0; - border: 0 -} - -textarea { - overflow: auto -} - -optgroup { - font-weight: 700 -} - -table { - border-spacing: 0; - border-collapse: collapse -} - -td, th { - padding: 0 -} - -/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ -@media print { - *, :after, :before { - color: #000 !important; - text-shadow: none !important; - background: 0 0 !important; - -webkit-box-shadow: none !important; - box-shadow: none !important - } - - a, a:visited { - text-decoration: underline - } - - a[href]:after { - content: " (" attr(href) ")" - } - - abbr[title]:after { - content: " (" attr(title) ")" - } - - a[href^="javascript:"]:after, a[href^="#"]:after { - content: "" - } - - blockquote, pre { - border: 1px solid #999; - page-break-inside: avoid - } - - thead { - display: table-header-group - } - - img, tr { - page-break-inside: avoid - } - - img { - max-width: 100% !important - } - - h2, h3, p { - orphans: 3; - widows: 3 - } - - h2, h3 { - page-break-after: avoid - } - - .navbar { - display: none - } - - .btn > .caret, .dropup > .btn > .caret { - border-top-color: #000 !important - } - - .label { - border: 1px solid #000 - } - - .table { - border-collapse: collapse !important - } - - .table td, .table th { - background-color: #fff !important - } - - .table-bordered td, .table-bordered th { - border: 1px solid #ddd !important - } -} - -@font-face { - font-family: 'Glyphicons Halflings'; - src: url(../fonts/glyphicons-halflings-regular.eot); - src: url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'), url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'), url(../fonts/glyphicons-halflings-regular.woff) format('woff'), url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'), url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg') -} - -.glyphicon { - position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: 400; - line-height: 1; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale -} - -.glyphicon-asterisk:before { - content: "\2a" -} - -.glyphicon-plus:before { - content: "\2b" -} - -.glyphicon-eur:before, .glyphicon-euro:before { - content: "\20ac" -} - -.glyphicon-minus:before { - content: "\2212" -} - -.glyphicon-cloud:before { - content: "\2601" -} - -.glyphicon-envelope:before { - content: "\2709" -} - -.glyphicon-pencil:before { - content: "\270f" -} - -.glyphicon-glass:before { - content: "\e001" -} - -.glyphicon-music:before { - content: "\e002" -} - -.glyphicon-search:before { - content: "\e003" -} - -.glyphicon-heart:before { - content: "\e005" -} - -.glyphicon-star:before { - content: "\e006" -} - -.glyphicon-star-empty:before { - content: "\e007" -} - -.glyphicon-user:before { - content: "\e008" -} - -.glyphicon-film:before { - content: "\e009" -} - -.glyphicon-th-large:before { - content: "\e010" -} - -.glyphicon-th:before { - content: "\e011" -} - -.glyphicon-th-list:before { - content: "\e012" -} - -.glyphicon-ok:before { - content: "\e013" -} - -.glyphicon-remove:before { - content: "\e014" -} - -.glyphicon-zoom-in:before { - content: "\e015" -} - -.glyphicon-zoom-out:before { - content: "\e016" -} - -.glyphicon-off:before { - content: "\e017" -} - -.glyphicon-signal:before { - content: "\e018" -} - -.glyphicon-cog:before { - content: "\e019" -} - -.glyphicon-trash:before { - content: "\e020" -} - -.glyphicon-home:before { - content: "\e021" -} - -.glyphicon-file:before { - content: "\e022" -} - -.glyphicon-time:before { - content: "\e023" -} - -.glyphicon-road:before { - content: "\e024" -} - -.glyphicon-download-alt:before { - content: "\e025" -} - -.glyphicon-download:before { - content: "\e026" -} - -.glyphicon-upload:before { - content: "\e027" -} - -.glyphicon-inbox:before { - content: "\e028" -} - -.glyphicon-play-circle:before { - content: "\e029" -} - -.glyphicon-repeat:before { - content: "\e030" -} - -.glyphicon-refresh:before { - content: "\e031" -} - -.glyphicon-list-alt:before { - content: "\e032" -} - -.glyphicon-lock:before { - content: "\e033" -} - -.glyphicon-flag:before { - content: "\e034" -} - -.glyphicon-headphones:before { - content: "\e035" -} - -.glyphicon-volume-off:before { - content: "\e036" -} - -.glyphicon-volume-down:before { - content: "\e037" -} - -.glyphicon-volume-up:before { - content: "\e038" -} - -.glyphicon-qrcode:before { - content: "\e039" -} - -.glyphicon-barcode:before { - content: "\e040" -} - -.glyphicon-tag:before { - content: "\e041" -} - -.glyphicon-tags:before { - content: "\e042" -} - -.glyphicon-book:before { - content: "\e043" -} - -.glyphicon-bookmark:before { - content: "\e044" -} - -.glyphicon-print:before { - content: "\e045" -} - -.glyphicon-camera:before { - content: "\e046" -} - -.glyphicon-font:before { - content: "\e047" -} - -.glyphicon-bold:before { - content: "\e048" -} - -.glyphicon-italic:before { - content: "\e049" -} - -.glyphicon-text-height:before { - content: "\e050" -} - -.glyphicon-text-width:before { - content: "\e051" -} - -.glyphicon-align-left:before { - content: "\e052" -} - -.glyphicon-align-center:before { - content: "\e053" -} - -.glyphicon-align-right:before { - content: "\e054" -} - -.glyphicon-align-justify:before { - content: "\e055" -} - -.glyphicon-list:before { - content: "\e056" -} - -.glyphicon-indent-left:before { - content: "\e057" -} - -.glyphicon-indent-right:before { - content: "\e058" -} - -.glyphicon-facetime-video:before { - content: "\e059" -} - -.glyphicon-picture:before { - content: "\e060" -} - -.glyphicon-map-marker:before { - content: "\e062" -} - -.glyphicon-adjust:before { - content: "\e063" -} - -.glyphicon-tint:before { - content: "\e064" -} - -.glyphicon-edit:before { - content: "\e065" -} - -.glyphicon-share:before { - content: "\e066" -} - -.glyphicon-check:before { - content: "\e067" -} - -.glyphicon-move:before { - content: "\e068" -} - -.glyphicon-step-backward:before { - content: "\e069" -} - -.glyphicon-fast-backward:before { - content: "\e070" -} - -.glyphicon-backward:before { - content: "\e071" -} - -.glyphicon-play:before { - content: "\e072" -} - -.glyphicon-pause:before { - content: "\e073" -} - -.glyphicon-stop:before { - content: "\e074" -} - -.glyphicon-forward:before { - content: "\e075" -} - -.glyphicon-fast-forward:before { - content: "\e076" -} - -.glyphicon-step-forward:before { - content: "\e077" -} - -.glyphicon-eject:before { - content: "\e078" -} - -.glyphicon-chevron-left:before { - content: "\e079" -} - -.glyphicon-chevron-right:before { - content: "\e080" -} - -.glyphicon-plus-sign:before { - content: "\e081" -} - -.glyphicon-minus-sign:before { - content: "\e082" -} - -.glyphicon-remove-sign:before { - content: "\e083" -} - -.glyphicon-ok-sign:before { - content: "\e084" -} - -.glyphicon-question-sign:before { - content: "\e085" -} - -.glyphicon-info-sign:before { - content: "\e086" -} - -.glyphicon-screenshot:before { - content: "\e087" -} - -.glyphicon-remove-circle:before { - content: "\e088" -} - -.glyphicon-ok-circle:before { - content: "\e089" -} - -.glyphicon-ban-circle:before { - content: "\e090" -} - -.glyphicon-arrow-left:before { - content: "\e091" -} - -.glyphicon-arrow-right:before { - content: "\e092" -} - -.glyphicon-arrow-up:before { - content: "\e093" -} - -.glyphicon-arrow-down:before { - content: "\e094" -} - -.glyphicon-share-alt:before { - content: "\e095" -} - -.glyphicon-resize-full:before { - content: "\e096" -} - -.glyphicon-resize-small:before { - content: "\e097" -} - -.glyphicon-exclamation-sign:before { - content: "\e101" -} - -.glyphicon-gift:before { - content: "\e102" -} - -.glyphicon-leaf:before { - content: "\e103" -} - -.glyphicon-fire:before { - content: "\e104" -} - -.glyphicon-eye-open:before { - content: "\e105" -} - -.glyphicon-eye-close:before { - content: "\e106" -} - -.glyphicon-warning-sign:before { - content: "\e107" -} - -.glyphicon-plane:before { - content: "\e108" -} - -.glyphicon-calendar:before { - content: "\e109" -} - -.glyphicon-random:before { - content: "\e110" -} - -.glyphicon-comment:before { - content: "\e111" -} - -.glyphicon-magnet:before { - content: "\e112" -} - -.glyphicon-chevron-up:before { - content: "\e113" -} - -.glyphicon-chevron-down:before { - content: "\e114" -} - -.glyphicon-retweet:before { - content: "\e115" -} - -.glyphicon-shopping-cart:before { - content: "\e116" -} - -.glyphicon-folder-close:before { - content: "\e117" -} - -.glyphicon-folder-open:before { - content: "\e118" -} - -.glyphicon-resize-vertical:before { - content: "\e119" -} - -.glyphicon-resize-horizontal:before { - content: "\e120" -} - -.glyphicon-hdd:before { - content: "\e121" -} - -.glyphicon-bullhorn:before { - content: "\e122" -} - -.glyphicon-bell:before { - content: "\e123" -} - -.glyphicon-certificate:before { - content: "\e124" -} - -.glyphicon-thumbs-up:before { - content: "\e125" -} - -.glyphicon-thumbs-down:before { - content: "\e126" -} - -.glyphicon-hand-right:before { - content: "\e127" -} - -.glyphicon-hand-left:before { - content: "\e128" -} - -.glyphicon-hand-up:before { - content: "\e129" -} - -.glyphicon-hand-down:before { - content: "\e130" -} - -.glyphicon-circle-arrow-right:before { - content: "\e131" -} - -.glyphicon-circle-arrow-left:before { - content: "\e132" -} - -.glyphicon-circle-arrow-up:before { - content: "\e133" -} - -.glyphicon-circle-arrow-down:before { - content: "\e134" -} - -.glyphicon-globe:before { - content: "\e135" -} - -.glyphicon-wrench:before { - content: "\e136" -} - -.glyphicon-tasks:before { - content: "\e137" -} - -.glyphicon-filter:before { - content: "\e138" -} - -.glyphicon-briefcase:before { - content: "\e139" -} - -.glyphicon-fullscreen:before { - content: "\e140" -} - -.glyphicon-dashboard:before { - content: "\e141" -} - -.glyphicon-paperclip:before { - content: "\e142" -} - -.glyphicon-heart-empty:before { - content: "\e143" -} - -.glyphicon-link:before { - content: "\e144" -} - -.glyphicon-phone:before { - content: "\e145" -} - -.glyphicon-pushpin:before { - content: "\e146" -} - -.glyphicon-usd:before { - content: "\e148" -} - -.glyphicon-gbp:before { - content: "\e149" -} - -.glyphicon-sort:before { - content: "\e150" -} - -.glyphicon-sort-by-alphabet:before { - content: "\e151" -} - -.glyphicon-sort-by-alphabet-alt:before { - content: "\e152" -} - -.glyphicon-sort-by-order:before { - content: "\e153" -} - -.glyphicon-sort-by-order-alt:before { - content: "\e154" -} - -.glyphicon-sort-by-attributes:before { - content: "\e155" -} - -.glyphicon-sort-by-attributes-alt:before { - content: "\e156" -} - -.glyphicon-unchecked:before { - content: "\e157" -} - -.glyphicon-expand:before { - content: "\e158" -} - -.glyphicon-collapse-down:before { - content: "\e159" -} - -.glyphicon-collapse-up:before { - content: "\e160" -} - -.glyphicon-log-in:before { - content: "\e161" -} - -.glyphicon-flash:before { - content: "\e162" -} - -.glyphicon-log-out:before { - content: "\e163" -} - -.glyphicon-new-window:before { - content: "\e164" -} - -.glyphicon-record:before { - content: "\e165" -} - -.glyphicon-save:before { - content: "\e166" -} - -.glyphicon-open:before { - content: "\e167" -} - -.glyphicon-saved:before { - content: "\e168" -} - -.glyphicon-import:before { - content: "\e169" -} - -.glyphicon-export:before { - content: "\e170" -} - -.glyphicon-send:before { - content: "\e171" -} - -.glyphicon-floppy-disk:before { - content: "\e172" -} - -.glyphicon-floppy-saved:before { - content: "\e173" -} - -.glyphicon-floppy-remove:before { - content: "\e174" -} - -.glyphicon-floppy-save:before { - content: "\e175" -} - -.glyphicon-floppy-open:before { - content: "\e176" -} - -.glyphicon-credit-card:before { - content: "\e177" -} - -.glyphicon-transfer:before { - content: "\e178" -} - -.glyphicon-cutlery:before { - content: "\e179" -} - -.glyphicon-header:before { - content: "\e180" -} - -.glyphicon-compressed:before { - content: "\e181" -} - -.glyphicon-earphone:before { - content: "\e182" -} - -.glyphicon-phone-alt:before { - content: "\e183" -} - -.glyphicon-tower:before { - content: "\e184" -} - -.glyphicon-stats:before { - content: "\e185" -} - -.glyphicon-sd-video:before { - content: "\e186" -} - -.glyphicon-hd-video:before { - content: "\e187" -} - -.glyphicon-subtitles:before { - content: "\e188" -} - -.glyphicon-sound-stereo:before { - content: "\e189" -} - -.glyphicon-sound-dolby:before { - content: "\e190" -} - -.glyphicon-sound-5-1:before { - content: "\e191" -} - -.glyphicon-sound-6-1:before { - content: "\e192" -} - -.glyphicon-sound-7-1:before { - content: "\e193" -} - -.glyphicon-copyright-mark:before { - content: "\e194" -} - -.glyphicon-registration-mark:before { - content: "\e195" -} - -.glyphicon-cloud-download:before { - content: "\e197" -} - -.glyphicon-cloud-upload:before { - content: "\e198" -} - -.glyphicon-tree-conifer:before { - content: "\e199" -} - -.glyphicon-tree-deciduous:before { - content: "\e200" -} - -.glyphicon-cd:before { - content: "\e201" -} - -.glyphicon-save-file:before { - content: "\e202" -} - -.glyphicon-open-file:before { - content: "\e203" -} - -.glyphicon-level-up:before { - content: "\e204" -} - -.glyphicon-copy:before { - content: "\e205" -} - -.glyphicon-paste:before { - content: "\e206" -} - -.glyphicon-alert:before { - content: "\e209" -} - -.glyphicon-equalizer:before { - content: "\e210" -} - -.glyphicon-king:before { - content: "\e211" -} - -.glyphicon-queen:before { - content: "\e212" -} - -.glyphicon-pawn:before { - content: "\e213" -} - -.glyphicon-bishop:before { - content: "\e214" -} - -.glyphicon-knight:before { - content: "\e215" -} - -.glyphicon-baby-formula:before { - content: "\e216" -} - -.glyphicon-tent:before { - content: "\26fa" -} - -.glyphicon-blackboard:before { - content: "\e218" -} - -.glyphicon-bed:before { - content: "\e219" -} - -.glyphicon-apple:before { - content: "\f8ff" -} - -.glyphicon-erase:before { - content: "\e221" -} - -.glyphicon-hourglass:before { - content: "\231b" -} - -.glyphicon-lamp:before { - content: "\e223" -} - -.glyphicon-duplicate:before { - content: "\e224" -} - -.glyphicon-piggy-bank:before { - content: "\e225" -} - -.glyphicon-scissors:before { - content: "\e226" -} - -.glyphicon-bitcoin:before { - content: "\e227" -} - -.glyphicon-btc:before { - content: "\e227" -} - -.glyphicon-xbt:before { - content: "\e227" -} - -.glyphicon-yen:before { - content: "\00a5" -} - -.glyphicon-jpy:before { - content: "\00a5" -} - -.glyphicon-ruble:before { - content: "\20bd" -} - -.glyphicon-rub:before { - content: "\20bd" -} - -.glyphicon-scale:before { - content: "\e230" -} - -.glyphicon-ice-lolly:before { - content: "\e231" -} - -.glyphicon-ice-lolly-tasted:before { - content: "\e232" -} - -.glyphicon-education:before { - content: "\e233" -} - -.glyphicon-option-horizontal:before { - content: "\e234" -} - -.glyphicon-option-vertical:before { - content: "\e235" -} - -.glyphicon-menu-hamburger:before { - content: "\e236" -} - -.glyphicon-modal-window:before { - content: "\e237" -} - -.glyphicon-oil:before { - content: "\e238" -} - -.glyphicon-grain:before { - content: "\e239" -} - -.glyphicon-sunglasses:before { - content: "\e240" -} - -.glyphicon-text-size:before { - content: "\e241" -} - -.glyphicon-text-color:before { - content: "\e242" -} - -.glyphicon-text-background:before { - content: "\e243" -} - -.glyphicon-object-align-top:before { - content: "\e244" -} - -.glyphicon-object-align-bottom:before { - content: "\e245" -} - -.glyphicon-object-align-horizontal:before { - content: "\e246" -} - -.glyphicon-object-align-left:before { - content: "\e247" -} - -.glyphicon-object-align-vertical:before { - content: "\e248" -} - -.glyphicon-object-align-right:before { - content: "\e249" -} - -.glyphicon-triangle-right:before { - content: "\e250" -} - -.glyphicon-triangle-left:before { - content: "\e251" -} - -.glyphicon-triangle-bottom:before { - content: "\e252" -} - -.glyphicon-triangle-top:before { - content: "\e253" -} - -.glyphicon-console:before { - content: "\e254" -} - -.glyphicon-superscript:before { - content: "\e255" -} - -.glyphicon-subscript:before { - content: "\e256" -} - -.glyphicon-menu-left:before { - content: "\e257" -} - -.glyphicon-menu-right:before { - content: "\e258" -} - -.glyphicon-menu-down:before { - content: "\e259" -} - -.glyphicon-menu-up:before { - content: "\e260" -} - -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box -} - -:after, :before { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box -} - -html { - font-size: 10px; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0) -} - -body { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.42857143; - color: #333; - background-color: #fff -} - -button, input, select, textarea { - font-family: inherit; - font-size: inherit; - line-height: inherit -} - -a { - color: #337ab7; - text-decoration: none -} - -a:focus, a:hover { - color: #23527c; - text-decoration: underline -} - -a:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px -} - -figure { - margin: 0 -} - -img { - vertical-align: middle -} - -.carousel-inner > .item > a > img, .carousel-inner > .item > img, .img-responsive, .thumbnail a > img, .thumbnail > img { - display: block; - max-width: 100%; - height: auto -} - -.img-rounded { - border-radius: 6px -} - -.img-thumbnail { - display: inline-block; - max-width: 100%; - height: auto; - padding: 4px; - line-height: 1.42857143; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 4px; - -webkit-transition: all .2s ease-in-out; - -o-transition: all .2s ease-in-out; - transition: all .2s ease-in-out -} - -.img-circle { - border-radius: 50% -} - -hr { - margin-top: 20px; - margin-bottom: 20px; - border: 0; - border-top: 1px solid #eee -} - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - border: 0 -} - -.sr-only-focusable:active, .sr-only-focusable:focus { - position: static; - width: auto; - height: auto; - margin: 0; - overflow: visible; - clip: auto -} - -[role=button] { - cursor: pointer -} - -.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { - font-family: inherit; - font-weight: 500; - line-height: 1.1; - color: inherit -} - -.h1 .small, .h1 small, .h2 .small, .h2 small, .h3 .small, .h3 small, .h4 .small, .h4 small, .h5 .small, .h5 small, .h6 .small, .h6 small, h1 .small, h1 small, h2 .small, h2 small, h3 .small, h3 small, h4 .small, h4 small, h5 .small, h5 small, h6 .small, h6 small { - font-weight: 400; - line-height: 1; - color: #777 -} - -.h1, .h2, .h3, h1, h2, h3 { - margin-top: 20px; - margin-bottom: 10px -} - -.h1 .small, .h1 small, .h2 .small, .h2 small, .h3 .small, .h3 small, h1 .small, h1 small, h2 .small, h2 small, h3 .small, h3 small { - font-size: 65% -} - -.h4, .h5, .h6, h4, h5, h6 { - margin-top: 10px; - margin-bottom: 10px -} - -.h4 .small, .h4 small, .h5 .small, .h5 small, .h6 .small, .h6 small, h4 .small, h4 small, h5 .small, h5 small, h6 .small, h6 small { - font-size: 75% -} - -.h1, h1 { - font-size: 36px -} - -.h2, h2 { - font-size: 30px -} - -.h3, h3 { - font-size: 24px -} - -.h4, h4 { - font-size: 18px -} - -.h5, h5 { - font-size: 14px -} - -.h6, h6 { - font-size: 12px -} - -p { - margin: 0 0 10px -} - -.lead { - margin-bottom: 20px; - font-size: 16px; - font-weight: 300; - line-height: 1.4 -} - -@media (min-width: 768px) { - .lead { - font-size: 21px - } -} - -.small, small { - font-size: 85% -} - -.mark, mark { - padding: .2em; - background-color: #fcf8e3 -} - -.text-left { - text-align: left -} - -.text-right { - text-align: right -} - -.text-center { - text-align: center -} - -.text-justify { - text-align: justify -} - -.text-nowrap { - white-space: nowrap -} - -.text-lowercase { - text-transform: lowercase -} - -.text-uppercase { - text-transform: uppercase -} - -.text-capitalize { - text-transform: capitalize -} - -.text-muted { - color: #777 -} - -.text-primary { - color: #337ab7 -} - -a.text-primary:focus, a.text-primary:hover { - color: #286090 -} - -.text-success { - color: #3c763d -} - -a.text-success:focus, a.text-success:hover { - color: #2b542c -} - -.text-info { - color: #31708f -} - -a.text-info:focus, a.text-info:hover { - color: #245269 -} - -.text-warning { - color: #8a6d3b -} - -a.text-warning:focus, a.text-warning:hover { - color: #66512c -} - -.text-danger { - color: #a94442 -} - -a.text-danger:focus, a.text-danger:hover { - color: #843534 -} - -.bg-primary { - color: #fff; - background-color: #337ab7 -} - -a.bg-primary:focus, a.bg-primary:hover { - background-color: #286090 -} - -.bg-success { - background-color: #dff0d8 -} - -a.bg-success:focus, a.bg-success:hover { - background-color: #c1e2b3 -} - -.bg-info { - background-color: #d9edf7 -} - -a.bg-info:focus, a.bg-info:hover { - background-color: #afd9ee -} - -.bg-warning { - background-color: #fcf8e3 -} - -a.bg-warning:focus, a.bg-warning:hover { - background-color: #f7ecb5 -} - -.bg-danger { - background-color: #f2dede -} - -a.bg-danger:focus, a.bg-danger:hover { - background-color: #e4b9b9 -} - -.page-header { - padding-bottom: 9px; - margin: 40px 0 20px; - border-bottom: 1px solid #eee -} - -ol, ul { - margin-top: 0; - margin-bottom: 10px -} - -ol ol, ol ul, ul ol, ul ul { - margin-bottom: 0 -} - -.list-unstyled { - padding-left: 0; - list-style: none -} - -.list-inline { - padding-left: 0; - margin-left: -5px; - list-style: none -} - -.list-inline > li { - display: inline-block; - padding-right: 5px; - padding-left: 5px -} - -dl { - margin-top: 0; - margin-bottom: 20px -} - -dd, dt { - line-height: 1.42857143 -} - -dt { - font-weight: 700 -} - -dd { - margin-left: 0 -} - -@media (min-width: 768px) { - .dl-horizontal dt { - float: left; - width: 160px; - overflow: hidden; - clear: left; - text-align: right; - text-overflow: ellipsis; - white-space: nowrap - } - - .dl-horizontal dd { - margin-left: 180px - } -} - -abbr[data-original-title], abbr[title] { - cursor: help; - border-bottom: 1px dotted #777 -} - -.initialism { - font-size: 90%; - text-transform: uppercase -} - -blockquote { - padding: 10px 20px; - margin: 0 0 20px; - font-size: 17.5px; - border-left: 5px solid #eee -} - -blockquote ol:last-child, blockquote p:last-child, blockquote ul:last-child { - margin-bottom: 0 -} - -blockquote .small, blockquote footer, blockquote small { - display: block; - font-size: 80%; - line-height: 1.42857143; - color: #777 -} - -blockquote .small:before, blockquote footer:before, blockquote small:before { - content: '\2014 \00A0' -} - -.blockquote-reverse, blockquote.pull-right { - padding-right: 15px; - padding-left: 0; - text-align: right; - border-right: 5px solid #eee; - border-left: 0 -} - -.blockquote-reverse .small:before, .blockquote-reverse footer:before, .blockquote-reverse small:before, blockquote.pull-right .small:before, blockquote.pull-right footer:before, blockquote.pull-right small:before { - content: '' -} - -.blockquote-reverse .small:after, .blockquote-reverse footer:after, .blockquote-reverse small:after, blockquote.pull-right .small:after, blockquote.pull-right footer:after, blockquote.pull-right small:after { - content: '\00A0 \2014' -} - -address { - margin-bottom: 20px; - font-style: normal; - line-height: 1.42857143 -} - -code, kbd, pre, samp { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace -} - -code { - padding: 2px 4px; - font-size: 90%; - color: #c7254e; - background-color: #f9f2f4; - border-radius: 4px -} - -kbd { - padding: 2px 4px; - font-size: 90%; - color: #fff; - background-color: #333; - border-radius: 3px; - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25) -} - -kbd kbd { - padding: 0; - font-size: 100%; - font-weight: 700; - -webkit-box-shadow: none; - box-shadow: none -} - -pre { - display: block; - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - line-height: 1.42857143; - color: #333; - word-break: break-all; - word-wrap: break-word; - background-color: #f5f5f5; - border: 1px solid #ccc; - border-radius: 4px -} - -pre code { - padding: 0; - font-size: inherit; - color: inherit; - white-space: pre-wrap; - background-color: transparent; - border-radius: 0 -} - -.pre-scrollable { - max-height: 340px; - overflow-y: scroll -} - -.container { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto -} - -@media (min-width: 768px) { - .container { - width: 750px - } -} - -@media (min-width: 992px) { - .container { - width: 970px - } -} - -@media (min-width: 1200px) { - .container { - width: 1170px - } -} - -.container-fluid { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto -} - -.row { - margin-right: -15px; - margin-left: -15px -} - -.col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-xs-1, .col-xs-10, .col-xs-11, .col-xs-12, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9 { - position: relative; - min-height: 1px; - padding-right: 15px; - padding-left: 15px -} - -.col-xs-1, .col-xs-10, .col-xs-11, .col-xs-12, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9 { - float: left -} - -.col-xs-12 { - width: 100% -} - -.col-xs-11 { - width: 91.66666667% -} - -.col-xs-10 { - width: 83.33333333% -} - -.col-xs-9 { - width: 75% -} - -.col-xs-8 { - width: 66.66666667% -} - -.col-xs-7 { - width: 58.33333333% -} - -.col-xs-6 { - width: 50% -} - -.col-xs-5 { - width: 41.66666667% -} - -.col-xs-4 { - width: 33.33333333% -} - -.col-xs-3 { - width: 25% -} - -.col-xs-2 { - width: 16.66666667% -} - -.col-xs-1 { - width: 8.33333333% -} - -.col-xs-pull-12 { - right: 100% -} - -.col-xs-pull-11 { - right: 91.66666667% -} - -.col-xs-pull-10 { - right: 83.33333333% -} - -.col-xs-pull-9 { - right: 75% -} - -.col-xs-pull-8 { - right: 66.66666667% -} - -.col-xs-pull-7 { - right: 58.33333333% -} - -.col-xs-pull-6 { - right: 50% -} - -.col-xs-pull-5 { - right: 41.66666667% -} - -.col-xs-pull-4 { - right: 33.33333333% -} - -.col-xs-pull-3 { - right: 25% -} - -.col-xs-pull-2 { - right: 16.66666667% -} - -.col-xs-pull-1 { - right: 8.33333333% -} - -.col-xs-pull-0 { - right: auto -} - -.col-xs-push-12 { - left: 100% -} - -.col-xs-push-11 { - left: 91.66666667% -} - -.col-xs-push-10 { - left: 83.33333333% -} - -.col-xs-push-9 { - left: 75% -} - -.col-xs-push-8 { - left: 66.66666667% -} - -.col-xs-push-7 { - left: 58.33333333% -} - -.col-xs-push-6 { - left: 50% -} - -.col-xs-push-5 { - left: 41.66666667% -} - -.col-xs-push-4 { - left: 33.33333333% -} - -.col-xs-push-3 { - left: 25% -} - -.col-xs-push-2 { - left: 16.66666667% -} - -.col-xs-push-1 { - left: 8.33333333% -} - -.col-xs-push-0 { - left: auto -} - -.col-xs-offset-12 { - margin-left: 100% -} - -.col-xs-offset-11 { - margin-left: 91.66666667% -} - -.col-xs-offset-10 { - margin-left: 83.33333333% -} - -.col-xs-offset-9 { - margin-left: 75% -} - -.col-xs-offset-8 { - margin-left: 66.66666667% -} - -.col-xs-offset-7 { - margin-left: 58.33333333% -} - -.col-xs-offset-6 { - margin-left: 50% -} - -.col-xs-offset-5 { - margin-left: 41.66666667% -} - -.col-xs-offset-4 { - margin-left: 33.33333333% -} - -.col-xs-offset-3 { - margin-left: 25% -} - -.col-xs-offset-2 { - margin-left: 16.66666667% -} - -.col-xs-offset-1 { - margin-left: 8.33333333% -} - -.col-xs-offset-0 { - margin-left: 0 -} - -@media (min-width: 768px) { - .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9 { - float: left - } - - .col-sm-12 { - width: 100% - } - - .col-sm-11 { - width: 91.66666667% - } - - .col-sm-10 { - width: 83.33333333% - } - - .col-sm-9 { - width: 75% - } - - .col-sm-8 { - width: 66.66666667% - } - - .col-sm-7 { - width: 58.33333333% - } - - .col-sm-6 { - width: 50% - } - - .col-sm-5 { - width: 41.66666667% - } - - .col-sm-4 { - width: 33.33333333% - } - - .col-sm-3 { - width: 25% - } - - .col-sm-2 { - width: 16.66666667% - } - - .col-sm-1 { - width: 8.33333333% - } - - .col-sm-pull-12 { - right: 100% - } - - .col-sm-pull-11 { - right: 91.66666667% - } - - .col-sm-pull-10 { - right: 83.33333333% - } - - .col-sm-pull-9 { - right: 75% - } - - .col-sm-pull-8 { - right: 66.66666667% - } - - .col-sm-pull-7 { - right: 58.33333333% - } - - .col-sm-pull-6 { - right: 50% - } - - .col-sm-pull-5 { - right: 41.66666667% - } - - .col-sm-pull-4 { - right: 33.33333333% - } - - .col-sm-pull-3 { - right: 25% - } - - .col-sm-pull-2 { - right: 16.66666667% - } - - .col-sm-pull-1 { - right: 8.33333333% - } - - .col-sm-pull-0 { - right: auto - } - - .col-sm-push-12 { - left: 100% - } - - .col-sm-push-11 { - left: 91.66666667% - } - - .col-sm-push-10 { - left: 83.33333333% - } - - .col-sm-push-9 { - left: 75% - } - - .col-sm-push-8 { - left: 66.66666667% - } - - .col-sm-push-7 { - left: 58.33333333% - } - - .col-sm-push-6 { - left: 50% - } - - .col-sm-push-5 { - left: 41.66666667% - } - - .col-sm-push-4 { - left: 33.33333333% - } - - .col-sm-push-3 { - left: 25% - } - - .col-sm-push-2 { - left: 16.66666667% - } - - .col-sm-push-1 { - left: 8.33333333% - } - - .col-sm-push-0 { - left: auto - } - - .col-sm-offset-12 { - margin-left: 100% - } - - .col-sm-offset-11 { - margin-left: 91.66666667% - } - - .col-sm-offset-10 { - margin-left: 83.33333333% - } - - .col-sm-offset-9 { - margin-left: 75% - } - - .col-sm-offset-8 { - margin-left: 66.66666667% - } - - .col-sm-offset-7 { - margin-left: 58.33333333% - } - - .col-sm-offset-6 { - margin-left: 50% - } - - .col-sm-offset-5 { - margin-left: 41.66666667% - } - - .col-sm-offset-4 { - margin-left: 33.33333333% - } - - .col-sm-offset-3 { - margin-left: 25% - } - - .col-sm-offset-2 { - margin-left: 16.66666667% - } - - .col-sm-offset-1 { - margin-left: 8.33333333% - } - - .col-sm-offset-0 { - margin-left: 0 - } -} - -@media (min-width: 992px) { - .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9 { - float: left - } - - .col-md-12 { - width: 100% - } - - .col-md-11 { - width: 91.66666667% - } - - .col-md-10 { - width: 83.33333333% - } - - .col-md-9 { - width: 75% - } - - .col-md-8 { - width: 66.66666667% - } - - .col-md-7 { - width: 58.33333333% - } - - .col-md-6 { - width: 50% - } - - .col-md-5 { - width: 41.66666667% - } - - .col-md-4 { - width: 33.33333333% - } - - .col-md-3 { - width: 25% - } - - .col-md-2 { - width: 16.66666667% - } - - .col-md-1 { - width: 8.33333333% - } - - .col-md-pull-12 { - right: 100% - } - - .col-md-pull-11 { - right: 91.66666667% - } - - .col-md-pull-10 { - right: 83.33333333% - } - - .col-md-pull-9 { - right: 75% - } - - .col-md-pull-8 { - right: 66.66666667% - } - - .col-md-pull-7 { - right: 58.33333333% - } - - .col-md-pull-6 { - right: 50% - } - - .col-md-pull-5 { - right: 41.66666667% - } - - .col-md-pull-4 { - right: 33.33333333% - } - - .col-md-pull-3 { - right: 25% - } - - .col-md-pull-2 { - right: 16.66666667% - } - - .col-md-pull-1 { - right: 8.33333333% - } - - .col-md-pull-0 { - right: auto - } - - .col-md-push-12 { - left: 100% - } - - .col-md-push-11 { - left: 91.66666667% - } - - .col-md-push-10 { - left: 83.33333333% - } - - .col-md-push-9 { - left: 75% - } - - .col-md-push-8 { - left: 66.66666667% - } - - .col-md-push-7 { - left: 58.33333333% - } - - .col-md-push-6 { - left: 50% - } - - .col-md-push-5 { - left: 41.66666667% - } - - .col-md-push-4 { - left: 33.33333333% - } - - .col-md-push-3 { - left: 25% - } - - .col-md-push-2 { - left: 16.66666667% - } - - .col-md-push-1 { - left: 8.33333333% - } - - .col-md-push-0 { - left: auto - } - - .col-md-offset-12 { - margin-left: 100% - } - - .col-md-offset-11 { - margin-left: 91.66666667% - } - - .col-md-offset-10 { - margin-left: 83.33333333% - } - - .col-md-offset-9 { - margin-left: 75% - } - - .col-md-offset-8 { - margin-left: 66.66666667% - } - - .col-md-offset-7 { - margin-left: 58.33333333% - } - - .col-md-offset-6 { - margin-left: 50% - } - - .col-md-offset-5 { - margin-left: 41.66666667% - } - - .col-md-offset-4 { - margin-left: 33.33333333% - } - - .col-md-offset-3 { - margin-left: 25% - } - - .col-md-offset-2 { - margin-left: 16.66666667% - } - - .col-md-offset-1 { - margin-left: 8.33333333% - } - - .col-md-offset-0 { - margin-left: 0 - } -} - -@media (min-width: 1200px) { - .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9 { - float: left - } - - .col-lg-12 { - width: 100% - } - - .col-lg-11 { - width: 91.66666667% - } - - .col-lg-10 { - width: 83.33333333% - } - - .col-lg-9 { - width: 75% - } - - .col-lg-8 { - width: 66.66666667% - } - - .col-lg-7 { - width: 58.33333333% - } - - .col-lg-6 { - width: 50% - } - - .col-lg-5 { - width: 41.66666667% - } - - .col-lg-4 { - width: 33.33333333% - } - - .col-lg-3 { - width: 25% - } - - .col-lg-2 { - width: 16.66666667% - } - - .col-lg-1 { - width: 8.33333333% - } - - .col-lg-pull-12 { - right: 100% - } - - .col-lg-pull-11 { - right: 91.66666667% - } - - .col-lg-pull-10 { - right: 83.33333333% - } - - .col-lg-pull-9 { - right: 75% - } - - .col-lg-pull-8 { - right: 66.66666667% - } - - .col-lg-pull-7 { - right: 58.33333333% - } - - .col-lg-pull-6 { - right: 50% - } - - .col-lg-pull-5 { - right: 41.66666667% - } - - .col-lg-pull-4 { - right: 33.33333333% - } - - .col-lg-pull-3 { - right: 25% - } - - .col-lg-pull-2 { - right: 16.66666667% - } - - .col-lg-pull-1 { - right: 8.33333333% - } - - .col-lg-pull-0 { - right: auto - } - - .col-lg-push-12 { - left: 100% - } - - .col-lg-push-11 { - left: 91.66666667% - } - - .col-lg-push-10 { - left: 83.33333333% - } - - .col-lg-push-9 { - left: 75% - } - - .col-lg-push-8 { - left: 66.66666667% - } - - .col-lg-push-7 { - left: 58.33333333% - } - - .col-lg-push-6 { - left: 50% - } - - .col-lg-push-5 { - left: 41.66666667% - } - - .col-lg-push-4 { - left: 33.33333333% - } - - .col-lg-push-3 { - left: 25% - } - - .col-lg-push-2 { - left: 16.66666667% - } - - .col-lg-push-1 { - left: 8.33333333% - } - - .col-lg-push-0 { - left: auto - } - - .col-lg-offset-12 { - margin-left: 100% - } - - .col-lg-offset-11 { - margin-left: 91.66666667% - } - - .col-lg-offset-10 { - margin-left: 83.33333333% - } - - .col-lg-offset-9 { - margin-left: 75% - } - - .col-lg-offset-8 { - margin-left: 66.66666667% - } - - .col-lg-offset-7 { - margin-left: 58.33333333% - } - - .col-lg-offset-6 { - margin-left: 50% - } - - .col-lg-offset-5 { - margin-left: 41.66666667% - } - - .col-lg-offset-4 { - margin-left: 33.33333333% - } - - .col-lg-offset-3 { - margin-left: 25% - } - - .col-lg-offset-2 { - margin-left: 16.66666667% - } - - .col-lg-offset-1 { - margin-left: 8.33333333% - } - - .col-lg-offset-0 { - margin-left: 0 - } -} - -table { - background-color: transparent -} - -caption { - padding-top: 8px; - padding-bottom: 8px; - color: #777; - text-align: left -} - -th { - text-align: left -} - -.table { - width: 100%; - max-width: 100%; - margin-bottom: 20px -} - -.table > tbody > tr > td, .table > tbody > tr > th, .table > tfoot > tr > td, .table > tfoot > tr > th, .table > thead > tr > td, .table > thead > tr > th { - padding: 8px; - line-height: 1.42857143; - vertical-align: top; - border-top: 1px solid #ddd -} - -.table > thead > tr > th { - vertical-align: bottom; - border-bottom: 2px solid #ddd -} - -.table > caption + thead > tr:first-child > td, .table > caption + thead > tr:first-child > th, .table > colgroup + thead > tr:first-child > td, .table > colgroup + thead > tr:first-child > th, .table > thead:first-child > tr:first-child > td, .table > thead:first-child > tr:first-child > th { - border-top: 0 -} - -.table > tbody + tbody { - border-top: 2px solid #ddd -} - -.table .table { - background-color: #fff -} - -.table-condensed > tbody > tr > td, .table-condensed > tbody > tr > th, .table-condensed > tfoot > tr > td, .table-condensed > tfoot > tr > th, .table-condensed > thead > tr > td, .table-condensed > thead > tr > th { - padding: 5px -} - -.table-bordered { - border: 1px solid #ddd -} - -.table-bordered > tbody > tr > td, .table-bordered > tbody > tr > th, .table-bordered > tfoot > tr > td, .table-bordered > tfoot > tr > th, .table-bordered > thead > tr > td, .table-bordered > thead > tr > th { - border: 1px solid #ddd -} - -.table-bordered > thead > tr > td, .table-bordered > thead > tr > th { - border-bottom-width: 2px -} - -.table-striped > tbody > tr:nth-of-type(odd) { - background-color: #f9f9f9 -} - -.table-hover > tbody > tr:hover { - background-color: #f5f5f5 -} - -table col[class*=col-] { - position: static; - display: table-column; - float: none -} - -table td[class*=col-], table th[class*=col-] { - position: static; - display: table-cell; - float: none -} - -.table > tbody > tr.active > td, .table > tbody > tr.active > th, .table > tbody > tr > td.active, .table > tbody > tr > th.active, .table > tfoot > tr.active > td, .table > tfoot > tr.active > th, .table > tfoot > tr > td.active, .table > tfoot > tr > th.active, .table > thead > tr.active > td, .table > thead > tr.active > th, .table > thead > tr > td.active, .table > thead > tr > th.active { - background-color: #f5f5f5 -} - -.table-hover > tbody > tr.active:hover > td, .table-hover > tbody > tr.active:hover > th, .table-hover > tbody > tr:hover > .active, .table-hover > tbody > tr > td.active:hover, .table-hover > tbody > tr > th.active:hover { - background-color: #e8e8e8 -} - -.table > tbody > tr.success > td, .table > tbody > tr.success > th, .table > tbody > tr > td.success, .table > tbody > tr > th.success, .table > tfoot > tr.success > td, .table > tfoot > tr.success > th, .table > tfoot > tr > td.success, .table > tfoot > tr > th.success, .table > thead > tr.success > td, .table > thead > tr.success > th, .table > thead > tr > td.success, .table > thead > tr > th.success { - background-color: #dff0d8 -} - -.table-hover > tbody > tr.success:hover > td, .table-hover > tbody > tr.success:hover > th, .table-hover > tbody > tr:hover > .success, .table-hover > tbody > tr > td.success:hover, .table-hover > tbody > tr > th.success:hover { - background-color: #d0e9c6 -} - -.table > tbody > tr.info > td, .table > tbody > tr.info > th, .table > tbody > tr > td.info, .table > tbody > tr > th.info, .table > tfoot > tr.info > td, .table > tfoot > tr.info > th, .table > tfoot > tr > td.info, .table > tfoot > tr > th.info, .table > thead > tr.info > td, .table > thead > tr.info > th, .table > thead > tr > td.info, .table > thead > tr > th.info { - background-color: #d9edf7 -} - -.table-hover > tbody > tr.info:hover > td, .table-hover > tbody > tr.info:hover > th, .table-hover > tbody > tr:hover > .info, .table-hover > tbody > tr > td.info:hover, .table-hover > tbody > tr > th.info:hover { - background-color: #c4e3f3 -} - -.table > tbody > tr.warning > td, .table > tbody > tr.warning > th, .table > tbody > tr > td.warning, .table > tbody > tr > th.warning, .table > tfoot > tr.warning > td, .table > tfoot > tr.warning > th, .table > tfoot > tr > td.warning, .table > tfoot > tr > th.warning, .table > thead > tr.warning > td, .table > thead > tr.warning > th, .table > thead > tr > td.warning, .table > thead > tr > th.warning { - background-color: #fcf8e3 -} - -.table-hover > tbody > tr.warning:hover > td, .table-hover > tbody > tr.warning:hover > th, .table-hover > tbody > tr:hover > .warning, .table-hover > tbody > tr > td.warning:hover, .table-hover > tbody > tr > th.warning:hover { - background-color: #faf2cc -} - -.table > tbody > tr.danger > td, .table > tbody > tr.danger > th, .table > tbody > tr > td.danger, .table > tbody > tr > th.danger, .table > tfoot > tr.danger > td, .table > tfoot > tr.danger > th, .table > tfoot > tr > td.danger, .table > tfoot > tr > th.danger, .table > thead > tr.danger > td, .table > thead > tr.danger > th, .table > thead > tr > td.danger, .table > thead > tr > th.danger { - background-color: #f2dede -} - -.table-hover > tbody > tr.danger:hover > td, .table-hover > tbody > tr.danger:hover > th, .table-hover > tbody > tr:hover > .danger, .table-hover > tbody > tr > td.danger:hover, .table-hover > tbody > tr > th.danger:hover { - background-color: #ebcccc -} - -.table-responsive { - min-height: .01%; - overflow-x: auto -} - -@media screen and (max-width: 767px) { - .table-responsive { - width: 100%; - margin-bottom: 15px; - overflow-y: hidden; - -ms-overflow-style: -ms-autohiding-scrollbar; - border: 1px solid #ddd - } - - .table-responsive > .table { - margin-bottom: 0 - } - - .table-responsive > .table > tbody > tr > td, .table-responsive > .table > tbody > tr > th, .table-responsive > .table > tfoot > tr > td, .table-responsive > .table > tfoot > tr > th, .table-responsive > .table > thead > tr > td, .table-responsive > .table > thead > tr > th { - white-space: nowrap - } - - .table-responsive > .table-bordered { - border: 0 - } - - .table-responsive > .table-bordered > tbody > tr > td:first-child, .table-responsive > .table-bordered > tbody > tr > th:first-child, .table-responsive > .table-bordered > tfoot > tr > td:first-child, .table-responsive > .table-bordered > tfoot > tr > th:first-child, .table-responsive > .table-bordered > thead > tr > td:first-child, .table-responsive > .table-bordered > thead > tr > th:first-child { - border-left: 0 - } - - .table-responsive > .table-bordered > tbody > tr > td:last-child, .table-responsive > .table-bordered > tbody > tr > th:last-child, .table-responsive > .table-bordered > tfoot > tr > td:last-child, .table-responsive > .table-bordered > tfoot > tr > th:last-child, .table-responsive > .table-bordered > thead > tr > td:last-child, .table-responsive > .table-bordered > thead > tr > th:last-child { - border-right: 0 - } - - .table-responsive > .table-bordered > tbody > tr:last-child > td, .table-responsive > .table-bordered > tbody > tr:last-child > th, .table-responsive > .table-bordered > tfoot > tr:last-child > td, .table-responsive > .table-bordered > tfoot > tr:last-child > th { - border-bottom: 0 - } -} - -fieldset { - min-width: 0; - padding: 0; - margin: 0; - border: 0 -} - -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: 20px; - font-size: 21px; - line-height: inherit; - color: #333; - border: 0; - border-bottom: 1px solid #e5e5e5 -} - -label { - display: inline-block; - max-width: 100%; - margin-bottom: 5px; - font-weight: 700 -} - -input[type=search] { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box -} - -input[type=checkbox], input[type=radio] { - margin: 4px 0 0; - margin-top: 1px \9; - line-height: normal -} - -input[type=file] { - display: block -} - -input[type=range] { - display: block; - width: 100% -} - -select[multiple], select[size] { - height: auto -} - -input[type=file]:focus, input[type=checkbox]:focus, input[type=radio]:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px -} - -output { - display: block; - padding-top: 7px; - font-size: 14px; - line-height: 1.42857143; - color: #555 -} - -.form-control { - display: block; - width: 100%; - height: 34px; - padding: 6px 12px; - font-size: 14px; - line-height: 1.42857143; - color: #555; - background-color: #fff; - background-image: none; - border: 1px solid #ccc; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; - -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; - transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s -} - -.form-control:focus { - border-color: #66afe9; - outline: 0; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6) -} - -.form-control::-moz-placeholder { - color: #999; - opacity: 1 -} - -.form-control:-ms-input-placeholder { - color: #999 -} - -.form-control::-webkit-input-placeholder { - color: #999 -} - -.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { - background-color: #eee; - opacity: 1 -} - -.form-control[disabled], fieldset[disabled] .form-control { - cursor: not-allowed -} - -textarea.form-control { - height: auto -} - -input[type=search] { - -webkit-appearance: none -} - -@media screen and (-webkit-min-device-pixel-ratio: 0) { - input[type=date].form-control, input[type=time].form-control, input[type=datetime-local].form-control, input[type=month].form-control { - line-height: 34px - } - - .input-group-sm input[type=date], .input-group-sm input[type=time], .input-group-sm input[type=datetime-local], .input-group-sm input[type=month], input[type=date].input-sm, input[type=time].input-sm, input[type=datetime-local].input-sm, input[type=month].input-sm { - line-height: 30px - } - - .input-group-lg input[type=date], .input-group-lg input[type=time], .input-group-lg input[type=datetime-local], .input-group-lg input[type=month], input[type=date].input-lg, input[type=time].input-lg, input[type=datetime-local].input-lg, input[type=month].input-lg { - line-height: 46px - } -} - -.form-group { - margin-bottom: 15px -} - -.checkbox, .radio { - position: relative; - display: block; - margin-top: 10px; - margin-bottom: 10px -} - -.checkbox label, .radio label { - min-height: 20px; - padding-left: 20px; - margin-bottom: 0; - font-weight: 400; - cursor: pointer -} - -.checkbox input[type=checkbox], .checkbox-inline input[type=checkbox], .radio input[type=radio], .radio-inline input[type=radio] { - position: absolute; - margin-top: 4px \9; - margin-left: -20px -} - -.checkbox + .checkbox, .radio + .radio { - margin-top: -5px -} - -.checkbox-inline, .radio-inline { - position: relative; - display: inline-block; - padding-left: 20px; - margin-bottom: 0; - font-weight: 400; - vertical-align: middle; - cursor: pointer -} - -.checkbox-inline + .checkbox-inline, .radio-inline + .radio-inline { - margin-top: 0; - margin-left: 10px -} - -fieldset[disabled] input[type=checkbox], fieldset[disabled] input[type=radio], input[type=checkbox].disabled, input[type=checkbox][disabled], input[type=radio].disabled, input[type=radio][disabled] { - cursor: not-allowed -} - -.checkbox-inline.disabled, .radio-inline.disabled, fieldset[disabled] .checkbox-inline, fieldset[disabled] .radio-inline { - cursor: not-allowed -} - -.checkbox.disabled label, .radio.disabled label, fieldset[disabled] .checkbox label, fieldset[disabled] .radio label { - cursor: not-allowed -} - -.form-control-static { - min-height: 34px; - padding-top: 7px; - padding-bottom: 7px; - margin-bottom: 0 -} - -.form-control-static.input-lg, .form-control-static.input-sm { - padding-right: 0; - padding-left: 0 -} - -.input-sm { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px -} - -select.input-sm { - height: 30px; - line-height: 30px -} - -select[multiple].input-sm, textarea.input-sm { - height: auto -} - -.form-group-sm .form-control { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px -} - -.form-group-sm select.form-control { - height: 30px; - line-height: 30px -} - -.form-group-sm select[multiple].form-control, .form-group-sm textarea.form-control { - height: auto -} - -.form-group-sm .form-control-static { - height: 30px; - min-height: 32px; - padding: 6px 10px; - font-size: 12px; - line-height: 1.5 -} - -.input-lg { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px -} - -select.input-lg { - height: 46px; - line-height: 46px -} - -select[multiple].input-lg, textarea.input-lg { - height: auto -} - -.form-group-lg .form-control { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px -} - -.form-group-lg select.form-control { - height: 46px; - line-height: 46px -} - -.form-group-lg select[multiple].form-control, .form-group-lg textarea.form-control { - height: auto -} - -.form-group-lg .form-control-static { - height: 46px; - min-height: 38px; - padding: 11px 16px; - font-size: 18px; - line-height: 1.3333333 -} - -.has-feedback { - position: relative -} - -.has-feedback .form-control { - padding-right: 42.5px -} - -.form-control-feedback { - position: absolute; - top: 0; - right: 0; - z-index: 2; - display: block; - width: 34px; - height: 34px; - line-height: 34px; - text-align: center; - pointer-events: none -} - -.form-group-lg .form-control + .form-control-feedback, .input-group-lg + .form-control-feedback, .input-lg + .form-control-feedback { - width: 46px; - height: 46px; - line-height: 46px -} - -.form-group-sm .form-control + .form-control-feedback, .input-group-sm + .form-control-feedback, .input-sm + .form-control-feedback { - width: 30px; - height: 30px; - line-height: 30px -} - -.has-success .checkbox, .has-success .checkbox-inline, .has-success .control-label, .has-success .help-block, .has-success .radio, .has-success .radio-inline, .has-success.checkbox label, .has-success.checkbox-inline label, .has-success.radio label, .has-success.radio-inline label { - color: #3c763d -} - -.has-success .form-control { - border-color: #3c763d; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075) -} - -.has-success .form-control:focus { - border-color: #2b542c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168 -} - -.has-success .input-group-addon { - color: #3c763d; - background-color: #dff0d8; - border-color: #3c763d -} - -.has-success .form-control-feedback { - color: #3c763d -} - -.has-warning .checkbox, .has-warning .checkbox-inline, .has-warning .control-label, .has-warning .help-block, .has-warning .radio, .has-warning .radio-inline, .has-warning.checkbox label, .has-warning.checkbox-inline label, .has-warning.radio label, .has-warning.radio-inline label { - color: #8a6d3b -} - -.has-warning .form-control { - border-color: #8a6d3b; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075) -} - -.has-warning .form-control:focus { - border-color: #66512c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b -} - -.has-warning .input-group-addon { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #8a6d3b -} - -.has-warning .form-control-feedback { - color: #8a6d3b -} - -.has-error .checkbox, .has-error .checkbox-inline, .has-error .control-label, .has-error .help-block, .has-error .radio, .has-error .radio-inline, .has-error.checkbox label, .has-error.checkbox-inline label, .has-error.radio label, .has-error.radio-inline label { - color: #a94442 -} - -.has-error .form-control { - border-color: #a94442; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075) -} - -.has-error .form-control:focus { - border-color: #843534; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483 -} - -.has-error .input-group-addon { - color: #a94442; - background-color: #f2dede; - border-color: #a94442 -} - -.has-error .form-control-feedback { - color: #a94442 -} - -.has-feedback label ~ .form-control-feedback { - top: 25px -} - -.has-feedback label.sr-only ~ .form-control-feedback { - top: 0 -} - -.help-block { - display: block; - margin-top: 5px; - margin-bottom: 10px; - color: #737373 -} - -@media (min-width: 768px) { - .form-inline .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle - } - - .form-inline .form-control { - display: inline-block; - width: auto; - vertical-align: middle - } - - .form-inline .form-control-static { - display: inline-block - } - - .form-inline .input-group { - display: inline-table; - vertical-align: middle - } - - .form-inline .input-group .form-control, .form-inline .input-group .input-group-addon, .form-inline .input-group .input-group-btn { - width: auto - } - - .form-inline .input-group > .form-control { - width: 100% - } - - .form-inline .control-label { - margin-bottom: 0; - vertical-align: middle - } - - .form-inline .checkbox, .form-inline .radio { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle - } - - .form-inline .checkbox label, .form-inline .radio label { - padding-left: 0 - } - - .form-inline .checkbox input[type=checkbox], .form-inline .radio input[type=radio] { - position: relative; - margin-left: 0 - } - - .form-inline .has-feedback .form-control-feedback { - top: 0 - } -} - -.form-horizontal .checkbox, .form-horizontal .checkbox-inline, .form-horizontal .radio, .form-horizontal .radio-inline { - padding-top: 7px; - margin-top: 0; - margin-bottom: 0 -} - -.form-horizontal .checkbox, .form-horizontal .radio { - min-height: 27px -} - -.form-horizontal .form-group { - margin-right: -15px; - margin-left: -15px -} - -@media (min-width: 768px) { - .form-horizontal .control-label { - padding-top: 7px; - margin-bottom: 0; - text-align: right - } -} - -.form-horizontal .has-feedback .form-control-feedback { - right: 15px -} - -@media (min-width: 768px) { - .form-horizontal .form-group-lg .control-label { - padding-top: 14.33px; - font-size: 18px - } -} - -@media (min-width: 768px) { - .form-horizontal .form-group-sm .control-label { - padding-top: 6px; - font-size: 12px - } -} - -.btn { - display: inline-block; - padding: 6px 12px; - margin-bottom: 0; - font-size: 14px; - font-weight: 400; - line-height: 1.42857143; - text-align: center; - white-space: nowrap; - vertical-align: middle; - -ms-touch-action: manipulation; - touch-action: manipulation; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-image: none; - border: 1px solid transparent; - border-radius: 4px -} - -.btn.active.focus, .btn.active:focus, .btn.focus, .btn:active.focus, .btn:active:focus, .btn:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px -} - -.btn.focus, .btn:focus, .btn:hover { - color: #333; - text-decoration: none -} - -.btn.active, .btn:active { - background-image: none; - outline: 0; - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125) -} - -.btn.disabled, .btn[disabled], fieldset[disabled] .btn { - cursor: not-allowed; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - box-shadow: none; - opacity: .65 -} - -a.btn.disabled, fieldset[disabled] a.btn { - pointer-events: none -} - -.btn-default { - color: #333; - background-color: #fff; - border-color: #ccc -} - -.btn-default.focus, .btn-default:focus { - color: #333; - background-color: #e6e6e6; - border-color: #8c8c8c -} - -.btn-default:hover { - color: #333; - background-color: #e6e6e6; - border-color: #adadad -} - -.btn-default.active, .btn-default:active, .open > .dropdown-toggle.btn-default { - color: #333; - background-color: #e6e6e6; - border-color: #adadad -} - -.btn-default.active.focus, .btn-default.active:focus, .btn-default.active:hover, .btn-default:active.focus, .btn-default:active:focus, .btn-default:active:hover, .open > .dropdown-toggle.btn-default.focus, .open > .dropdown-toggle.btn-default:focus, .open > .dropdown-toggle.btn-default:hover { - color: #333; - background-color: #d4d4d4; - border-color: #8c8c8c -} - -.btn-default.active, .btn-default:active, .open > .dropdown-toggle.btn-default { - background-image: none -} - -.btn-default.disabled, .btn-default.disabled.active, .btn-default.disabled.focus, .btn-default.disabled:active, .btn-default.disabled:focus, .btn-default.disabled:hover, .btn-default[disabled], .btn-default[disabled].active, .btn-default[disabled].focus, .btn-default[disabled]:active, .btn-default[disabled]:focus, .btn-default[disabled]:hover, fieldset[disabled] .btn-default, fieldset[disabled] .btn-default.active, fieldset[disabled] .btn-default.focus, fieldset[disabled] .btn-default:active, fieldset[disabled] .btn-default:focus, fieldset[disabled] .btn-default:hover { - background-color: #fff; - border-color: #ccc -} - -.btn-default .badge { - color: #fff; - background-color: #333 -} - -.btn-primary { - color: #fff; - background-color: #337ab7; - border-color: #2e6da4 -} - -.btn-primary.focus, .btn-primary:focus { - color: #fff; - background-color: #286090; - border-color: #122b40 -} - -.btn-primary:hover { - color: #fff; - background-color: #286090; - border-color: #204d74 -} - -.btn-primary.active, .btn-primary:active, .open > .dropdown-toggle.btn-primary { - color: #fff; - background-color: #286090; - border-color: #204d74 -} - -.btn-primary.active.focus, .btn-primary.active:focus, .btn-primary.active:hover, .btn-primary:active.focus, .btn-primary:active:focus, .btn-primary:active:hover, .open > .dropdown-toggle.btn-primary.focus, .open > .dropdown-toggle.btn-primary:focus, .open > .dropdown-toggle.btn-primary:hover { - color: #fff; - background-color: #204d74; - border-color: #122b40 -} - -.btn-primary.active, .btn-primary:active, .open > .dropdown-toggle.btn-primary { - background-image: none -} - -.btn-primary.disabled, .btn-primary.disabled.active, .btn-primary.disabled.focus, .btn-primary.disabled:active, .btn-primary.disabled:focus, .btn-primary.disabled:hover, .btn-primary[disabled], .btn-primary[disabled].active, .btn-primary[disabled].focus, .btn-primary[disabled]:active, .btn-primary[disabled]:focus, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary, fieldset[disabled] .btn-primary.active, fieldset[disabled] .btn-primary.focus, fieldset[disabled] .btn-primary:active, fieldset[disabled] .btn-primary:focus, fieldset[disabled] .btn-primary:hover { - background-color: #337ab7; - border-color: #2e6da4 -} - -.btn-primary .badge { - color: #337ab7; - background-color: #fff -} - -.btn-success { - color: #fff; - background-color: #5cb85c; - border-color: #4cae4c -} - -.btn-success.focus, .btn-success:focus { - color: #fff; - background-color: #449d44; - border-color: #255625 -} - -.btn-success:hover { - color: #fff; - background-color: #449d44; - border-color: #398439 -} - -.btn-success.active, .btn-success:active, .open > .dropdown-toggle.btn-success { - color: #fff; - background-color: #449d44; - border-color: #398439 -} - -.btn-success.active.focus, .btn-success.active:focus, .btn-success.active:hover, .btn-success:active.focus, .btn-success:active:focus, .btn-success:active:hover, .open > .dropdown-toggle.btn-success.focus, .open > .dropdown-toggle.btn-success:focus, .open > .dropdown-toggle.btn-success:hover { - color: #fff; - background-color: #398439; - border-color: #255625 -} - -.btn-success.active, .btn-success:active, .open > .dropdown-toggle.btn-success { - background-image: none -} - -.btn-success.disabled, .btn-success.disabled.active, .btn-success.disabled.focus, .btn-success.disabled:active, .btn-success.disabled:focus, .btn-success.disabled:hover, .btn-success[disabled], .btn-success[disabled].active, .btn-success[disabled].focus, .btn-success[disabled]:active, .btn-success[disabled]:focus, .btn-success[disabled]:hover, fieldset[disabled] .btn-success, fieldset[disabled] .btn-success.active, fieldset[disabled] .btn-success.focus, fieldset[disabled] .btn-success:active, fieldset[disabled] .btn-success:focus, fieldset[disabled] .btn-success:hover { - background-color: #5cb85c; - border-color: #4cae4c -} - -.btn-success .badge { - color: #5cb85c; - background-color: #fff -} - -.btn-info { - color: #fff; - background-color: #5bc0de; - border-color: #46b8da -} - -.btn-info.focus, .btn-info:focus { - color: #fff; - background-color: #31b0d5; - border-color: #1b6d85 -} - -.btn-info:hover { - color: #fff; - background-color: #31b0d5; - border-color: #269abc -} - -.btn-info.active, .btn-info:active, .open > .dropdown-toggle.btn-info { - color: #fff; - background-color: #31b0d5; - border-color: #269abc -} - -.btn-info.active.focus, .btn-info.active:focus, .btn-info.active:hover, .btn-info:active.focus, .btn-info:active:focus, .btn-info:active:hover, .open > .dropdown-toggle.btn-info.focus, .open > .dropdown-toggle.btn-info:focus, .open > .dropdown-toggle.btn-info:hover { - color: #fff; - background-color: #269abc; - border-color: #1b6d85 -} - -.btn-info.active, .btn-info:active, .open > .dropdown-toggle.btn-info { - background-image: none -} - -.btn-info.disabled, .btn-info.disabled.active, .btn-info.disabled.focus, .btn-info.disabled:active, .btn-info.disabled:focus, .btn-info.disabled:hover, .btn-info[disabled], .btn-info[disabled].active, .btn-info[disabled].focus, .btn-info[disabled]:active, .btn-info[disabled]:focus, .btn-info[disabled]:hover, fieldset[disabled] .btn-info, fieldset[disabled] .btn-info.active, fieldset[disabled] .btn-info.focus, fieldset[disabled] .btn-info:active, fieldset[disabled] .btn-info:focus, fieldset[disabled] .btn-info:hover { - background-color: #5bc0de; - border-color: #46b8da -} - -.btn-info .badge { - color: #5bc0de; - background-color: #fff -} - -.btn-warning { - color: #fff; - background-color: #f0ad4e; - border-color: #eea236 -} - -.btn-warning.focus, .btn-warning:focus { - color: #fff; - background-color: #ec971f; - border-color: #985f0d -} - -.btn-warning:hover { - color: #fff; - background-color: #ec971f; - border-color: #d58512 -} - -.btn-warning.active, .btn-warning:active, .open > .dropdown-toggle.btn-warning { - color: #fff; - background-color: #ec971f; - border-color: #d58512 -} - -.btn-warning.active.focus, .btn-warning.active:focus, .btn-warning.active:hover, .btn-warning:active.focus, .btn-warning:active:focus, .btn-warning:active:hover, .open > .dropdown-toggle.btn-warning.focus, .open > .dropdown-toggle.btn-warning:focus, .open > .dropdown-toggle.btn-warning:hover { - color: #fff; - background-color: #d58512; - border-color: #985f0d -} - -.btn-warning.active, .btn-warning:active, .open > .dropdown-toggle.btn-warning { - background-image: none -} - -.btn-warning.disabled, .btn-warning.disabled.active, .btn-warning.disabled.focus, .btn-warning.disabled:active, .btn-warning.disabled:focus, .btn-warning.disabled:hover, .btn-warning[disabled], .btn-warning[disabled].active, .btn-warning[disabled].focus, .btn-warning[disabled]:active, .btn-warning[disabled]:focus, .btn-warning[disabled]:hover, fieldset[disabled] .btn-warning, fieldset[disabled] .btn-warning.active, fieldset[disabled] .btn-warning.focus, fieldset[disabled] .btn-warning:active, fieldset[disabled] .btn-warning:focus, fieldset[disabled] .btn-warning:hover { - background-color: #f0ad4e; - border-color: #eea236 -} - -.btn-warning .badge { - color: #f0ad4e; - background-color: #fff -} - -.btn-danger { - color: #fff; - background-color: #d9534f; - border-color: #d43f3a -} - -.btn-danger.focus, .btn-danger:focus { - color: #fff; - background-color: #c9302c; - border-color: #761c19 -} - -.btn-danger:hover { - color: #fff; - background-color: #c9302c; - border-color: #ac2925 -} - -.btn-danger.active, .btn-danger:active, .open > .dropdown-toggle.btn-danger { - color: #fff; - background-color: #c9302c; - border-color: #ac2925 -} - -.btn-danger.active.focus, .btn-danger.active:focus, .btn-danger.active:hover, .btn-danger:active.focus, .btn-danger:active:focus, .btn-danger:active:hover, .open > .dropdown-toggle.btn-danger.focus, .open > .dropdown-toggle.btn-danger:focus, .open > .dropdown-toggle.btn-danger:hover { - color: #fff; - background-color: #ac2925; - border-color: #761c19 -} - -.btn-danger.active, .btn-danger:active, .open > .dropdown-toggle.btn-danger { - background-image: none -} - -.btn-danger.disabled, .btn-danger.disabled.active, .btn-danger.disabled.focus, .btn-danger.disabled:active, .btn-danger.disabled:focus, .btn-danger.disabled:hover, .btn-danger[disabled], .btn-danger[disabled].active, .btn-danger[disabled].focus, .btn-danger[disabled]:active, .btn-danger[disabled]:focus, .btn-danger[disabled]:hover, fieldset[disabled] .btn-danger, fieldset[disabled] .btn-danger.active, fieldset[disabled] .btn-danger.focus, fieldset[disabled] .btn-danger:active, fieldset[disabled] .btn-danger:focus, fieldset[disabled] .btn-danger:hover { - background-color: #d9534f; - border-color: #d43f3a -} - -.btn-danger .badge { - color: #d9534f; - background-color: #fff -} - -.btn-link { - font-weight: 400; - color: #337ab7; - border-radius: 0 -} - -.btn-link, .btn-link.active, .btn-link:active, .btn-link[disabled], fieldset[disabled] .btn-link { - background-color: transparent; - -webkit-box-shadow: none; - box-shadow: none -} - -.btn-link, .btn-link:active, .btn-link:focus, .btn-link:hover { - border-color: transparent -} - -.btn-link:focus, .btn-link:hover { - color: #23527c; - text-decoration: underline; - background-color: transparent -} - -.btn-link[disabled]:focus, .btn-link[disabled]:hover, fieldset[disabled] .btn-link:focus, fieldset[disabled] .btn-link:hover { - color: #777; - text-decoration: none -} - -.btn-group-lg > .btn, .btn-lg { - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px -} - -.btn-group-sm > .btn, .btn-sm { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px -} - -.btn-group-xs > .btn, .btn-xs { - padding: 1px 5px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px -} - -.btn-block { - display: block; - width: 100% -} - -.btn-block + .btn-block { - margin-top: 5px -} - -input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].btn-block { - width: 100% -} - -.fade { - opacity: 0; - -webkit-transition: opacity .15s linear; - -o-transition: opacity .15s linear; - transition: opacity .15s linear -} - -.fade.in { - opacity: 1 -} - -.collapse { - display: none -} - -.collapse.in { - display: block -} - -tr.collapse.in { - display: table-row -} - -tbody.collapse.in { - display: table-row-group -} - -.collapsing { - position: relative; - height: 0; - overflow: hidden; - -webkit-transition-timing-function: ease; - -o-transition-timing-function: ease; - transition-timing-function: ease; - -webkit-transition-duration: .35s; - -o-transition-duration: .35s; - transition-duration: .35s; - -webkit-transition-property: height, visibility; - -o-transition-property: height, visibility; - transition-property: height, visibility -} - -.caret { - display: inline-block; - width: 0; - height: 0; - margin-left: 2px; - vertical-align: middle; - border-top: 4px dashed; - border-top: 4px solid \9; - border-right: 4px solid transparent; - border-left: 4px solid transparent -} - -.dropdown, .dropup { - position: relative -} - -.dropdown-toggle:focus { - outline: 0 -} - -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - min-width: 160px; - padding: 5px 0; - margin: 2px 0 0; - font-size: 14px; - text-align: left; - list-style: none; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, .15); - border-radius: 4px; - -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); - box-shadow: 0 6px 12px rgba(0, 0, 0, .175) -} - -.dropdown-menu.pull-right { - right: 0; - left: auto -} - -.dropdown-menu .divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5 -} - -.dropdown-menu > li > a { - display: block; - padding: 3px 20px; - clear: both; - font-weight: 400; - line-height: 1.42857143; - color: #333; - white-space: nowrap -} - -.dropdown-menu > li > a:focus, .dropdown-menu > li > a:hover { - color: #262626; - text-decoration: none; - background-color: #f5f5f5 -} - -.dropdown-menu > .active > a, .dropdown-menu > .active > a:focus, .dropdown-menu > .active > a:hover { - color: #fff; - text-decoration: none; - background-color: #337ab7; - outline: 0 -} - -.dropdown-menu > .disabled > a, .dropdown-menu > .disabled > a:focus, .dropdown-menu > .disabled > a:hover { - color: #777 -} - -.dropdown-menu > .disabled > a:focus, .dropdown-menu > .disabled > a:hover { - text-decoration: none; - cursor: not-allowed; - background-color: transparent; - background-image: none; - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false) -} - -.open > .dropdown-menu { - display: block -} - -.open > a { - outline: 0 -} - -.dropdown-menu-right { - right: 0; - left: auto -} - -.dropdown-menu-left { - right: auto; - left: 0 -} - -.dropdown-header { - display: block; - padding: 3px 20px; - font-size: 12px; - line-height: 1.42857143; - color: #777; - white-space: nowrap -} - -.dropdown-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 990 -} - -.pull-right > .dropdown-menu { - right: 0; - left: auto -} - -.dropup .caret, .navbar-fixed-bottom .dropdown .caret { - content: ""; - border-top: 0; - border-bottom: 4px dashed; - border-bottom: 4px solid \9 -} - -.dropup .dropdown-menu, .navbar-fixed-bottom .dropdown .dropdown-menu { - top: auto; - bottom: 100%; - margin-bottom: 2px -} - -@media (min-width: 768px) { - .navbar-right .dropdown-menu { - right: 0; - left: auto - } - - .navbar-right .dropdown-menu-left { - right: auto; - left: 0 - } -} - -.btn-group, .btn-group-vertical { - position: relative; - display: inline-block; - vertical-align: middle -} - -.btn-group-vertical > .btn, .btn-group > .btn { - position: relative; - float: left -} - -.btn-group-vertical > .btn.active, .btn-group-vertical > .btn:active, .btn-group-vertical > .btn:focus, .btn-group-vertical > .btn:hover, .btn-group > .btn.active, .btn-group > .btn:active, .btn-group > .btn:focus, .btn-group > .btn:hover { - z-index: 2 -} - -.btn-group .btn + .btn, .btn-group .btn + .btn-group, .btn-group .btn-group + .btn, .btn-group .btn-group + .btn-group { - margin-left: -1px -} - -.btn-toolbar { - margin-left: -5px -} - -.btn-toolbar .btn, .btn-toolbar .btn-group, .btn-toolbar .input-group { - float: left -} - -.btn-toolbar > .btn, .btn-toolbar > .btn-group, .btn-toolbar > .input-group { - margin-left: 5px -} - -.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { - border-radius: 0 -} - -.btn-group > .btn:first-child { - margin-left: 0 -} - -.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { - border-top-right-radius: 0; - border-bottom-right-radius: 0 -} - -.btn-group > .btn:last-child:not(:first-child), .btn-group > .dropdown-toggle:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0 -} - -.btn-group > .btn-group { - float: left -} - -.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0 -} - -.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, .btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { - border-top-right-radius: 0; - border-bottom-right-radius: 0 -} - -.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { - border-top-left-radius: 0; - border-bottom-left-radius: 0 -} - -.btn-group .dropdown-toggle:active, .btn-group.open .dropdown-toggle { - outline: 0 -} - -.btn-group > .btn + .dropdown-toggle { - padding-right: 8px; - padding-left: 8px -} - -.btn-group > .btn-lg + .dropdown-toggle { - padding-right: 12px; - padding-left: 12px -} - -.btn-group.open .dropdown-toggle { - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125) -} - -.btn-group.open .dropdown-toggle.btn-link { - -webkit-box-shadow: none; - box-shadow: none -} - -.btn .caret { - margin-left: 0 -} - -.btn-lg .caret { - border-width: 5px 5px 0; - border-bottom-width: 0 -} - -.dropup .btn-lg .caret { - border-width: 0 5px 5px -} - -.btn-group-vertical > .btn, .btn-group-vertical > .btn-group, .btn-group-vertical > .btn-group > .btn { - display: block; - float: none; - width: 100%; - max-width: 100% -} - -.btn-group-vertical > .btn-group > .btn { - float: none -} - -.btn-group-vertical > .btn + .btn, .btn-group-vertical > .btn + .btn-group, .btn-group-vertical > .btn-group + .btn, .btn-group-vertical > .btn-group + .btn-group { - margin-top: -1px; - margin-left: 0 -} - -.btn-group-vertical > .btn:not(:first-child):not(:last-child) { - border-radius: 0 -} - -.btn-group-vertical > .btn:first-child:not(:last-child) { - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0 -} - -.btn-group-vertical > .btn:last-child:not(:first-child) { - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-left-radius: 4px -} - -.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0 -} - -.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, .btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0 -} - -.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { - border-top-left-radius: 0; - border-top-right-radius: 0 -} - -.btn-group-justified { - display: table; - width: 100%; - table-layout: fixed; - border-collapse: separate -} - -.btn-group-justified > .btn, .btn-group-justified > .btn-group { - display: table-cell; - float: none; - width: 1% -} - -.btn-group-justified > .btn-group .btn { - width: 100% -} - -.btn-group-justified > .btn-group .dropdown-menu { - left: auto -} - -[data-toggle=buttons] > .btn input[type=checkbox], [data-toggle=buttons] > .btn input[type=radio], [data-toggle=buttons] > .btn-group > .btn input[type=checkbox], [data-toggle=buttons] > .btn-group > .btn input[type=radio] { - position: absolute; - clip: rect(0, 0, 0, 0); - pointer-events: none -} - -.input-group { - position: relative; - display: table; - border-collapse: separate -} - -.input-group[class*=col-] { - float: none; - padding-right: 0; - padding-left: 0 -} - -.input-group .form-control { - position: relative; - z-index: 2; - float: left; - width: 100%; - margin-bottom: 0 -} - -.input-group-lg > .form-control, .input-group-lg > .input-group-addon, .input-group-lg > .input-group-btn > .btn { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px -} - -select.input-group-lg > .form-control, select.input-group-lg > .input-group-addon, select.input-group-lg > .input-group-btn > .btn { - height: 46px; - line-height: 46px -} - -select[multiple].input-group-lg > .form-control, select[multiple].input-group-lg > .input-group-addon, select[multiple].input-group-lg > .input-group-btn > .btn, textarea.input-group-lg > .form-control, textarea.input-group-lg > .input-group-addon, textarea.input-group-lg > .input-group-btn > .btn { - height: auto -} - -.input-group-sm > .form-control, .input-group-sm > .input-group-addon, .input-group-sm > .input-group-btn > .btn { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px -} - -select.input-group-sm > .form-control, select.input-group-sm > .input-group-addon, select.input-group-sm > .input-group-btn > .btn { - height: 30px; - line-height: 30px -} - -select[multiple].input-group-sm > .form-control, select[multiple].input-group-sm > .input-group-addon, select[multiple].input-group-sm > .input-group-btn > .btn, textarea.input-group-sm > .form-control, textarea.input-group-sm > .input-group-addon, textarea.input-group-sm > .input-group-btn > .btn { - height: auto -} - -.input-group .form-control, .input-group-addon, .input-group-btn { - display: table-cell -} - -.input-group .form-control:not(:first-child):not(:last-child), .input-group-addon:not(:first-child):not(:last-child), .input-group-btn:not(:first-child):not(:last-child) { - border-radius: 0 -} - -.input-group-addon, .input-group-btn { - width: 1%; - white-space: nowrap; - vertical-align: middle -} - -.input-group-addon { - padding: 6px 12px; - font-size: 14px; - font-weight: 400; - line-height: 1; - color: #555; - text-align: center; - background-color: #eee; - border: 1px solid #ccc; - border-radius: 4px -} - -.input-group-addon.input-sm { - padding: 5px 10px; - font-size: 12px; - border-radius: 3px -} - -.input-group-addon.input-lg { - padding: 10px 16px; - font-size: 18px; - border-radius: 6px -} - -.input-group-addon input[type=checkbox], .input-group-addon input[type=radio] { - margin-top: 0 -} - -.input-group .form-control:first-child, .input-group-addon:first-child, .input-group-btn:first-child > .btn, .input-group-btn:first-child > .btn-group > .btn, .input-group-btn:first-child > .dropdown-toggle, .input-group-btn:last-child > .btn-group:not(:last-child) > .btn, .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) { - border-top-right-radius: 0; - border-bottom-right-radius: 0 -} - -.input-group-addon:first-child { - border-right: 0 -} - -.input-group .form-control:last-child, .input-group-addon:last-child, .input-group-btn:first-child > .btn-group:not(:first-child) > .btn, .input-group-btn:first-child > .btn:not(:first-child), .input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group > .btn, .input-group-btn:last-child > .dropdown-toggle { - border-top-left-radius: 0; - border-bottom-left-radius: 0 -} - -.input-group-addon:last-child { - border-left: 0 -} - -.input-group-btn { - position: relative; - font-size: 0; - white-space: nowrap -} - -.input-group-btn > .btn { - position: relative -} - -.input-group-btn > .btn + .btn { - margin-left: -1px -} - -.input-group-btn > .btn:active, .input-group-btn > .btn:focus, .input-group-btn > .btn:hover { - z-index: 2 -} - -.input-group-btn:first-child > .btn, .input-group-btn:first-child > .btn-group { - margin-right: -1px -} - -.input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group { - z-index: 2; - margin-left: -1px -} - -.nav { - padding-left: 0; - margin-bottom: 0; - list-style: none -} - -.nav > li { - position: relative; - display: block -} - -.nav > li > a { - position: relative; - display: block; - padding: 10px 15px -} - -.nav > li > a:focus, .nav > li > a:hover { - text-decoration: none; - background-color: #eee -} - -.nav > li.disabled > a { - color: #777 -} - -.nav > li.disabled > a:focus, .nav > li.disabled > a:hover { - color: #777; - text-decoration: none; - cursor: not-allowed; - background-color: transparent -} - -.nav .open > a, .nav .open > a:focus, .nav .open > a:hover { - background-color: #eee; - border-color: #337ab7 -} - -.nav .nav-divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5 -} - -.nav > li > a > img { - max-width: none -} - -.nav-tabs { - border-bottom: 1px solid #ddd -} - -.nav-tabs > li { - float: left; - margin-bottom: -1px -} - -.nav-tabs > li > a { - margin-right: 2px; - line-height: 1.42857143; - border: 1px solid transparent; - border-radius: 4px 4px 0 0 -} - -.nav-tabs > li > a:hover { - border-color: #eee #eee #ddd -} - -.nav-tabs > li.active > a, .nav-tabs > li.active > a:focus, .nav-tabs > li.active > a:hover { - color: #555; - cursor: default; - background-color: #fff; - border: 1px solid #ddd; - border-bottom-color: transparent -} - -.nav-tabs.nav-justified { - width: 100%; - border-bottom: 0 -} - -.nav-tabs.nav-justified > li { - float: none -} - -.nav-tabs.nav-justified > li > a { - margin-bottom: 5px; - text-align: center -} - -.nav-tabs.nav-justified > .dropdown .dropdown-menu { - top: auto; - left: auto -} - -@media (min-width: 768px) { - .nav-tabs.nav-justified > li { - display: table-cell; - width: 1% - } - - .nav-tabs.nav-justified > li > a { - margin-bottom: 0 - } -} - -.nav-tabs.nav-justified > li > a { - margin-right: 0; - border-radius: 4px -} - -.nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:focus, .nav-tabs.nav-justified > .active > a:hover { - border: 1px solid #ddd -} - -@media (min-width: 768px) { - .nav-tabs.nav-justified > li > a { - border-bottom: 1px solid #ddd; - border-radius: 4px 4px 0 0 - } - - .nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:focus, .nav-tabs.nav-justified > .active > a:hover { - border-bottom-color: #fff - } -} - -.nav-pills > li { - float: left -} - -.nav-pills > li > a { - border-radius: 4px -} - -.nav-pills > li + li { - margin-left: 2px -} - -.nav-pills > li.active > a, .nav-pills > li.active > a:focus, .nav-pills > li.active > a:hover { - color: #fff; - background-color: #337ab7 -} - -.nav-stacked > li { - float: none -} - -.nav-stacked > li + li { - margin-top: 2px; - margin-left: 0 -} - -.nav-justified { - width: 100% -} - -.nav-justified > li { - float: none -} - -.nav-justified > li > a { - margin-bottom: 5px; - text-align: center -} - -.nav-justified > .dropdown .dropdown-menu { - top: auto; - left: auto -} - -@media (min-width: 768px) { - .nav-justified > li { - display: table-cell; - width: 1% - } - - .nav-justified > li > a { - margin-bottom: 0 - } -} - -.nav-tabs-justified { - border-bottom: 0 -} - -.nav-tabs-justified > li > a { - margin-right: 0; - border-radius: 4px -} - -.nav-tabs-justified > .active > a, .nav-tabs-justified > .active > a:focus, .nav-tabs-justified > .active > a:hover { - border: 1px solid #ddd -} - -@media (min-width: 768px) { - .nav-tabs-justified > li > a { - border-bottom: 1px solid #ddd; - border-radius: 4px 4px 0 0 - } - - .nav-tabs-justified > .active > a, .nav-tabs-justified > .active > a:focus, .nav-tabs-justified > .active > a:hover { - border-bottom-color: #fff - } -} - -.tab-content > .tab-pane { - display: none -} - -.tab-content > .active { - display: block -} - -.nav-tabs .dropdown-menu { - margin-top: -1px; - border-top-left-radius: 0; - border-top-right-radius: 0 -} - -.navbar { - position: relative; - min-height: 50px; - margin-bottom: 20px; - border: 1px solid transparent -} - -@media (min-width: 768px) { - .navbar { - border-radius: 4px - } -} - -@media (min-width: 768px) { - .navbar-header { - float: left - } -} - -.navbar-collapse { - padding-right: 15px; - padding-left: 15px; - overflow-x: visible; - -webkit-overflow-scrolling: touch; - border-top: 1px solid transparent; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1) -} - -.navbar-collapse.in { - overflow-y: auto -} - -@media (min-width: 768px) { - .navbar-collapse { - width: auto; - border-top: 0; - -webkit-box-shadow: none; - box-shadow: none - } - - .navbar-collapse.collapse { - display: block !important; - height: auto !important; - padding-bottom: 0; - overflow: visible !important - } - - .navbar-collapse.in { - overflow-y: visible - } - - .navbar-fixed-bottom .navbar-collapse, .navbar-fixed-top .navbar-collapse, .navbar-static-top .navbar-collapse { - padding-right: 0; - padding-left: 0 - } -} - -.navbar-fixed-bottom .navbar-collapse, .navbar-fixed-top .navbar-collapse { - max-height: 340px -} - -@media (max-device-width: 480px) and (orientation: landscape) { - .navbar-fixed-bottom .navbar-collapse, .navbar-fixed-top .navbar-collapse { - max-height: 200px - } -} - -.container-fluid > .navbar-collapse, .container-fluid > .navbar-header, .container > .navbar-collapse, .container > .navbar-header { - margin-right: -15px; - margin-left: -15px -} - -@media (min-width: 768px) { - .container-fluid > .navbar-collapse, .container-fluid > .navbar-header, .container > .navbar-collapse, .container > .navbar-header { - margin-right: 0; - margin-left: 0 - } -} - -.navbar-static-top { - z-index: 1000; - border-width: 0 0 1px -} - -@media (min-width: 768px) { - .navbar-static-top { - border-radius: 0 - } -} - -.navbar-fixed-bottom, .navbar-fixed-top { - position: fixed; - right: 0; - left: 0; - z-index: 1030 -} - -@media (min-width: 768px) { - .navbar-fixed-bottom, .navbar-fixed-top { - border-radius: 0 - } -} - -.navbar-fixed-top { - top: 0; - border-width: 0 0 1px -} - -.navbar-fixed-bottom { - bottom: 0; - margin-bottom: 0; - border-width: 1px 0 0 -} - -.navbar-brand { - float: left; - height: 50px; - padding: 15px 15px; - font-size: 18px; - line-height: 20px -} - -.navbar-brand:focus, .navbar-brand:hover { - text-decoration: none -} - -.navbar-brand > img { - display: block -} - -@media (min-width: 768px) { - .navbar > .container .navbar-brand, .navbar > .container-fluid .navbar-brand { - margin-left: -15px - } -} - -.navbar-toggle { - position: relative; - float: right; - padding: 9px 10px; - margin-top: 8px; - margin-right: 15px; - margin-bottom: 8px; - background-color: transparent; - background-image: none; - border: 1px solid transparent; - border-radius: 4px -} - -.navbar-toggle:focus { - outline: 0 -} - -.navbar-toggle .icon-bar { - display: block; - width: 22px; - height: 2px; - border-radius: 1px -} - -.navbar-toggle .icon-bar + .icon-bar { - margin-top: 4px -} - -@media (min-width: 768px) { - .navbar-toggle { - display: none - } -} - -.navbar-nav { - margin: 7.5px -15px -} - -.navbar-nav > li > a { - padding-top: 10px; - padding-bottom: 10px; - line-height: 20px -} - -@media (max-width: 767px) { - .navbar-nav .open .dropdown-menu { - position: static; - float: none; - width: auto; - margin-top: 0; - background-color: transparent; - border: 0; - -webkit-box-shadow: none; - box-shadow: none - } - - .navbar-nav .open .dropdown-menu .dropdown-header, .navbar-nav .open .dropdown-menu > li > a { - padding: 5px 15px 5px 25px - } - - .navbar-nav .open .dropdown-menu > li > a { - line-height: 20px - } - - .navbar-nav .open .dropdown-menu > li > a:focus, .navbar-nav .open .dropdown-menu > li > a:hover { - background-image: none - } -} - -@media (min-width: 768px) { - .navbar-nav { - float: left; - margin: 0 - } - - .navbar-nav > li { - float: left - } - - .navbar-nav > li > a { - padding-top: 15px; - padding-bottom: 15px - } -} - -.navbar-form { - padding: 10px 15px; - margin-top: 8px; - margin-right: -15px; - margin-bottom: 8px; - margin-left: -15px; - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1) -} - -@media (min-width: 768px) { - .navbar-form .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle - } - - .navbar-form .form-control { - display: inline-block; - width: auto; - vertical-align: middle - } - - .navbar-form .form-control-static { - display: inline-block - } - - .navbar-form .input-group { - display: inline-table; - vertical-align: middle - } - - .navbar-form .input-group .form-control, .navbar-form .input-group .input-group-addon, .navbar-form .input-group .input-group-btn { - width: auto - } - - .navbar-form .input-group > .form-control { - width: 100% - } - - .navbar-form .control-label { - margin-bottom: 0; - vertical-align: middle - } - - .navbar-form .checkbox, .navbar-form .radio { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle - } - - .navbar-form .checkbox label, .navbar-form .radio label { - padding-left: 0 - } - - .navbar-form .checkbox input[type=checkbox], .navbar-form .radio input[type=radio] { - position: relative; - margin-left: 0 - } - - .navbar-form .has-feedback .form-control-feedback { - top: 0 - } -} - -@media (max-width: 767px) { - .navbar-form .form-group { - margin-bottom: 5px - } - - .navbar-form .form-group:last-child { - margin-bottom: 0 - } -} - -@media (min-width: 768px) { - .navbar-form { - width: auto; - padding-top: 0; - padding-bottom: 0; - margin-right: 0; - margin-left: 0; - border: 0; - -webkit-box-shadow: none; - box-shadow: none - } -} - -.navbar-nav > li > .dropdown-menu { - margin-top: 0; - border-top-left-radius: 0; - border-top-right-radius: 0 -} - -.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { - margin-bottom: 0; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0 -} - -.navbar-btn { - margin-top: 8px; - margin-bottom: 8px -} - -.navbar-btn.btn-sm { - margin-top: 10px; - margin-bottom: 10px -} - -.navbar-btn.btn-xs { - margin-top: 14px; - margin-bottom: 14px -} - -.navbar-text { - margin-top: 15px; - margin-bottom: 15px -} - -@media (min-width: 768px) { - .navbar-text { - float: left; - margin-right: 15px; - margin-left: 15px - } -} - -@media (min-width: 768px) { - .navbar-left { - float: left !important - } - - .navbar-right { - float: right !important; - margin-right: -15px - } - - .navbar-right ~ .navbar-right { - margin-right: 0 - } -} - -.navbar-default { - background-color: #f8f8f8; - border-color: #e7e7e7 -} - -.navbar-default .navbar-brand { - color: #777 -} - -.navbar-default .navbar-brand:focus, .navbar-default .navbar-brand:hover { - color: #5e5e5e; - background-color: transparent -} - -.navbar-default .navbar-text { - color: #777 -} - -.navbar-default .navbar-nav > li > a { - color: #777 -} - -.navbar-default .navbar-nav > li > a:focus, .navbar-default .navbar-nav > li > a:hover { - color: #333; - background-color: transparent -} - -.navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:focus, .navbar-default .navbar-nav > .active > a:hover { - color: #555; - background-color: #e7e7e7 -} - -.navbar-default .navbar-nav > .disabled > a, .navbar-default .navbar-nav > .disabled > a:focus, .navbar-default .navbar-nav > .disabled > a:hover { - color: #ccc; - background-color: transparent -} - -.navbar-default .navbar-toggle { - border-color: #ddd -} - -.navbar-default .navbar-toggle:focus, .navbar-default .navbar-toggle:hover { - background-color: #ddd -} - -.navbar-default .navbar-toggle .icon-bar { - background-color: #888 -} - -.navbar-default .navbar-collapse, .navbar-default .navbar-form { - border-color: #e7e7e7 -} - -.navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:focus, .navbar-default .navbar-nav > .open > a:hover { - color: #555; - background-color: #e7e7e7 -} - -@media (max-width: 767px) { - .navbar-default .navbar-nav .open .dropdown-menu > li > a { - color: #777 - } - - .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus, .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover { - color: #333; - background-color: transparent - } - - .navbar-default .navbar-nav .open .dropdown-menu > .active > a, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover { - color: #555; - background-color: #e7e7e7 - } - - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover { - color: #ccc; - background-color: transparent - } -} - -.navbar-default .navbar-link { - color: #777 -} - -.navbar-default .navbar-link:hover { - color: #333 -} - -.navbar-default .btn-link { - color: #777 -} - -.navbar-default .btn-link:focus, .navbar-default .btn-link:hover { - color: #333 -} - -.navbar-default .btn-link[disabled]:focus, .navbar-default .btn-link[disabled]:hover, fieldset[disabled] .navbar-default .btn-link:focus, fieldset[disabled] .navbar-default .btn-link:hover { - color: #ccc -} - -.navbar-inverse { - background-color: #222; - border-color: #080808 -} - -.navbar-inverse .navbar-brand { - color: #9d9d9d -} - -.navbar-inverse .navbar-brand:focus, .navbar-inverse .navbar-brand:hover { - color: #fff; - background-color: transparent -} - -.navbar-inverse .navbar-text { - color: #9d9d9d -} - -.navbar-inverse .navbar-nav > li > a { - color: #9d9d9d -} - -.navbar-inverse .navbar-nav > li > a:focus, .navbar-inverse .navbar-nav > li > a:hover { - color: #fff; - background-color: transparent -} - -.navbar-inverse .navbar-nav > .active > a, .navbar-inverse .navbar-nav > .active > a:focus, .navbar-inverse .navbar-nav > .active > a:hover { - color: #fff; - background-color: #080808 -} - -.navbar-inverse .navbar-nav > .disabled > a, .navbar-inverse .navbar-nav > .disabled > a:focus, .navbar-inverse .navbar-nav > .disabled > a:hover { - color: #444; - background-color: transparent -} - -.navbar-inverse .navbar-toggle { - border-color: #333 -} - -.navbar-inverse .navbar-toggle:focus, .navbar-inverse .navbar-toggle:hover { - background-color: #333 -} - -.navbar-inverse .navbar-toggle .icon-bar { - background-color: #fff -} - -.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form { - border-color: #101010 -} - -.navbar-inverse .navbar-nav > .open > a, .navbar-inverse .navbar-nav > .open > a:focus, .navbar-inverse .navbar-nav > .open > a:hover { - color: #fff; - background-color: #080808 -} - -@media (max-width: 767px) { - .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { - border-color: #080808 - } - - .navbar-inverse .navbar-nav .open .dropdown-menu .divider { - background-color: #080808 - } - - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { - color: #9d9d9d - } - - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus, .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover { - color: #fff; - background-color: transparent - } - - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover { - color: #fff; - background-color: #080808 - } - - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover { - color: #444; - background-color: transparent - } -} - -.navbar-inverse .navbar-link { - color: #9d9d9d -} - -.navbar-inverse .navbar-link:hover { - color: #fff -} - -.navbar-inverse .btn-link { - color: #9d9d9d -} - -.navbar-inverse .btn-link:focus, .navbar-inverse .btn-link:hover { - color: #fff -} - -.navbar-inverse .btn-link[disabled]:focus, .navbar-inverse .btn-link[disabled]:hover, fieldset[disabled] .navbar-inverse .btn-link:focus, fieldset[disabled] .navbar-inverse .btn-link:hover { - color: #444 -} - -.breadcrumb { - padding: 8px 15px; - margin-bottom: 20px; - list-style: none; - background-color: #f5f5f5; - border-radius: 4px -} - -.breadcrumb > li { - display: inline-block -} - -.breadcrumb > li + li:before { - padding: 0 5px; - color: #ccc; - content: "/\00a0" -} - -.breadcrumb > .active { - color: #777 -} - -.pagination { - display: inline-block; - padding-left: 0; - margin: 20px 0; - border-radius: 4px -} - -.pagination > li { - display: inline -} - -.pagination > li > a, .pagination > li > span { - position: relative; - float: left; - padding: 6px 12px; - margin-left: -1px; - line-height: 1.42857143; - color: #337ab7; - text-decoration: none; - background-color: #fff; - border: 1px solid #ddd -} - -.pagination > li:first-child > a, .pagination > li:first-child > span { - margin-left: 0; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px -} - -.pagination > li:last-child > a, .pagination > li:last-child > span { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px -} - -.pagination > li > a:focus, .pagination > li > a:hover, .pagination > li > span:focus, .pagination > li > span:hover { - z-index: 3; - color: #23527c; - background-color: #eee; - border-color: #ddd -} - -.pagination > .active > a, .pagination > .active > a:focus, .pagination > .active > a:hover, .pagination > .active > span, .pagination > .active > span:focus, .pagination > .active > span:hover { - z-index: 2; - color: #fff; - cursor: default; - background-color: #337ab7; - border-color: #337ab7 -} - -.pagination > .disabled > a, .pagination > .disabled > a:focus, .pagination > .disabled > a:hover, .pagination > .disabled > span, .pagination > .disabled > span:focus, .pagination > .disabled > span:hover { - color: #777; - cursor: not-allowed; - background-color: #fff; - border-color: #ddd -} - -.pagination-lg > li > a, .pagination-lg > li > span { - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333 -} - -.pagination-lg > li:first-child > a, .pagination-lg > li:first-child > span { - border-top-left-radius: 6px; - border-bottom-left-radius: 6px -} - -.pagination-lg > li:last-child > a, .pagination-lg > li:last-child > span { - border-top-right-radius: 6px; - border-bottom-right-radius: 6px -} - -.pagination-sm > li > a, .pagination-sm > li > span { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5 -} - -.pagination-sm > li:first-child > a, .pagination-sm > li:first-child > span { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px -} - -.pagination-sm > li:last-child > a, .pagination-sm > li:last-child > span { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px -} - -.pager { - padding-left: 0; - margin: 20px 0; - text-align: center; - list-style: none -} - -.pager li { - display: inline -} - -.pager li > a, .pager li > span { - display: inline-block; - padding: 5px 14px; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 15px -} - -.pager li > a:focus, .pager li > a:hover { - text-decoration: none; - background-color: #eee -} - -.pager .next > a, .pager .next > span { - float: right -} - -.pager .previous > a, .pager .previous > span { - float: left -} - -.pager .disabled > a, .pager .disabled > a:focus, .pager .disabled > a:hover, .pager .disabled > span { - color: #777; - cursor: not-allowed; - background-color: #fff -} - -.label { - display: inline; - padding: .2em .6em .3em; - font-size: 75%; - font-weight: 700; - line-height: 1; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: .25em -} - -a.label:focus, a.label:hover { - color: #fff; - text-decoration: none; - cursor: pointer -} - -.label:empty { - display: none -} - -.btn .label { - position: relative; - top: -1px -} - -.label-default { - background-color: #777 -} - -.label-default[href]:focus, .label-default[href]:hover { - background-color: #5e5e5e -} - -.label-primary { - background-color: #337ab7 -} - -.label-primary[href]:focus, .label-primary[href]:hover { - background-color: #286090 -} - -.label-success { - background-color: #5cb85c -} - -.label-success[href]:focus, .label-success[href]:hover { - background-color: #449d44 -} - -.label-info { - background-color: #5bc0de -} - -.label-info[href]:focus, .label-info[href]:hover { - background-color: #31b0d5 -} - -.label-warning { - background-color: #f0ad4e -} - -.label-warning[href]:focus, .label-warning[href]:hover { - background-color: #ec971f -} - -.label-danger { - background-color: #d9534f -} - -.label-danger[href]:focus, .label-danger[href]:hover { - background-color: #c9302c -} - -.badge { - display: inline-block; - min-width: 10px; - padding: 3px 7px; - font-size: 12px; - font-weight: 700; - line-height: 1; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: middle; - background-color: #777; - border-radius: 10px -} - -.badge:empty { - display: none -} - -.btn .badge { - position: relative; - top: -1px -} - -.btn-group-xs > .btn .badge, .btn-xs .badge { - top: 0; - padding: 1px 5px -} - -a.badge:focus, a.badge:hover { - color: #fff; - text-decoration: none; - cursor: pointer -} - -.list-group-item.active > .badge, .nav-pills > .active > a > .badge { - color: #337ab7; - background-color: #fff -} - -.list-group-item > .badge { - float: right -} - -.list-group-item > .badge + .badge { - margin-right: 5px -} - -.nav-pills > li > a > .badge { - margin-left: 3px -} - -.jumbotron { - padding-top: 30px; - padding-bottom: 30px; - margin-bottom: 30px; - color: inherit; - background-color: #eee -} - -.jumbotron .h1, .jumbotron h1 { - color: inherit -} - -.jumbotron p { - margin-bottom: 15px; - font-size: 21px; - font-weight: 200 -} - -.jumbotron > hr { - border-top-color: #d5d5d5 -} - -.container .jumbotron, .container-fluid .jumbotron { - border-radius: 6px -} - -.jumbotron .container { - max-width: 100% -} - -@media screen and (min-width: 768px) { - .jumbotron { - padding-top: 48px; - padding-bottom: 48px - } - - .container .jumbotron, .container-fluid .jumbotron { - padding-right: 60px; - padding-left: 60px - } - - .jumbotron .h1, .jumbotron h1 { - font-size: 63px - } -} - -.thumbnail { - display: block; - padding: 4px; - margin-bottom: 20px; - line-height: 1.42857143; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 4px; - -webkit-transition: border .2s ease-in-out; - -o-transition: border .2s ease-in-out; - transition: border .2s ease-in-out -} - -.thumbnail a > img, .thumbnail > img { - margin-right: auto; - margin-left: auto -} - -a.thumbnail.active, a.thumbnail:focus, a.thumbnail:hover { - border-color: #337ab7 -} - -.thumbnail .caption { - padding: 9px; - color: #333 -} - -.alert { - padding: 15px; - margin-bottom: 20px; - border: 1px solid transparent; - border-radius: 4px -} - -.alert h4 { - margin-top: 0; - color: inherit -} - -.alert .alert-link { - font-weight: 700 -} - -.alert > p, .alert > ul { - margin-bottom: 0 -} - -.alert > p + p { - margin-top: 5px -} - -.alert-dismissable, .alert-dismissible { - padding-right: 35px -} - -.alert-dismissable .close, .alert-dismissible .close { - position: relative; - top: -2px; - right: -21px; - color: inherit -} - -.alert-success { - color: #3c763d; - background-color: #dff0d8; - border-color: #d6e9c6 -} - -.alert-success hr { - border-top-color: #c9e2b3 -} - -.alert-success .alert-link { - color: #2b542c -} - -.alert-info { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1 -} - -.alert-info hr { - border-top-color: #a6e1ec -} - -.alert-info .alert-link { - color: #245269 -} - -.alert-warning { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc -} - -.alert-warning hr { - border-top-color: #f7e1b5 -} - -.alert-warning .alert-link { - color: #66512c -} - -.alert-danger { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1 -} - -.alert-danger hr { - border-top-color: #e4b9c0 -} - -.alert-danger .alert-link { - color: #843534 -} - -@-webkit-keyframes progress-bar-stripes { - from { - background-position: 40px 0 - } - to { - background-position: 0 0 - } -} - -@-o-keyframes progress-bar-stripes { - from { - background-position: 40px 0 - } - to { - background-position: 0 0 - } -} - -@keyframes progress-bar-stripes { - from { - background-position: 40px 0 - } - to { - background-position: 0 0 - } -} - -.progress { - height: 20px; - margin-bottom: 20px; - overflow: hidden; - background-color: #f5f5f5; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1) -} - -.progress-bar { - float: left; - width: 0; - height: 100%; - font-size: 12px; - line-height: 20px; - color: #fff; - text-align: center; - background-color: #337ab7; - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); - -webkit-transition: width .6s ease; - -o-transition: width .6s ease; - transition: width .6s ease -} - -.progress-bar-striped, .progress-striped .progress-bar { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - -webkit-background-size: 40px 40px; - background-size: 40px 40px -} - -.progress-bar.active, .progress.active .progress-bar { - -webkit-animation: progress-bar-stripes 2s linear infinite; - -o-animation: progress-bar-stripes 2s linear infinite; - animation: progress-bar-stripes 2s linear infinite -} - -.progress-bar-success { - background-color: #5cb85c -} - -.progress-striped .progress-bar-success { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent) -} - -.progress-bar-info { - background-color: #5bc0de -} - -.progress-striped .progress-bar-info { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent) -} - -.progress-bar-warning { - background-color: #f0ad4e -} - -.progress-striped .progress-bar-warning { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent) -} - -.progress-bar-danger { - background-color: #d9534f -} - -.progress-striped .progress-bar-danger { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent) -} - -.media { - margin-top: 15px -} - -.media:first-child { - margin-top: 0 -} - -.media, .media-body { - overflow: hidden; - zoom: 1 -} - -.media-body { - width: 10000px -} - -.media-object { - display: block -} - -.media-object.img-thumbnail { - max-width: none -} - -.media-right, .media > .pull-right { - padding-left: 10px -} - -.media-left, .media > .pull-left { - padding-right: 10px -} - -.media-body, .media-left, .media-right { - display: table-cell; - vertical-align: top -} - -.media-middle { - vertical-align: middle -} - -.media-bottom { - vertical-align: bottom -} - -.media-heading { - margin-top: 0; - margin-bottom: 5px -} - -.media-list { - padding-left: 0; - list-style: none -} - -.list-group { - padding-left: 0; - margin-bottom: 20px -} - -.list-group-item { - position: relative; - display: block; - padding: 10px 15px; - margin-bottom: -1px; - background-color: #fff; - border: 1px solid #ddd -} - -.list-group-item:first-child { - border-top-left-radius: 4px; - border-top-right-radius: 4px -} - -.list-group-item:last-child { - margin-bottom: 0; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px -} - -a.list-group-item, button.list-group-item { - color: #555 -} - -a.list-group-item .list-group-item-heading, button.list-group-item .list-group-item-heading { - color: #333 -} - -a.list-group-item:focus, a.list-group-item:hover, button.list-group-item:focus, button.list-group-item:hover { - color: #555; - text-decoration: none; - background-color: #f5f5f5 -} - -button.list-group-item { - width: 100%; - text-align: left -} - -.list-group-item.disabled, .list-group-item.disabled:focus, .list-group-item.disabled:hover { - color: #777; - cursor: not-allowed; - background-color: #eee -} - -.list-group-item.disabled .list-group-item-heading, .list-group-item.disabled:focus .list-group-item-heading, .list-group-item.disabled:hover .list-group-item-heading { - color: inherit -} - -.list-group-item.disabled .list-group-item-text, .list-group-item.disabled:focus .list-group-item-text, .list-group-item.disabled:hover .list-group-item-text { - color: #777 -} - -.list-group-item.active, .list-group-item.active:focus, .list-group-item.active:hover { - z-index: 2; - color: #fff; - background-color: #337ab7; - border-color: #337ab7 -} - -.list-group-item.active .list-group-item-heading, .list-group-item.active .list-group-item-heading > .small, .list-group-item.active .list-group-item-heading > small, .list-group-item.active:focus .list-group-item-heading, .list-group-item.active:focus .list-group-item-heading > .small, .list-group-item.active:focus .list-group-item-heading > small, .list-group-item.active:hover .list-group-item-heading, .list-group-item.active:hover .list-group-item-heading > .small, .list-group-item.active:hover .list-group-item-heading > small { - color: inherit -} - -.list-group-item.active .list-group-item-text, .list-group-item.active:focus .list-group-item-text, .list-group-item.active:hover .list-group-item-text { - color: #c7ddef -} - -.list-group-item-success { - color: #3c763d; - background-color: #dff0d8 -} - -a.list-group-item-success, button.list-group-item-success { - color: #3c763d -} - -a.list-group-item-success .list-group-item-heading, button.list-group-item-success .list-group-item-heading { - color: inherit -} - -a.list-group-item-success:focus, a.list-group-item-success:hover, button.list-group-item-success:focus, button.list-group-item-success:hover { - color: #3c763d; - background-color: #d0e9c6 -} - -a.list-group-item-success.active, a.list-group-item-success.active:focus, a.list-group-item-success.active:hover, button.list-group-item-success.active, button.list-group-item-success.active:focus, button.list-group-item-success.active:hover { - color: #fff; - background-color: #3c763d; - border-color: #3c763d -} - -.list-group-item-info { - color: #31708f; - background-color: #d9edf7 -} - -a.list-group-item-info, button.list-group-item-info { - color: #31708f -} - -a.list-group-item-info .list-group-item-heading, button.list-group-item-info .list-group-item-heading { - color: inherit -} - -a.list-group-item-info:focus, a.list-group-item-info:hover, button.list-group-item-info:focus, button.list-group-item-info:hover { - color: #31708f; - background-color: #c4e3f3 -} - -a.list-group-item-info.active, a.list-group-item-info.active:focus, a.list-group-item-info.active:hover, button.list-group-item-info.active, button.list-group-item-info.active:focus, button.list-group-item-info.active:hover { - color: #fff; - background-color: #31708f; - border-color: #31708f -} - -.list-group-item-warning { - color: #8a6d3b; - background-color: #fcf8e3 -} - -a.list-group-item-warning, button.list-group-item-warning { - color: #8a6d3b -} - -a.list-group-item-warning .list-group-item-heading, button.list-group-item-warning .list-group-item-heading { - color: inherit -} - -a.list-group-item-warning:focus, a.list-group-item-warning:hover, button.list-group-item-warning:focus, button.list-group-item-warning:hover { - color: #8a6d3b; - background-color: #faf2cc -} - -a.list-group-item-warning.active, a.list-group-item-warning.active:focus, a.list-group-item-warning.active:hover, button.list-group-item-warning.active, button.list-group-item-warning.active:focus, button.list-group-item-warning.active:hover { - color: #fff; - background-color: #8a6d3b; - border-color: #8a6d3b -} - -.list-group-item-danger { - color: #a94442; - background-color: #f2dede -} - -a.list-group-item-danger, button.list-group-item-danger { - color: #a94442 -} - -a.list-group-item-danger .list-group-item-heading, button.list-group-item-danger .list-group-item-heading { - color: inherit -} - -a.list-group-item-danger:focus, a.list-group-item-danger:hover, button.list-group-item-danger:focus, button.list-group-item-danger:hover { - color: #a94442; - background-color: #ebcccc -} - -a.list-group-item-danger.active, a.list-group-item-danger.active:focus, a.list-group-item-danger.active:hover, button.list-group-item-danger.active, button.list-group-item-danger.active:focus, button.list-group-item-danger.active:hover { - color: #fff; - background-color: #a94442; - border-color: #a94442 -} - -.list-group-item-heading { - margin-top: 0; - margin-bottom: 5px -} - -.list-group-item-text { - margin-bottom: 0; - line-height: 1.3 -} - -.panel { - margin-bottom: 20px; - background-color: #fff; - border: 1px solid transparent; - border-radius: 4px; - -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); - box-shadow: 0 1px 1px rgba(0, 0, 0, .05) -} - -.panel-body { - padding: 15px -} - -.panel-heading { - padding: 10px 15px; - border-bottom: 1px solid transparent; - border-top-left-radius: 3px; - border-top-right-radius: 3px -} - -.panel-heading > .dropdown .dropdown-toggle { - color: inherit -} - -.panel-title { - margin-top: 0; - margin-bottom: 0; - font-size: 16px; - color: inherit -} - -.panel-title > .small, .panel-title > .small > a, .panel-title > a, .panel-title > small, .panel-title > small > a { - color: inherit -} - -.panel-footer { - padding: 10px 15px; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px -} - -.panel > .list-group, .panel > .panel-collapse > .list-group { - margin-bottom: 0 -} - -.panel > .list-group .list-group-item, .panel > .panel-collapse > .list-group .list-group-item { - border-width: 1px 0; - border-radius: 0 -} - -.panel > .list-group:first-child .list-group-item:first-child, .panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { - border-top: 0; - border-top-left-radius: 3px; - border-top-right-radius: 3px -} - -.panel > .list-group:last-child .list-group-item:last-child, .panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { - border-bottom: 0; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px -} - -.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { - border-top-left-radius: 0; - border-top-right-radius: 0 -} - -.panel-heading + .list-group .list-group-item:first-child { - border-top-width: 0 -} - -.list-group + .panel-footer { - border-top-width: 0 -} - -.panel > .panel-collapse > .table, .panel > .table, .panel > .table-responsive > .table { - margin-bottom: 0 -} - -.panel > .panel-collapse > .table caption, .panel > .table caption, .panel > .table-responsive > .table caption { - padding-right: 15px; - padding-left: 15px -} - -.panel > .table-responsive:first-child > .table:first-child, .panel > .table:first-child { - border-top-left-radius: 3px; - border-top-right-radius: 3px -} - -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child, .panel > .table:first-child > thead:first-child > tr:first-child { - border-top-left-radius: 3px; - border-top-right-radius: 3px -} - -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, .panel > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table:first-child > thead:first-child > tr:first-child th:first-child { - border-top-left-radius: 3px -} - -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, .panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, .panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, .panel > .table:first-child > thead:first-child > tr:first-child td:last-child, .panel > .table:first-child > thead:first-child > tr:first-child th:last-child { - border-top-right-radius: 3px -} - -.panel > .table-responsive:last-child > .table:last-child, .panel > .table:last-child { - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px -} - -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child, .panel > .table:last-child > tbody:last-child > tr:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child { - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px -} - -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child, .panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, .panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, .panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child { - border-bottom-left-radius: 3px -} - -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child, .panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, .panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child { - border-bottom-right-radius: 3px -} - -.panel > .panel-body + .table, .panel > .panel-body + .table-responsive, .panel > .table + .panel-body, .panel > .table-responsive + .panel-body { - border-top: 1px solid #ddd -} - -.panel > .table > tbody:first-child > tr:first-child td, .panel > .table > tbody:first-child > tr:first-child th { - border-top: 0 -} - -.panel > .table-bordered, .panel > .table-responsive > .table-bordered { - border: 0 -} - -.panel > .table-bordered > tbody > tr > td:first-child, .panel > .table-bordered > tbody > tr > th:first-child, .panel > .table-bordered > tfoot > tr > td:first-child, .panel > .table-bordered > tfoot > tr > th:first-child, .panel > .table-bordered > thead > tr > td:first-child, .panel > .table-bordered > thead > tr > th:first-child, .panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, .panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, .panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child, .panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, .panel > .table-responsive > .table-bordered > thead > tr > td:first-child, .panel > .table-responsive > .table-bordered > thead > tr > th:first-child { - border-left: 0 -} - -.panel > .table-bordered > tbody > tr > td:last-child, .panel > .table-bordered > tbody > tr > th:last-child, .panel > .table-bordered > tfoot > tr > td:last-child, .panel > .table-bordered > tfoot > tr > th:last-child, .panel > .table-bordered > thead > tr > td:last-child, .panel > .table-bordered > thead > tr > th:last-child, .panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, .panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, .panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child, .panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, .panel > .table-responsive > .table-bordered > thead > tr > td:last-child, .panel > .table-responsive > .table-bordered > thead > tr > th:last-child { - border-right: 0 -} - -.panel > .table-bordered > tbody > tr:first-child > td, .panel > .table-bordered > tbody > tr:first-child > th, .panel > .table-bordered > thead > tr:first-child > td, .panel > .table-bordered > thead > tr:first-child > th, .panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:first-child > th, .panel > .table-responsive > .table-bordered > thead > tr:first-child > td, .panel > .table-responsive > .table-bordered > thead > tr:first-child > th { - border-bottom: 0 -} - -.panel > .table-bordered > tbody > tr:last-child > td, .panel > .table-bordered > tbody > tr:last-child > th, .panel > .table-bordered > tfoot > tr:last-child > td, .panel > .table-bordered > tfoot > tr:last-child > th, .panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, .panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, .panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { - border-bottom: 0 -} - -.panel > .table-responsive { - margin-bottom: 0; - border: 0 -} - -.panel-group { - margin-bottom: 20px -} - -.panel-group .panel { - margin-bottom: 0; - border-radius: 4px -} - -.panel-group .panel + .panel { - margin-top: 5px -} - -.panel-group .panel-heading { - border-bottom: 0 -} - -.panel-group .panel-heading + .panel-collapse > .list-group, .panel-group .panel-heading + .panel-collapse > .panel-body { - border-top: 1px solid #ddd -} - -.panel-group .panel-footer { - border-top: 0 -} - -.panel-group .panel-footer + .panel-collapse .panel-body { - border-bottom: 1px solid #ddd -} - -.panel-default { - border-color: #ddd -} - -.panel-default > .panel-heading { - color: #333; - background-color: #f5f5f5; - border-color: #ddd -} - -.panel-default > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #ddd -} - -.panel-default > .panel-heading .badge { - color: #f5f5f5; - background-color: #333 -} - -.panel-default > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #ddd -} - -.panel-primary { - border-color: #337ab7 -} - -.panel-primary > .panel-heading { - color: #fff; - background-color: #337ab7; - border-color: #337ab7 -} - -.panel-primary > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #337ab7 -} - -.panel-primary > .panel-heading .badge { - color: #337ab7; - background-color: #fff -} - -.panel-primary > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #337ab7 -} - -.panel-success { - border-color: #d6e9c6 -} - -.panel-success > .panel-heading { - color: #3c763d; - background-color: #dff0d8; - border-color: #d6e9c6 -} - -.panel-success > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #d6e9c6 -} - -.panel-success > .panel-heading .badge { - color: #dff0d8; - background-color: #3c763d -} - -.panel-success > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #d6e9c6 -} - -.panel-info { - border-color: #bce8f1 -} - -.panel-info > .panel-heading { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1 -} - -.panel-info > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #bce8f1 -} - -.panel-info > .panel-heading .badge { - color: #d9edf7; - background-color: #31708f -} - -.panel-info > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #bce8f1 -} - -.panel-warning { - border-color: #faebcc -} - -.panel-warning > .panel-heading { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc -} - -.panel-warning > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #faebcc -} - -.panel-warning > .panel-heading .badge { - color: #fcf8e3; - background-color: #8a6d3b -} - -.panel-warning > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #faebcc -} - -.panel-danger { - border-color: #ebccd1 -} - -.panel-danger > .panel-heading { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1 -} - -.panel-danger > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #ebccd1 -} - -.panel-danger > .panel-heading .badge { - color: #f2dede; - background-color: #a94442 -} - -.panel-danger > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #ebccd1 -} - -.embed-responsive { - position: relative; - display: block; - height: 0; - padding: 0; - overflow: hidden -} - -.embed-responsive .embed-responsive-item, .embed-responsive embed, .embed-responsive iframe, .embed-responsive object, .embed-responsive video { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - border: 0 -} - -.embed-responsive-16by9 { - padding-bottom: 56.25% -} - -.embed-responsive-4by3 { - padding-bottom: 75% -} - -.well { - min-height: 20px; - padding: 19px; - margin-bottom: 20px; - background-color: #f5f5f5; - border: 1px solid #e3e3e3; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05) -} - -.well blockquote { - border-color: #ddd; - border-color: rgba(0, 0, 0, .15) -} - -.well-lg { - padding: 24px; - border-radius: 6px -} - -.well-sm { - padding: 9px; - border-radius: 3px -} - -.close { - float: right; - font-size: 21px; - font-weight: 700; - line-height: 1; - color: #000; - text-shadow: 0 1px 0 #fff; - filter: alpha(opacity=20); - opacity: .2 -} - -.close:focus, .close:hover { - color: #000; - text-decoration: none; - cursor: pointer; - filter: alpha(opacity=50); - opacity: .5 -} - -button.close { - -webkit-appearance: none; - padding: 0; - cursor: pointer; - background: 0 0; - border: 0 -} - -.modal-open { - overflow: hidden -} - -.modal { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1050; - display: none; - overflow: hidden; - -webkit-overflow-scrolling: touch; - outline: 0 -} - -.modal.fade .modal-dialog { - -webkit-transition: -webkit-transform .3s ease-out; - -o-transition: -o-transform .3s ease-out; - transition: transform .3s ease-out; - -webkit-transform: translate(0, -25%); - -ms-transform: translate(0, -25%); - -o-transform: translate(0, -25%); - transform: translate(0, -25%) -} - -.modal.in .modal-dialog { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0) -} - -.modal-open .modal { - overflow-x: hidden; - overflow-y: auto -} - -.modal-dialog { - position: relative; - width: auto; - margin: 10px -} - -.modal-content { - position: relative; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #999; - border: 1px solid rgba(0, 0, 0, .2); - border-radius: 6px; - outline: 0; - -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); - box-shadow: 0 3px 9px rgba(0, 0, 0, .5) -} - -.modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1040; - background-color: #000 -} - -.modal-backdrop.fade { - filter: alpha(opacity=0); - opacity: 0 -} - -.modal-backdrop.in { - filter: alpha(opacity=50); - opacity: .5 -} - -.modal-header { - min-height: 16.43px; - padding: 15px; - border-bottom: 1px solid #e5e5e5 -} - -.modal-header .close { - margin-top: -2px -} - -.modal-title { - margin: 0; - line-height: 1.42857143 -} - -.modal-body { - position: relative; - padding: 15px -} - -.modal-footer { - padding: 15px; - text-align: right; - border-top: 1px solid #e5e5e5 -} - -.modal-footer .btn + .btn { - margin-bottom: 0; - margin-left: 5px -} - -.modal-footer .btn-group .btn + .btn { - margin-left: -1px -} - -.modal-footer .btn-block + .btn-block { - margin-left: 0 -} - -.modal-scrollbar-measure { - position: absolute; - top: -9999px; - width: 50px; - height: 50px; - overflow: scroll -} - -@media (min-width: 768px) { - .modal-dialog { - width: 600px; - margin: 30px auto - } - - .modal-content { - -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); - box-shadow: 0 5px 15px rgba(0, 0, 0, .5) - } - - .modal-sm { - width: 300px - } -} - -@media (min-width: 992px) { - .modal-lg { - width: 900px - } -} - -.tooltip { - position: absolute; - z-index: 1070; - display: block; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 12px; - font-style: normal; - font-weight: 400; - line-height: 1.42857143; - text-align: left; - text-align: start; - text-decoration: none; - text-shadow: none; - text-transform: none; - letter-spacing: normal; - word-break: normal; - word-spacing: normal; - word-wrap: normal; - white-space: normal; - filter: alpha(opacity=0); - opacity: 0; - line-break: auto -} - -.tooltip.in { - filter: alpha(opacity=90); - opacity: .9 -} - -.tooltip.top { - padding: 5px 0; - margin-top: -3px -} - -.tooltip.right { - padding: 0 5px; - margin-left: 3px -} - -.tooltip.bottom { - padding: 5px 0; - margin-top: 3px -} - -.tooltip.left { - padding: 0 5px; - margin-left: -3px -} - -.tooltip-inner { - max-width: 200px; - padding: 3px 8px; - color: #fff; - text-align: center; - background-color: #000; - border-radius: 4px -} - -.tooltip-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid -} - -.tooltip.top .tooltip-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-width: 5px 5px 0; - border-top-color: #000 -} - -.tooltip.top-left .tooltip-arrow { - right: 5px; - bottom: 0; - margin-bottom: -5px; - border-width: 5px 5px 0; - border-top-color: #000 -} - -.tooltip.top-right .tooltip-arrow { - bottom: 0; - left: 5px; - margin-bottom: -5px; - border-width: 5px 5px 0; - border-top-color: #000 -} - -.tooltip.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-width: 5px 5px 5px 0; - border-right-color: #000 -} - -.tooltip.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-width: 5px 0 5px 5px; - border-left-color: #000 -} - -.tooltip.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000 -} - -.tooltip.bottom-left .tooltip-arrow { - top: 0; - right: 5px; - margin-top: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000 -} - -.tooltip.bottom-right .tooltip-arrow { - top: 0; - left: 5px; - margin-top: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000 -} - -.popover { - position: absolute; - top: 0; - left: 0; - z-index: 1060; - display: none; - max-width: 276px; - padding: 1px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: 1.42857143; - text-align: left; - text-align: start; - text-decoration: none; - text-shadow: none; - text-transform: none; - letter-spacing: normal; - word-break: normal; - word-spacing: normal; - word-wrap: normal; - white-space: normal; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, .2); - border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); - box-shadow: 0 5px 10px rgba(0, 0, 0, .2); - line-break: auto -} - -.popover.top { - margin-top: -10px -} - -.popover.right { - margin-left: 10px -} - -.popover.bottom { - margin-top: 10px -} - -.popover.left { - margin-left: -10px -} - -.popover-title { - padding: 8px 14px; - margin: 0; - font-size: 14px; - background-color: #f7f7f7; - border-bottom: 1px solid #ebebeb; - border-radius: 5px 5px 0 0 -} - -.popover-content { - padding: 9px 14px -} - -.popover > .arrow, .popover > .arrow:after { - position: absolute; - display: block; - width: 0; - height: 0; - border-color: transparent; - border-style: solid -} - -.popover > .arrow { - border-width: 11px -} - -.popover > .arrow:after { - content: ""; - border-width: 10px -} - -.popover.top > .arrow { - bottom: -11px; - left: 50%; - margin-left: -11px; - border-top-color: #999; - border-top-color: rgba(0, 0, 0, .25); - border-bottom-width: 0 -} - -.popover.top > .arrow:after { - bottom: 1px; - margin-left: -10px; - content: " "; - border-top-color: #fff; - border-bottom-width: 0 -} - -.popover.right > .arrow { - top: 50%; - left: -11px; - margin-top: -11px; - border-right-color: #999; - border-right-color: rgba(0, 0, 0, .25); - border-left-width: 0 -} - -.popover.right > .arrow:after { - bottom: -10px; - left: 1px; - content: " "; - border-right-color: #fff; - border-left-width: 0 -} - -.popover.bottom > .arrow { - top: -11px; - left: 50%; - margin-left: -11px; - border-top-width: 0; - border-bottom-color: #999; - border-bottom-color: rgba(0, 0, 0, .25) -} - -.popover.bottom > .arrow:after { - top: 1px; - margin-left: -10px; - content: " "; - border-top-width: 0; - border-bottom-color: #fff -} - -.popover.left > .arrow { - top: 50%; - right: -11px; - margin-top: -11px; - border-right-width: 0; - border-left-color: #999; - border-left-color: rgba(0, 0, 0, .25) -} - -.popover.left > .arrow:after { - right: 1px; - bottom: -10px; - content: " "; - border-right-width: 0; - border-left-color: #fff -} - -.carousel { - position: relative -} - -.carousel-inner { - position: relative; - width: 100%; - overflow: hidden -} - -.carousel-inner > .item { - position: relative; - display: none; - -webkit-transition: .6s ease-in-out left; - -o-transition: .6s ease-in-out left; - transition: .6s ease-in-out left -} - -.carousel-inner > .item > a > img, .carousel-inner > .item > img { - line-height: 1 -} - -@media all and (transform-3d),(-webkit-transform-3d) { - .carousel-inner > .item { - -webkit-transition: -webkit-transform .6s ease-in-out; - -o-transition: -o-transform .6s ease-in-out; - transition: transform .6s ease-in-out; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - -webkit-perspective: 1000px; - perspective: 1000px - } - - .carousel-inner > .item.active.right, .carousel-inner > .item.next { - left: 0; - -webkit-transform: translate3d(100%, 0, 0); - transform: translate3d(100%, 0, 0) - } - - .carousel-inner > .item.active.left, .carousel-inner > .item.prev { - left: 0; - -webkit-transform: translate3d(-100%, 0, 0); - transform: translate3d(-100%, 0, 0) - } - - .carousel-inner > .item.active, .carousel-inner > .item.next.left, .carousel-inner > .item.prev.right { - left: 0; - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0) - } -} - -.carousel-inner > .active, .carousel-inner > .next, .carousel-inner > .prev { - display: block -} - -.carousel-inner > .active { - left: 0 -} - -.carousel-inner > .next, .carousel-inner > .prev { - position: absolute; - top: 0; - width: 100% -} - -.carousel-inner > .next { - left: 100% -} - -.carousel-inner > .prev { - left: -100% -} - -.carousel-inner > .next.left, .carousel-inner > .prev.right { - left: 0 -} - -.carousel-inner > .active.left { - left: -100% -} - -.carousel-inner > .active.right { - left: 100% -} - -.carousel-control { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 15%; - font-size: 20px; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, .6); - filter: alpha(opacity=50); - opacity: .5 -} - -.carousel-control.left { - background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .0001) 100%); - background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .0001) 100%); - background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); - background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .0001) 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); - background-repeat: repeat-x -} - -.carousel-control.right { - right: 0; - left: auto; - background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0, rgba(0, 0, 0, .5) 100%); - background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0, rgba(0, 0, 0, .5) 100%); - background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); - background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0, rgba(0, 0, 0, .5) 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); - background-repeat: repeat-x -} - -.carousel-control:focus, .carousel-control:hover { - color: #fff; - text-decoration: none; - filter: alpha(opacity=90); - outline: 0; - opacity: .9 -} - -.carousel-control .glyphicon-chevron-left, .carousel-control .glyphicon-chevron-right, .carousel-control .icon-next, .carousel-control .icon-prev { - position: absolute; - top: 50%; - z-index: 5; - display: inline-block; - margin-top: -10px -} - -.carousel-control .glyphicon-chevron-left, .carousel-control .icon-prev { - left: 50%; - margin-left: -10px -} - -.carousel-control .glyphicon-chevron-right, .carousel-control .icon-next { - right: 50%; - margin-right: -10px -} - -.carousel-control .icon-next, .carousel-control .icon-prev { - width: 20px; - height: 20px; - font-family: serif; - line-height: 1 -} - -.carousel-control .icon-prev:before { - content: '\2039' -} - -.carousel-control .icon-next:before { - content: '\203a' -} - -.carousel-indicators { - position: absolute; - bottom: 10px; - left: 50%; - z-index: 15; - width: 60%; - padding-left: 0; - margin-left: -30%; - text-align: center; - list-style: none -} - -.carousel-indicators li { - display: inline-block; - width: 10px; - height: 10px; - margin: 1px; - text-indent: -999px; - cursor: pointer; - background-color: #000 \9; - background-color: rgba(0, 0, 0, 0); - border: 1px solid #fff; - border-radius: 10px -} - -.carousel-indicators .active { - width: 12px; - height: 12px; - margin: 0; - background-color: #fff -} - -.carousel-caption { - position: absolute; - right: 15%; - bottom: 20px; - left: 15%; - z-index: 10; - padding-top: 20px; - padding-bottom: 20px; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, .6) -} - -.carousel-caption .btn { - text-shadow: none -} - -@media screen and (min-width: 768px) { - .carousel-control .glyphicon-chevron-left, .carousel-control .glyphicon-chevron-right, .carousel-control .icon-next, .carousel-control .icon-prev { - width: 30px; - height: 30px; - margin-top: -15px; - font-size: 30px - } - - .carousel-control .glyphicon-chevron-left, .carousel-control .icon-prev { - margin-left: -15px - } - - .carousel-control .glyphicon-chevron-right, .carousel-control .icon-next { - margin-right: -15px - } - - .carousel-caption { - right: 20%; - left: 20%; - padding-bottom: 30px - } - - .carousel-indicators { - bottom: 20px - } -} - -.btn-group-vertical > .btn-group:after, .btn-group-vertical > .btn-group:before, .btn-toolbar:after, .btn-toolbar:before, .clearfix:after, .clearfix:before, .container-fluid:after, .container-fluid:before, .container:after, .container:before, .dl-horizontal dd:after, .dl-horizontal dd:before, .form-horizontal .form-group:after, .form-horizontal .form-group:before, .modal-footer:after, .modal-footer:before, .nav:after, .nav:before, .navbar-collapse:after, .navbar-collapse:before, .navbar-header:after, .navbar-header:before, .navbar:after, .navbar:before, .pager:after, .pager:before, .panel-body:after, .panel-body:before, .row:after, .row:before { - display: table; - content: " " -} - -.btn-group-vertical > .btn-group:after, .btn-toolbar:after, .clearfix:after, .container-fluid:after, .container:after, .dl-horizontal dd:after, .form-horizontal .form-group:after, .modal-footer:after, .nav:after, .navbar-collapse:after, .navbar-header:after, .navbar:after, .pager:after, .panel-body:after, .row:after { - clear: both -} - -.center-block { - display: block; - margin-right: auto; - margin-left: auto -} - -.pull-right { - float: right !important -} - -.pull-left { - float: left !important -} - -.hide { - display: none !important -} - -.show { - display: block !important -} - -.invisible { - visibility: hidden -} - -.text-hide { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0 -} - -.hidden { - display: none !important -} - -.affix { - position: fixed -} - -@-ms-viewport { - width: device-width -} - -.visible-lg, .visible-md, .visible-sm, .visible-xs { - display: none !important -} - -.visible-lg-block, .visible-lg-inline, .visible-lg-inline-block, .visible-md-block, .visible-md-inline, .visible-md-inline-block, .visible-sm-block, .visible-sm-inline, .visible-sm-inline-block, .visible-xs-block, .visible-xs-inline, .visible-xs-inline-block { - display: none !important -} - -@media (max-width: 767px) { - .visible-xs { - display: block !important - } - - table.visible-xs { - display: table !important - } - - tr.visible-xs { - display: table-row !important - } - - td.visible-xs, th.visible-xs { - display: table-cell !important - } -} - -@media (max-width: 767px) { - .visible-xs-block { - display: block !important - } -} - -@media (max-width: 767px) { - .visible-xs-inline { - display: inline !important - } -} - -@media (max-width: 767px) { - .visible-xs-inline-block { - display: inline-block !important - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm { - display: block !important - } - - table.visible-sm { - display: table !important - } - - tr.visible-sm { - display: table-row !important - } - - td.visible-sm, th.visible-sm { - display: table-cell !important - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-block { - display: block !important - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-inline { - display: inline !important - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-inline-block { - display: inline-block !important - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md { - display: block !important - } - - table.visible-md { - display: table !important - } - - tr.visible-md { - display: table-row !important - } - - td.visible-md, th.visible-md { - display: table-cell !important - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-block { - display: block !important - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-inline { - display: inline !important - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-inline-block { - display: inline-block !important - } -} - -@media (min-width: 1200px) { - .visible-lg { - display: block !important - } - - table.visible-lg { - display: table !important - } - - tr.visible-lg { - display: table-row !important - } - - td.visible-lg, th.visible-lg { - display: table-cell !important - } -} - -@media (min-width: 1200px) { - .visible-lg-block { - display: block !important - } -} - -@media (min-width: 1200px) { - .visible-lg-inline { - display: inline !important - } -} - -@media (min-width: 1200px) { - .visible-lg-inline-block { - display: inline-block !important - } -} - -@media (max-width: 767px) { - .hidden-xs { - display: none !important - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .hidden-sm { - display: none !important - } -} - -@media (min-width: 992px) and (max-width: 1199px) { - .hidden-md { - display: none !important - } -} - -@media (min-width: 1200px) { - .hidden-lg { - display: none !important - } -} - -.visible-print { - display: none !important -} - -@media print { - .visible-print { - display: block !important - } - - table.visible-print { - display: table !important - } - - tr.visible-print { - display: table-row !important - } - - td.visible-print, th.visible-print { - display: table-cell !important - } -} - -.visible-print-block { - display: none !important -} - -@media print { - .visible-print-block { - display: block !important - } -} - -.visible-print-inline { - display: none !important -} - -@media print { - .visible-print-inline { - display: inline !important - } -} - -.visible-print-inline-block { - display: none !important -} - -@media print { - .visible-print-inline-block { - display: inline-block !important - } -} - -@media print { - .hidden-print { - display: none !important - } -} \ No newline at end of file diff --git a/src/main/webapp/resources/css/common.css b/src/main/webapp/resources/css/common.css deleted file mode 100644 index e5e835f1f..000000000 --- a/src/main/webapp/resources/css/common.css +++ /dev/null @@ -1,75 +0,0 @@ -body { - padding-top: 100px; - padding-bottom: 50px; - background-color: #ffffff; - -} -form { - border: 3px solid #204a87; -} - -img.logo { - display: block; - margin-left: auto; - margin-right: auto; - width: 300px; - height: 70px; - - } -.form-signin { - max-width: 330px; - padding: 15px; - margin: 0 auto; -} -.btn-custom,.btn-custom:hover, .btn-custom:focus, .btn-custom:active { - border-radius: 0; - color: #ffffff; - background-color: #ef2929; - border-color: #ef2929; - -} -.btn-custom-LOGIN { - border-radius: 0; - color: #ffffff; - height:3em; - background-color: #26C6DA; - border-color: #ef2929; - -} - -.form-signin .form-signin-heading, -.form-signin .checkbox { - margin-bottom: 10px; -} - -.form-signin .checkbox { - font-weight: normal; -} - -.form-signin .form-control { - position: relative; - height: auto; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - padding: 10px; - font-size: 16px; -} - -.form-signin .form-control:focus { - z-index: 2; -} - -.form-signin input { - margin-top: 10px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} - -.form-signin button { - margin-top: 10px; -} - -.has-error { - color: red -} \ No newline at end of file diff --git a/src/main/webapp/resources/css/profile.css b/src/main/webapp/resources/css/profile.css deleted file mode 100644 index 41e4a126e..000000000 --- a/src/main/webapp/resources/css/profile.css +++ /dev/null @@ -1,244 +0,0 @@ -.mainbody { - background:#f0f0f0; -} -/* Special class on .container surrounding .navbar, used for positioning it into place. */ -.navbar-wrapper { - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 20; - margin-left: -15px; - margin-right: -15px; - //background-color:#1C3B47; -} -.navbar-custom { - background-color: #1C3B47; - border-color: #E7E7E7; - -} - -/* Flip around the padding for proper display in narrow viewports */ -.navbar-wrapper .container { - padding-left: 0; - padding-right: 0; -} -.navbar-wrapper .navbar { - padding-left: 15px; - padding-right: 15px; -} - -.navbar-content -{ - width:320px; - padding: 15px; - padding-bottom:0px; -} -.navbar-content:before, .navbar-content:after -{ - display: table; - content: ""; - line-height: 0; -} -.navbar-nav.navbar-right:last-child { - margin-right: 15px !important; -} -.navbar-footer -{ - background-color:#DDD; -} -.navbar-brand,.navbar-toggle,.brand_network{ - color: #FFFFFF; - font-weight: bold; - -} -.navbar-custom .navbar-nav > li > a { - color:#FFFFFF; - font-weight: bold; -} -.navbar-custom .navbar-toggle{ - color: #f57900; - background: #f57900; -} -.navbar-footer-content { padding:15px 15px 15px 15px; } -.dropdown-menu { -padding: 0px; -overflow: hidden; -} - -.brand_network { - color: #9D9D9D; - float: left; - position: absolute; - left: 70px; - top: 30px; - font-size: smaller; -} - -.post-content { - margin-left:58px; -} - -.badge-important { - margin-top: 3px; - margin-left: 25px; - position: absolute; -} - -body { - background-color: #e8e8e8; -} -//contact forms - -.container { - max-width:700px; - width:100%; - margin:0 auto; - position:relative; -} - -#contact input[type="text"], #contact input[type="email"], #contact input[type="tel"], #contact input[type="url"], #contact textarea, #contact button[type="submit"] { font:400 12px/16px "Open Sans", Helvetica, Arial, sans-serif; } - -#contact,#technologies,#about { - background:#F9F9F9; - padding:25px; - margin:50px 0; -} - -#contact h3,#technologies h3,#about h3 { - color: #F96; - display: block; - font-size: 30px; - font-weight: 400; -} - -#contact h4,#technologies h4,#about h4 { - margin:5px 0 15px; - display:block; - font-size:13px; -} - -fieldset { - border: medium none !important; - margin: 0 0 10px; - min-width: 100%; - padding: 0; - width: 100%; -} - -#contact input[type="text"], #contact input[type="email"], #contact input[type="tel"], #contact input[type="url"], #contact textarea { - width:100%; - border:1px solid #CCC; - background:#FFF; - margin:0 0 5px; - padding:10px; -} - -#contact input[type="text"]:hover, #contact input[type="email"]:hover, #contact input[type="tel"]:hover, #contact input[type="url"]:hover, #contact textarea:hover { - -webkit-transition:border-color 0.3s ease-in-out; - -moz-transition:border-color 0.3s ease-in-out; - transition:border-color 0.3s ease-in-out; - border:1px solid #AAA; -} - -#contact textarea { - height:100px; - max-width:100%; - resize:none; -} - -#contact button[type="submit"] { - cursor:pointer; - width:100%; - border:none; - background:#0CF; - color:#FFF; - margin:0 0 5px; - padding:10px; - font-size:15px; -} - -#contact button[type="submit"]:hover { - background:#09C; - -webkit-transition:background 0.3s ease-in-out; - -moz-transition:background 0.3s ease-in-out; - transition:background-color 0.3s ease-in-out; -} - -#contact button[type="submit"]:active { box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.5); } - -#contact input:focus, #contact textarea:focus { - outline:0; - border:1px solid #999; -} -::-webkit-input-placeholder { - color:#888; -} -:-moz-placeholder { - color:#888; -} -::-moz-placeholder { - color:#888; -} -:-ms-input-placeholder { - color:#888; -} -blockquote{ - display:block; - background: #fff; - padding: 15px 20px 15px 45px; - margin: 0 0 20px; - position: relative; - - /*Font*/ - font-family: Georgia, serif; - font-size: 16px; - line-height: 1.2; - color: #666; - text-align: justify; - - /*Borders - (Optional)*/ - border-left: 15px solid #c76c0c; - border-right: 2px solid #c76c0c; - - /*Box Shadow - (Optional)*/ - -moz-box-shadow: 2px 2px 15px #ccc; - -webkit-box-shadow: 2px 2px 15px #ccc; - box-shadow: 2px 2px 15px #ccc; -} - -blockquote::before{ - //content: "\201C"; /*Unicode for Left Double Quote*/ - - /*Font*/ - font-family: Verdana,sans-serif; - font-size: 60px; - font-weight: bold; - color: #1C3B47; - - /*Positioning*/ - position: absolute; - left: 25px; - top:5px; -} - -blockquote::after{ - /*Reset to make sure*/ - content: ""; -} - -blockquote a{ - text-decoration: none; - background: #eee; - cursor: pointer; - padding: 0 4px; - color: #c76c0c; -} - -blockquote a:hover{ - color: #1C3B47; -} - -blockquote em{ - font-style: italic; -} \ No newline at end of file diff --git a/src/main/webapp/resources/css/w3.css b/src/main/webapp/resources/css/w3.css deleted file mode 100644 index 9fda92c8c..000000000 --- a/src/main/webapp/resources/css/w3.css +++ /dev/null @@ -1,231 +0,0 @@ -/* W3.CSS 4.06 November 2017 by Jan Egil and Borge Refsnes */ -html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} -/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ -html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} -article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block} -audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline} -audio:not([controls]){display:none;height:0}[hidden],template{display:none} -a{background-color:transparent;-webkit-text-decoration-skip:objects} -a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted} -dfn{font-style:italic}mark{background:#ff0;color:#000} -small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} -sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}svg:not(:root){overflow:hidden} -code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible} -button,input,select,textarea{font:inherit;margin:0}optgroup{font-weight:bold} -button,input{overflow:visible}button,select{text-transform:none} -button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button} -button::-moz-focus-inner, [type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner{border-style:none;padding:0} -button:-moz-focusring, [type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring{outline:1px dotted ButtonText} -fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em} -legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto} -[type=checkbox],[type=radio]{padding:0} -[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto} -[type=search]{-webkit-appearance:textfield;outline-offset:-2px} -[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none} -::-webkit-input-placeholder{color:inherit;opacity:0.54} -::-webkit-file-upload-button{-webkit-appearance:button;font:inherit} -/* End extract */ -html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden} -h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}.w3-serif{font-family:serif} -h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px} -hr{border:0;border-top:1px solid #eee;margin:20px 0} -.w3-image{max-width:100%;height:auto}img{margin-bottom:-5px}a{color:inherit} -.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc} -.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1} -.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1} -.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center} -.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top} -.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px} -.w3-btn,.w3-button{border:none;display:inline-block;outline:0;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap} -.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)} -.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} -.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none} -.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none} -.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%} -.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none} -.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block} -.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s} -.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%} -.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc} -.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer} -.w3-dropdown-hover:hover .w3-dropdown-content{display:block;z-index:1} -.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000} -.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000} -.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0} -.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px} -.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto} -.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%} -.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%} -.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px} -.w3-main,#main{transition:margin-left .4s} -.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)} -.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px} -.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto} -.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;outline:none;display:block} -.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left} -.w3-bar .w3-button{white-space:normal} -.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;outline:none;white-space:normal;float:none} -.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%} -.w3-responsive{display:block;overflow-x:auto} -.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before, -.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both} -.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%} -.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%} -.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%} -.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%} -@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%} -.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%} -.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}} -@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%} -.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%} -.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}} -.w3-content{max-width:980px;margin:auto}.w3-rest{overflow:hidden} -.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell} -.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom} -.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important} -@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px} -.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative} -.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center} -.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}} -@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}} -@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}} -@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}} -@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}} -.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0} -.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2} -.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0} -.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0} -.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)} -.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)} -.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)} -.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} -.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} -.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none} -.w3-display-position{position:absolute} -.w3-circle{border-radius:50%} -.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} -.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} -.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} -.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} -.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} -.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} -.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)} -.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)} -.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}} -.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} -.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}} -.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}} -.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}} -.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}} -.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}} -.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}} -.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important} -.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1} -.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75} -.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)} -.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)} -.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)} -.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important} -.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important} -.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important} -.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important} -.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important} -.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important} -.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important} -.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important} -.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important} -.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important} -.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important} -.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px 8px 50px}.w3-padding-large{padding:12px 24px!important} -.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important} -.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important} -.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important} -.w3-left{float:left!important}.w3-right{float:right!important} -.w3-button:hover{color:#000!important;background-color:#ccc!important} -.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} -.w3-hover-none:hover{box-shadow:none!important} -/* Colors */ -.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} -.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} -.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} -.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} -.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} -.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} -.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important} -.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important} -.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important} -.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important} -.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important} -.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important} -.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important} -.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important} -.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} -.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} -.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} -.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} -.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} -.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} -.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} -.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} -.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} -.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important} -.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} -.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} -.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} -.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} -.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} -.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important} -.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important} -.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important} -.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important} -.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important} -.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important} -.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important} -.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important} -.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important} -.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important} -.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important} -.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important} -.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important} -.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important} -.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important} -.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important} -.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important} -.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important} -.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important} -.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important} -.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important} -.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important} -.w3-text-white,.w3-hover-text-white:hover{color:#fff!important} -.w3-text-black,.w3-hover-text-black:hover{color:#000!important} -.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important} -.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important} -.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important} -.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important} -.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important} -.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important} -.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important} -.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important} -.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important} -.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important} -.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important} -.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important} -.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important} -.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important} -.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important} -.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important} -.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important} -.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important} -.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important} -.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important} -.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important} -.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important} -.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important} -.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important} -.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important} -.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important} -.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important} -.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important} -.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important} -.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important} -.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important} \ No newline at end of file diff --git a/src/main/webapp/resources/js/bootstrap.min.js b/src/main/webapp/resources/js/bootstrap.min.js deleted file mode 100644 index f363824be..000000000 --- a/src/main/webapp/resources/js/bootstrap.min.js +++ /dev/null @@ -1,784 +0,0 @@ -/*! - * Bootstrap v3.3.5 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under the MIT license - */ -if ("undefined" == typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery"); -+function (a) { - "use strict"; - var b = a.fn.jquery.split(" ")[0].split("."); - if (b[0] < 2 && b[1] < 9 || 1 == b[0] && 9 == b[1] && b[2] < 1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher") -}(jQuery), +function (a) { - "use strict"; - function b() { - var a = document.createElement("bootstrap"), b = { - WebkitTransition: "webkitTransitionEnd", - MozTransition: "transitionend", - OTransition: "oTransitionEnd otransitionend", - transition: "transitionend" - }; - for (var c in b)if (void 0 !== a.style[c])return {end: b[c]}; - return !1 - } - - a.fn.emulateTransitionEnd = function (b) { - var c = !1, d = this; - a(this).one("bsTransitionEnd", function () { - c = !0 - }); - var e = function () { - c || a(d).trigger(a.support.transition.end) - }; - return setTimeout(e, b), this - }, a(function () { - a.support.transition = b(), a.support.transition && (a.event.special.bsTransitionEnd = { - bindType: a.support.transition.end, - delegateType: a.support.transition.end, - handle: function (b) { - return a(b.target).is(this) ? b.handleObj.handler.apply(this, arguments) : void 0 - } - }) - }) -}(jQuery), +function (a) { - "use strict"; - function b(b) { - return this.each(function () { - var c = a(this), e = c.data("bs.alert"); - e || c.data("bs.alert", e = new d(this)), "string" == typeof b && e[b].call(c) - }) - } - - var c = '[data-dismiss="alert"]', d = function (b) { - a(b).on("click", c, this.close) - }; - d.VERSION = "3.3.5", d.TRANSITION_DURATION = 150, d.prototype.close = function (b) { - function c() { - g.detach().trigger("closed.bs.alert").remove() - } - - var e = a(this), f = e.attr("data-target"); - f || (f = e.attr("href"), f = f && f.replace(/.*(?=#[^\s]*$)/, "")); - var g = a(f); - b && b.preventDefault(), g.length || (g = e.closest(".alert")), g.trigger(b = a.Event("close.bs.alert")), b.isDefaultPrevented() || (g.removeClass("in"), a.support.transition && g.hasClass("fade") ? g.one("bsTransitionEnd", c).emulateTransitionEnd(d.TRANSITION_DURATION) : c()) - }; - var e = a.fn.alert; - a.fn.alert = b, a.fn.alert.Constructor = d, a.fn.alert.noConflict = function () { - return a.fn.alert = e, this - }, a(document).on("click.bs.alert.data-api", c, d.prototype.close) -}(jQuery), +function (a) { - "use strict"; - function b(b) { - return this.each(function () { - var d = a(this), e = d.data("bs.button"), f = "object" == typeof b && b; - e || d.data("bs.button", e = new c(this, f)), "toggle" == b ? e.toggle() : b && e.setState(b) - }) - } - - var c = function (b, d) { - this.$element = a(b), this.options = a.extend({}, c.DEFAULTS, d), this.isLoading = !1 - }; - c.VERSION = "3.3.5", c.DEFAULTS = {loadingText: "loading..."}, c.prototype.setState = function (b) { - var c = "disabled", d = this.$element, e = d.is("input") ? "val" : "html", f = d.data(); - b += "Text", null == f.resetText && d.data("resetText", d[e]()), setTimeout(a.proxy(function () { - d[e](null == f[b] ? this.options[b] : f[b]), "loadingText" == b ? (this.isLoading = !0, d.addClass(c).attr(c, c)) : this.isLoading && (this.isLoading = !1, d.removeClass(c).removeAttr(c)) - }, this), 0) - }, c.prototype.toggle = function () { - var a = !0, b = this.$element.closest('[data-toggle="buttons"]'); - if (b.length) { - var c = this.$element.find("input"); - "radio" == c.prop("type") ? (c.prop("checked") && (a = !1), b.find(".active").removeClass("active"), this.$element.addClass("active")) : "checkbox" == c.prop("type") && (c.prop("checked") !== this.$element.hasClass("active") && (a = !1), this.$element.toggleClass("active")), c.prop("checked", this.$element.hasClass("active")), a && c.trigger("change") - } else this.$element.attr("aria-pressed", !this.$element.hasClass("active")), this.$element.toggleClass("active") - }; - var d = a.fn.button; - a.fn.button = b, a.fn.button.Constructor = c, a.fn.button.noConflict = function () { - return a.fn.button = d, this - }, a(document).on("click.bs.button.data-api", '[data-toggle^="button"]', function (c) { - var d = a(c.target); - d.hasClass("btn") || (d = d.closest(".btn")), b.call(d, "toggle"), a(c.target).is('input[type="radio"]') || a(c.target).is('input[type="checkbox"]') || c.preventDefault() - }).on("focus.bs.button.data-api blur.bs.button.data-api", '[data-toggle^="button"]', function (b) { - a(b.target).closest(".btn").toggleClass("focus", /^focus(in)?$/.test(b.type)) - }) -}(jQuery), +function (a) { - "use strict"; - function b(b) { - return this.each(function () { - var d = a(this), e = d.data("bs.carousel"), f = a.extend({}, c.DEFAULTS, d.data(), "object" == typeof b && b), g = "string" == typeof b ? b : f.slide; - e || d.data("bs.carousel", e = new c(this, f)), "number" == typeof b ? e.to(b) : g ? e[g]() : f.interval && e.pause().cycle() - }) - } - - var c = function (b, c) { - this.$element = a(b), this.$indicators = this.$element.find(".carousel-indicators"), this.options = c, this.paused = null, this.sliding = null, this.interval = null, this.$active = null, this.$items = null, this.options.keyboard && this.$element.on("keydown.bs.carousel", a.proxy(this.keydown, this)), "hover" == this.options.pause && !("ontouchstart"in document.documentElement) && this.$element.on("mouseenter.bs.carousel", a.proxy(this.pause, this)).on("mouseleave.bs.carousel", a.proxy(this.cycle, this)) - }; - c.VERSION = "3.3.5", c.TRANSITION_DURATION = 600, c.DEFAULTS = { - interval: 5e3, - pause: "hover", - wrap: !0, - keyboard: !0 - }, c.prototype.keydown = function (a) { - if (!/input|textarea/i.test(a.target.tagName)) { - switch (a.which) { - case 37: - this.prev(); - break; - case 39: - this.next(); - break; - default: - return - } - a.preventDefault() - } - }, c.prototype.cycle = function (b) { - return b || (this.paused = !1), this.interval && clearInterval(this.interval), this.options.interval && !this.paused && (this.interval = setInterval(a.proxy(this.next, this), this.options.interval)), this - }, c.prototype.getItemIndex = function (a) { - return this.$items = a.parent().children(".item"), this.$items.index(a || this.$active) - }, c.prototype.getItemForDirection = function (a, b) { - var c = this.getItemIndex(b), d = "prev" == a && 0 === c || "next" == a && c == this.$items.length - 1; - if (d && !this.options.wrap)return b; - var e = "prev" == a ? -1 : 1, f = (c + e) % this.$items.length; - return this.$items.eq(f) - }, c.prototype.to = function (a) { - var b = this, c = this.getItemIndex(this.$active = this.$element.find(".item.active")); - return a > this.$items.length - 1 || 0 > a ? void 0 : this.sliding ? this.$element.one("slid.bs.carousel", function () { - b.to(a) - }) : c == a ? this.pause().cycle() : this.slide(a > c ? "next" : "prev", this.$items.eq(a)) - }, c.prototype.pause = function (b) { - return b || (this.paused = !0), this.$element.find(".next, .prev").length && a.support.transition && (this.$element.trigger(a.support.transition.end), this.cycle(!0)), this.interval = clearInterval(this.interval), this - }, c.prototype.next = function () { - return this.sliding ? void 0 : this.slide("next") - }, c.prototype.prev = function () { - return this.sliding ? void 0 : this.slide("prev") - }, c.prototype.slide = function (b, d) { - var e = this.$element.find(".item.active"), f = d || this.getItemForDirection(b, e), g = this.interval, h = "next" == b ? "left" : "right", i = this; - if (f.hasClass("active"))return this.sliding = !1; - var j = f[0], k = a.Event("slide.bs.carousel", {relatedTarget: j, direction: h}); - if (this.$element.trigger(k), !k.isDefaultPrevented()) { - if (this.sliding = !0, g && this.pause(), this.$indicators.length) { - this.$indicators.find(".active").removeClass("active"); - var l = a(this.$indicators.children()[this.getItemIndex(f)]); - l && l.addClass("active") - } - var m = a.Event("slid.bs.carousel", {relatedTarget: j, direction: h}); - return a.support.transition && this.$element.hasClass("slide") ? (f.addClass(b), f[0].offsetWidth, e.addClass(h), f.addClass(h), e.one("bsTransitionEnd", function () { - f.removeClass([b, h].join(" ")).addClass("active"), e.removeClass(["active", h].join(" ")), i.sliding = !1, setTimeout(function () { - i.$element.trigger(m) - }, 0) - }).emulateTransitionEnd(c.TRANSITION_DURATION)) : (e.removeClass("active"), f.addClass("active"), this.sliding = !1, this.$element.trigger(m)), g && this.cycle(), this - } - }; - var d = a.fn.carousel; - a.fn.carousel = b, a.fn.carousel.Constructor = c, a.fn.carousel.noConflict = function () { - return a.fn.carousel = d, this - }; - var e = function (c) { - var d, e = a(this), f = a(e.attr("data-target") || (d = e.attr("href")) && d.replace(/.*(?=#[^\s]+$)/, "")); - if (f.hasClass("carousel")) { - var g = a.extend({}, f.data(), e.data()), h = e.attr("data-slide-to"); - h && (g.interval = !1), b.call(f, g), h && f.data("bs.carousel").to(h), c.preventDefault() - } - }; - a(document).on("click.bs.carousel.data-api", "[data-slide]", e).on("click.bs.carousel.data-api", "[data-slide-to]", e), a(window).on("load", function () { - a('[data-ride="carousel"]').each(function () { - var c = a(this); - b.call(c, c.data()) - }) - }) -}(jQuery), +function (a) { - "use strict"; - function b(b) { - var c, d = b.attr("data-target") || (c = b.attr("href")) && c.replace(/.*(?=#[^\s]+$)/, ""); - return a(d) - } - - function c(b) { - return this.each(function () { - var c = a(this), e = c.data("bs.collapse"), f = a.extend({}, d.DEFAULTS, c.data(), "object" == typeof b && b); - !e && f.toggle && /show|hide/.test(b) && (f.toggle = !1), e || c.data("bs.collapse", e = new d(this, f)), "string" == typeof b && e[b]() - }) - } - - var d = function (b, c) { - this.$element = a(b), this.options = a.extend({}, d.DEFAULTS, c), this.$trigger = a('[data-toggle="collapse"][href="#' + b.id + '"],[data-toggle="collapse"][data-target="#' + b.id + '"]'), this.transitioning = null, this.options.parent ? this.$parent = this.getParent() : this.addAriaAndCollapsedClass(this.$element, this.$trigger), this.options.toggle && this.toggle() - }; - d.VERSION = "3.3.5", d.TRANSITION_DURATION = 350, d.DEFAULTS = {toggle: !0}, d.prototype.dimension = function () { - var a = this.$element.hasClass("width"); - return a ? "width" : "height" - }, d.prototype.show = function () { - if (!this.transitioning && !this.$element.hasClass("in")) { - var b, e = this.$parent && this.$parent.children(".panel").children(".in, .collapsing"); - if (!(e && e.length && (b = e.data("bs.collapse"), b && b.transitioning))) { - var f = a.Event("show.bs.collapse"); - if (this.$element.trigger(f), !f.isDefaultPrevented()) { - e && e.length && (c.call(e, "hide"), b || e.data("bs.collapse", null)); - var g = this.dimension(); - this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded", !0), this.$trigger.removeClass("collapsed").attr("aria-expanded", !0), this.transitioning = 1; - var h = function () { - this.$element.removeClass("collapsing").addClass("collapse in")[g](""), this.transitioning = 0, this.$element.trigger("shown.bs.collapse") - }; - if (!a.support.transition)return h.call(this); - var i = a.camelCase(["scroll", g].join("-")); - this.$element.one("bsTransitionEnd", a.proxy(h, this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i]) - } - } - } - }, d.prototype.hide = function () { - if (!this.transitioning && this.$element.hasClass("in")) { - var b = a.Event("hide.bs.collapse"); - if (this.$element.trigger(b), !b.isDefaultPrevented()) { - var c = this.dimension(); - this.$element[c](this.$element[c]())[0].offsetHeight, this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded", !1), this.$trigger.addClass("collapsed").attr("aria-expanded", !1), this.transitioning = 1; - var e = function () { - this.transitioning = 0, this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse") - }; - return a.support.transition ? void this.$element[c](0).one("bsTransitionEnd", a.proxy(e, this)).emulateTransitionEnd(d.TRANSITION_DURATION) : e.call(this) - } - } - }, d.prototype.toggle = function () { - this[this.$element.hasClass("in") ? "hide" : "show"]() - }, d.prototype.getParent = function () { - return a(this.options.parent).find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]').each(a.proxy(function (c, d) { - var e = a(d); - this.addAriaAndCollapsedClass(b(e), e) - }, this)).end() - }, d.prototype.addAriaAndCollapsedClass = function (a, b) { - var c = a.hasClass("in"); - a.attr("aria-expanded", c), b.toggleClass("collapsed", !c).attr("aria-expanded", c) - }; - var e = a.fn.collapse; - a.fn.collapse = c, a.fn.collapse.Constructor = d, a.fn.collapse.noConflict = function () { - return a.fn.collapse = e, this - }, a(document).on("click.bs.collapse.data-api", '[data-toggle="collapse"]', function (d) { - var e = a(this); - e.attr("data-target") || d.preventDefault(); - var f = b(e), g = f.data("bs.collapse"), h = g ? "toggle" : e.data(); - c.call(f, h) - }) -}(jQuery), +function (a) { - "use strict"; - function b(b) { - var c = b.attr("data-target"); - c || (c = b.attr("href"), c = c && /#[A-Za-z]/.test(c) && c.replace(/.*(?=#[^\s]*$)/, "")); - var d = c && a(c); - return d && d.length ? d : b.parent() - } - - function c(c) { - c && 3 === c.which || (a(e).remove(), a(f).each(function () { - var d = a(this), e = b(d), f = {relatedTarget: this}; - e.hasClass("open") && (c && "click" == c.type && /input|textarea/i.test(c.target.tagName) && a.contains(e[0], c.target) || (e.trigger(c = a.Event("hide.bs.dropdown", f)), c.isDefaultPrevented() || (d.attr("aria-expanded", "false"), e.removeClass("open").trigger("hidden.bs.dropdown", f)))) - })) - } - - function d(b) { - return this.each(function () { - var c = a(this), d = c.data("bs.dropdown"); - d || c.data("bs.dropdown", d = new g(this)), "string" == typeof b && d[b].call(c) - }) - } - - var e = ".dropdown-backdrop", f = '[data-toggle="dropdown"]', g = function (b) { - a(b).on("click.bs.dropdown", this.toggle) - }; - g.VERSION = "3.3.5", g.prototype.toggle = function (d) { - var e = a(this); - if (!e.is(".disabled, :disabled")) { - var f = b(e), g = f.hasClass("open"); - if (c(), !g) { - "ontouchstart"in document.documentElement && !f.closest(".navbar-nav").length && a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click", c); - var h = {relatedTarget: this}; - if (f.trigger(d = a.Event("show.bs.dropdown", h)), d.isDefaultPrevented())return; - e.trigger("focus").attr("aria-expanded", "true"), f.toggleClass("open").trigger("shown.bs.dropdown", h) - } - return !1 - } - }, g.prototype.keydown = function (c) { - if (/(38|40|27|32)/.test(c.which) && !/input|textarea/i.test(c.target.tagName)) { - var d = a(this); - if (c.preventDefault(), c.stopPropagation(), !d.is(".disabled, :disabled")) { - var e = b(d), g = e.hasClass("open"); - if (!g && 27 != c.which || g && 27 == c.which)return 27 == c.which && e.find(f).trigger("focus"), d.trigger("click"); - var h = " li:not(.disabled):visible a", i = e.find(".dropdown-menu" + h); - if (i.length) { - var j = i.index(c.target); - 38 == c.which && j > 0 && j--, 40 == c.which && j < i.length - 1 && j++, ~j || (j = 0), i.eq(j).trigger("focus") - } - } - } - }; - var h = a.fn.dropdown; - a.fn.dropdown = d, a.fn.dropdown.Constructor = g, a.fn.dropdown.noConflict = function () { - return a.fn.dropdown = h, this - }, a(document).on("click.bs.dropdown.data-api", c).on("click.bs.dropdown.data-api", ".dropdown form", function (a) { - a.stopPropagation() - }).on("click.bs.dropdown.data-api", f, g.prototype.toggle).on("keydown.bs.dropdown.data-api", f, g.prototype.keydown).on("keydown.bs.dropdown.data-api", ".dropdown-menu", g.prototype.keydown) -}(jQuery), +function (a) { - "use strict"; - function b(b, d) { - return this.each(function () { - var e = a(this), f = e.data("bs.modal"), g = a.extend({}, c.DEFAULTS, e.data(), "object" == typeof b && b); - f || e.data("bs.modal", f = new c(this, g)), "string" == typeof b ? f[b](d) : g.show && f.show(d) - }) - } - - var c = function (b, c) { - this.options = c, this.$body = a(document.body), this.$element = a(b), this.$dialog = this.$element.find(".modal-dialog"), this.$backdrop = null, this.isShown = null, this.originalBodyPad = null, this.scrollbarWidth = 0, this.ignoreBackdropClick = !1, this.options.remote && this.$element.find(".modal-content").load(this.options.remote, a.proxy(function () { - this.$element.trigger("loaded.bs.modal") - }, this)) - }; - c.VERSION = "3.3.5", c.TRANSITION_DURATION = 300, c.BACKDROP_TRANSITION_DURATION = 150, c.DEFAULTS = { - backdrop: !0, - keyboard: !0, - show: !0 - }, c.prototype.toggle = function (a) { - return this.isShown ? this.hide() : this.show(a) - }, c.prototype.show = function (b) { - var d = this, e = a.Event("show.bs.modal", {relatedTarget: b}); - this.$element.trigger(e), this.isShown || e.isDefaultPrevented() || (this.isShown = !0, this.checkScrollbar(), this.setScrollbar(), this.$body.addClass("modal-open"), this.escape(), this.resize(), this.$element.on("click.dismiss.bs.modal", '[data-dismiss="modal"]', a.proxy(this.hide, this)), this.$dialog.on("mousedown.dismiss.bs.modal", function () { - d.$element.one("mouseup.dismiss.bs.modal", function (b) { - a(b.target).is(d.$element) && (d.ignoreBackdropClick = !0) - }) - }), this.backdrop(function () { - var e = a.support.transition && d.$element.hasClass("fade"); - d.$element.parent().length || d.$element.appendTo(d.$body), d.$element.show().scrollTop(0), d.adjustDialog(), e && d.$element[0].offsetWidth, d.$element.addClass("in"), d.enforceFocus(); - var f = a.Event("shown.bs.modal", {relatedTarget: b}); - e ? d.$dialog.one("bsTransitionEnd", function () { - d.$element.trigger("focus").trigger(f) - }).emulateTransitionEnd(c.TRANSITION_DURATION) : d.$element.trigger("focus").trigger(f) - })) - }, c.prototype.hide = function (b) { - b && b.preventDefault(), b = a.Event("hide.bs.modal"), this.$element.trigger(b), this.isShown && !b.isDefaultPrevented() && (this.isShown = !1, this.escape(), this.resize(), a(document).off("focusin.bs.modal"), this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"), this.$dialog.off("mousedown.dismiss.bs.modal"), a.support.transition && this.$element.hasClass("fade") ? this.$element.one("bsTransitionEnd", a.proxy(this.hideModal, this)).emulateTransitionEnd(c.TRANSITION_DURATION) : this.hideModal()) - }, c.prototype.enforceFocus = function () { - a(document).off("focusin.bs.modal").on("focusin.bs.modal", a.proxy(function (a) { - this.$element[0] === a.target || this.$element.has(a.target).length || this.$element.trigger("focus") - }, this)) - }, c.prototype.escape = function () { - this.isShown && this.options.keyboard ? this.$element.on("keydown.dismiss.bs.modal", a.proxy(function (a) { - 27 == a.which && this.hide() - }, this)) : this.isShown || this.$element.off("keydown.dismiss.bs.modal") - }, c.prototype.resize = function () { - this.isShown ? a(window).on("resize.bs.modal", a.proxy(this.handleUpdate, this)) : a(window).off("resize.bs.modal") - }, c.prototype.hideModal = function () { - var a = this; - this.$element.hide(), this.backdrop(function () { - a.$body.removeClass("modal-open"), a.resetAdjustments(), a.resetScrollbar(), a.$element.trigger("hidden.bs.modal") - }) - }, c.prototype.removeBackdrop = function () { - this.$backdrop && this.$backdrop.remove(), this.$backdrop = null - }, c.prototype.backdrop = function (b) { - var d = this, e = this.$element.hasClass("fade") ? "fade" : ""; - if (this.isShown && this.options.backdrop) { - var f = a.support.transition && e; - if (this.$backdrop = a(document.createElement("div")).addClass("modal-backdrop " + e).appendTo(this.$body), this.$element.on("click.dismiss.bs.modal", a.proxy(function (a) { - return this.ignoreBackdropClick ? void(this.ignoreBackdropClick = !1) : void(a.target === a.currentTarget && ("static" == this.options.backdrop ? this.$element[0].focus() : this.hide())) - }, this)), f && this.$backdrop[0].offsetWidth, this.$backdrop.addClass("in"), !b)return; - f ? this.$backdrop.one("bsTransitionEnd", b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION) : b() - } else if (!this.isShown && this.$backdrop) { - this.$backdrop.removeClass("in"); - var g = function () { - d.removeBackdrop(), b && b() - }; - a.support.transition && this.$element.hasClass("fade") ? this.$backdrop.one("bsTransitionEnd", g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION) : g() - } else b && b() - }, c.prototype.handleUpdate = function () { - this.adjustDialog() - }, c.prototype.adjustDialog = function () { - var a = this.$element[0].scrollHeight > document.documentElement.clientHeight; - this.$element.css({ - paddingLeft: !this.bodyIsOverflowing && a ? this.scrollbarWidth : "", - paddingRight: this.bodyIsOverflowing && !a ? this.scrollbarWidth : "" - }) - }, c.prototype.resetAdjustments = function () { - this.$element.css({paddingLeft: "", paddingRight: ""}) - }, c.prototype.checkScrollbar = function () { - var a = window.innerWidth; - if (!a) { - var b = document.documentElement.getBoundingClientRect(); - a = b.right - Math.abs(b.left) - } - this.bodyIsOverflowing = document.body.clientWidth < a, this.scrollbarWidth = this.measureScrollbar() - }, c.prototype.setScrollbar = function () { - var a = parseInt(this.$body.css("padding-right") || 0, 10); - this.originalBodyPad = document.body.style.paddingRight || "", this.bodyIsOverflowing && this.$body.css("padding-right", a + this.scrollbarWidth) - }, c.prototype.resetScrollbar = function () { - this.$body.css("padding-right", this.originalBodyPad) - }, c.prototype.measureScrollbar = function () { - var a = document.createElement("div"); - a.className = "modal-scrollbar-measure", this.$body.append(a); - var b = a.offsetWidth - a.clientWidth; - return this.$body[0].removeChild(a), b - }; - var d = a.fn.modal; - a.fn.modal = b, a.fn.modal.Constructor = c, a.fn.modal.noConflict = function () { - return a.fn.modal = d, this - }, a(document).on("click.bs.modal.data-api", '[data-toggle="modal"]', function (c) { - var d = a(this), e = d.attr("href"), f = a(d.attr("data-target") || e && e.replace(/.*(?=#[^\s]+$)/, "")), g = f.data("bs.modal") ? "toggle" : a.extend({remote: !/#/.test(e) && e}, f.data(), d.data()); - d.is("a") && c.preventDefault(), f.one("show.bs.modal", function (a) { - a.isDefaultPrevented() || f.one("hidden.bs.modal", function () { - d.is(":visible") && d.trigger("focus") - }) - }), b.call(f, g, this) - }) -}(jQuery), +function (a) { - "use strict"; - function b(b) { - return this.each(function () { - var d = a(this), e = d.data("bs.tooltip"), f = "object" == typeof b && b; - (e || !/destroy|hide/.test(b)) && (e || d.data("bs.tooltip", e = new c(this, f)), "string" == typeof b && e[b]()) - }) - } - - var c = function (a, b) { - this.type = null, this.options = null, this.enabled = null, this.timeout = null, this.hoverState = null, this.$element = null, this.inState = null, this.init("tooltip", a, b) - }; - c.VERSION = "3.3.5", c.TRANSITION_DURATION = 150, c.DEFAULTS = { - animation: !0, - placement: "top", - selector: !1, - template: '', - trigger: "hover focus", - title: "", - delay: 0, - html: !1, - container: !1, - viewport: {selector: "body", padding: 0} - }, c.prototype.init = function (b, c, d) { - if (this.enabled = !0, this.type = b, this.$element = a(c), this.options = this.getOptions(d), this.$viewport = this.options.viewport && a(a.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : this.options.viewport.selector || this.options.viewport), this.inState = { - click: !1, - hover: !1, - focus: !1 - }, this.$element[0]instanceof document.constructor && !this.options.selector)throw new Error("`selector` option must be specified when initializing " + this.type + " on the window.document object!"); - for (var e = this.options.trigger.split(" "), f = e.length; f--;) { - var g = e[f]; - if ("click" == g)this.$element.on("click." + this.type, this.options.selector, a.proxy(this.toggle, this)); else if ("manual" != g) { - var h = "hover" == g ? "mouseenter" : "focusin", i = "hover" == g ? "mouseleave" : "focusout"; - this.$element.on(h + "." + this.type, this.options.selector, a.proxy(this.enter, this)), this.$element.on(i + "." + this.type, this.options.selector, a.proxy(this.leave, this)) - } - } - this.options.selector ? this._options = a.extend({}, this.options, { - trigger: "manual", - selector: "" - }) : this.fixTitle() - }, c.prototype.getDefaults = function () { - return c.DEFAULTS - }, c.prototype.getOptions = function (b) { - return b = a.extend({}, this.getDefaults(), this.$element.data(), b), b.delay && "number" == typeof b.delay && (b.delay = { - show: b.delay, - hide: b.delay - }), b - }, c.prototype.getDelegateOptions = function () { - var b = {}, c = this.getDefaults(); - return this._options && a.each(this._options, function (a, d) { - c[a] != d && (b[a] = d) - }), b - }, c.prototype.enter = function (b) { - var c = b instanceof this.constructor ? b : a(b.currentTarget).data("bs." + this.type); - return c || (c = new this.constructor(b.currentTarget, this.getDelegateOptions()), a(b.currentTarget).data("bs." + this.type, c)), b instanceof a.Event && (c.inState["focusin" == b.type ? "focus" : "hover"] = !0), c.tip().hasClass("in") || "in" == c.hoverState ? void(c.hoverState = "in") : (clearTimeout(c.timeout), c.hoverState = "in", c.options.delay && c.options.delay.show ? void(c.timeout = setTimeout(function () { - "in" == c.hoverState && c.show() - }, c.options.delay.show)) : c.show()) - }, c.prototype.isInStateTrue = function () { - for (var a in this.inState)if (this.inState[a])return !0; - return !1 - }, c.prototype.leave = function (b) { - var c = b instanceof this.constructor ? b : a(b.currentTarget).data("bs." + this.type); - return c || (c = new this.constructor(b.currentTarget, this.getDelegateOptions()), a(b.currentTarget).data("bs." + this.type, c)), b instanceof a.Event && (c.inState["focusout" == b.type ? "focus" : "hover"] = !1), c.isInStateTrue() ? void 0 : (clearTimeout(c.timeout), c.hoverState = "out", c.options.delay && c.options.delay.hide ? void(c.timeout = setTimeout(function () { - "out" == c.hoverState && c.hide() - }, c.options.delay.hide)) : c.hide()) - }, c.prototype.show = function () { - var b = a.Event("show.bs." + this.type); - if (this.hasContent() && this.enabled) { - this.$element.trigger(b); - var d = a.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]); - if (b.isDefaultPrevented() || !d)return; - var e = this, f = this.tip(), g = this.getUID(this.type); - this.setContent(), f.attr("id", g), this.$element.attr("aria-describedby", g), this.options.animation && f.addClass("fade"); - var h = "function" == typeof this.options.placement ? this.options.placement.call(this, f[0], this.$element[0]) : this.options.placement, i = /\s?auto?\s?/i, j = i.test(h); - j && (h = h.replace(i, "") || "top"), f.detach().css({ - top: 0, - left: 0, - display: "block" - }).addClass(h).data("bs." + this.type, this), this.options.container ? f.appendTo(this.options.container) : f.insertAfter(this.$element), this.$element.trigger("inserted.bs." + this.type); - var k = this.getPosition(), l = f[0].offsetWidth, m = f[0].offsetHeight; - if (j) { - var n = h, o = this.getPosition(this.$viewport); - h = "bottom" == h && k.bottom + m > o.bottom ? "top" : "top" == h && k.top - m < o.top ? "bottom" : "right" == h && k.right + l > o.width ? "left" : "left" == h && k.left - l < o.left ? "right" : h, f.removeClass(n).addClass(h) - } - var p = this.getCalculatedOffset(h, k, l, m); - this.applyPlacement(p, h); - var q = function () { - var a = e.hoverState; - e.$element.trigger("shown.bs." + e.type), e.hoverState = null, "out" == a && e.leave(e) - }; - a.support.transition && this.$tip.hasClass("fade") ? f.one("bsTransitionEnd", q).emulateTransitionEnd(c.TRANSITION_DURATION) : q() - } - }, c.prototype.applyPlacement = function (b, c) { - var d = this.tip(), e = d[0].offsetWidth, f = d[0].offsetHeight, g = parseInt(d.css("margin-top"), 10), h = parseInt(d.css("margin-left"), 10); - isNaN(g) && (g = 0), isNaN(h) && (h = 0), b.top += g, b.left += h, a.offset.setOffset(d[0], a.extend({ - using: function (a) { - d.css({top: Math.round(a.top), left: Math.round(a.left)}) - } - }, b), 0), d.addClass("in"); - var i = d[0].offsetWidth, j = d[0].offsetHeight; - "top" == c && j != f && (b.top = b.top + f - j); - var k = this.getViewportAdjustedDelta(c, b, i, j); - k.left ? b.left += k.left : b.top += k.top; - var l = /top|bottom/.test(c), m = l ? 2 * k.left - e + i : 2 * k.top - f + j, n = l ? "offsetWidth" : "offsetHeight"; - d.offset(b), this.replaceArrow(m, d[0][n], l) - }, c.prototype.replaceArrow = function (a, b, c) { - this.arrow().css(c ? "left" : "top", 50 * (1 - a / b) + "%").css(c ? "top" : "left", "") - }, c.prototype.setContent = function () { - var a = this.tip(), b = this.getTitle(); - a.find(".tooltip-inner")[this.options.html ? "html" : "text"](b), a.removeClass("fade in top bottom left right") - }, c.prototype.hide = function (b) { - function d() { - "in" != e.hoverState && f.detach(), e.$element.removeAttr("aria-describedby").trigger("hidden.bs." + e.type), b && b() - } - - var e = this, f = a(this.$tip), g = a.Event("hide.bs." + this.type); - return this.$element.trigger(g), g.isDefaultPrevented() ? void 0 : (f.removeClass("in"), a.support.transition && f.hasClass("fade") ? f.one("bsTransitionEnd", d).emulateTransitionEnd(c.TRANSITION_DURATION) : d(), this.hoverState = null, this) - }, c.prototype.fixTitle = function () { - var a = this.$element; - (a.attr("title") || "string" != typeof a.attr("data-original-title")) && a.attr("data-original-title", a.attr("title") || "").attr("title", "") - }, c.prototype.hasContent = function () { - return this.getTitle() - }, c.prototype.getPosition = function (b) { - b = b || this.$element; - var c = b[0], d = "BODY" == c.tagName, e = c.getBoundingClientRect(); - null == e.width && (e = a.extend({}, e, {width: e.right - e.left, height: e.bottom - e.top})); - var f = d ? { - top: 0, - left: 0 - } : b.offset(), g = {scroll: d ? document.documentElement.scrollTop || document.body.scrollTop : b.scrollTop()}, h = d ? { - width: a(window).width(), - height: a(window).height() - } : null; - return a.extend({}, e, g, h, f) - }, c.prototype.getCalculatedOffset = function (a, b, c, d) { - return "bottom" == a ? { - top: b.top + b.height, - left: b.left + b.width / 2 - c / 2 - } : "top" == a ? { - top: b.top - d, - left: b.left + b.width / 2 - c / 2 - } : "left" == a ? {top: b.top + b.height / 2 - d / 2, left: b.left - c} : { - top: b.top + b.height / 2 - d / 2, - left: b.left + b.width - } - }, c.prototype.getViewportAdjustedDelta = function (a, b, c, d) { - var e = {top: 0, left: 0}; - if (!this.$viewport)return e; - var f = this.options.viewport && this.options.viewport.padding || 0, g = this.getPosition(this.$viewport); - if (/right|left/.test(a)) { - var h = b.top - f - g.scroll, i = b.top + f - g.scroll + d; - h < g.top ? e.top = g.top - h : i > g.top + g.height && (e.top = g.top + g.height - i) - } else { - var j = b.left - f, k = b.left + f + c; - j < g.left ? e.left = g.left - j : k > g.right && (e.left = g.left + g.width - k) - } - return e - }, c.prototype.getTitle = function () { - var a, b = this.$element, c = this.options; - return a = b.attr("data-original-title") || ("function" == typeof c.title ? c.title.call(b[0]) : c.title) - }, c.prototype.getUID = function (a) { - do a += ~~(1e6 * Math.random()); while (document.getElementById(a)); - return a - }, c.prototype.tip = function () { - if (!this.$tip && (this.$tip = a(this.options.template), 1 != this.$tip.length))throw new Error(this.type + " `template` option must consist of exactly 1 top-level element!"); - return this.$tip - }, c.prototype.arrow = function () { - return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow") - }, c.prototype.enable = function () { - this.enabled = !0 - }, c.prototype.disable = function () { - this.enabled = !1 - }, c.prototype.toggleEnabled = function () { - this.enabled = !this.enabled - }, c.prototype.toggle = function (b) { - var c = this; - b && (c = a(b.currentTarget).data("bs." + this.type), c || (c = new this.constructor(b.currentTarget, this.getDelegateOptions()), a(b.currentTarget).data("bs." + this.type, c))), b ? (c.inState.click = !c.inState.click, c.isInStateTrue() ? c.enter(c) : c.leave(c)) : c.tip().hasClass("in") ? c.leave(c) : c.enter(c) - }, c.prototype.destroy = function () { - var a = this; - clearTimeout(this.timeout), this.hide(function () { - a.$element.off("." + a.type).removeData("bs." + a.type), a.$tip && a.$tip.detach(), a.$tip = null, a.$arrow = null, a.$viewport = null - }) - }; - var d = a.fn.tooltip; - a.fn.tooltip = b, a.fn.tooltip.Constructor = c, a.fn.tooltip.noConflict = function () { - return a.fn.tooltip = d, this - } -}(jQuery), +function (a) { - "use strict"; - function b(b) { - return this.each(function () { - var d = a(this), e = d.data("bs.popover"), f = "object" == typeof b && b; - (e || !/destroy|hide/.test(b)) && (e || d.data("bs.popover", e = new c(this, f)), "string" == typeof b && e[b]()) - }) - } - - var c = function (a, b) { - this.init("popover", a, b) - }; - if (!a.fn.tooltip)throw new Error("Popover requires tooltip.js"); - c.VERSION = "3.3.5", c.DEFAULTS = a.extend({}, a.fn.tooltip.Constructor.DEFAULTS, { - placement: "right", - trigger: "click", - content: "", - template: '' - }), c.prototype = a.extend({}, a.fn.tooltip.Constructor.prototype), c.prototype.constructor = c, c.prototype.getDefaults = function () { - return c.DEFAULTS - }, c.prototype.setContent = function () { - var a = this.tip(), b = this.getTitle(), c = this.getContent(); - a.find(".popover-title")[this.options.html ? "html" : "text"](b), a.find(".popover-content").children().detach().end()[this.options.html ? "string" == typeof c ? "html" : "append" : "text"](c), a.removeClass("fade top bottom left right in"), a.find(".popover-title").html() || a.find(".popover-title").hide() - }, c.prototype.hasContent = function () { - return this.getTitle() || this.getContent() - }, c.prototype.getContent = function () { - var a = this.$element, b = this.options; - return a.attr("data-content") || ("function" == typeof b.content ? b.content.call(a[0]) : b.content) - }, c.prototype.arrow = function () { - return this.$arrow = this.$arrow || this.tip().find(".arrow") - }; - var d = a.fn.popover; - a.fn.popover = b, a.fn.popover.Constructor = c, a.fn.popover.noConflict = function () { - return a.fn.popover = d, this - } -}(jQuery), +function (a) { - "use strict"; - function b(c, d) { - this.$body = a(document.body), this.$scrollElement = a(a(c).is(document.body) ? window : c), this.options = a.extend({}, b.DEFAULTS, d), this.selector = (this.options.target || "") + " .nav li > a", this.offsets = [], this.targets = [], this.activeTarget = null, this.scrollHeight = 0, this.$scrollElement.on("scroll.bs.scrollspy", a.proxy(this.process, this)), this.refresh(), this.process() - } - - function c(c) { - return this.each(function () { - var d = a(this), e = d.data("bs.scrollspy"), f = "object" == typeof c && c; - e || d.data("bs.scrollspy", e = new b(this, f)), "string" == typeof c && e[c]() - }) - } - - b.VERSION = "3.3.5", b.DEFAULTS = {offset: 10}, b.prototype.getScrollHeight = function () { - return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) - }, b.prototype.refresh = function () { - var b = this, c = "offset", d = 0; - this.offsets = [], this.targets = [], this.scrollHeight = this.getScrollHeight(), a.isWindow(this.$scrollElement[0]) || (c = "position", d = this.$scrollElement.scrollTop()), this.$body.find(this.selector).map(function () { - var b = a(this), e = b.data("target") || b.attr("href"), f = /^#./.test(e) && a(e); - return f && f.length && f.is(":visible") && [[f[c]().top + d, e]] || null - }).sort(function (a, b) { - return a[0] - b[0] - }).each(function () { - b.offsets.push(this[0]), b.targets.push(this[1]) - }) - }, b.prototype.process = function () { - var a, b = this.$scrollElement.scrollTop() + this.options.offset, c = this.getScrollHeight(), d = this.options.offset + c - this.$scrollElement.height(), e = this.offsets, f = this.targets, g = this.activeTarget; - if (this.scrollHeight != c && this.refresh(), b >= d)return g != (a = f[f.length - 1]) && this.activate(a); - if (g && b < e[0])return this.activeTarget = null, this.clear(); - for (a = e.length; a--;)g != f[a] && b >= e[a] && (void 0 === e[a + 1] || b < e[a + 1]) && this.activate(f[a]) - }, b.prototype.activate = function (b) { - this.activeTarget = b, this.clear(); - var c = this.selector + '[data-target="' + b + '"],' + this.selector + '[href="' + b + '"]', d = a(c).parents("li").addClass("active"); - d.parent(".dropdown-menu").length && (d = d.closest("li.dropdown").addClass("active")), - d.trigger("activate.bs.scrollspy") - }, b.prototype.clear = function () { - a(this.selector).parentsUntil(this.options.target, ".active").removeClass("active") - }; - var d = a.fn.scrollspy; - a.fn.scrollspy = c, a.fn.scrollspy.Constructor = b, a.fn.scrollspy.noConflict = function () { - return a.fn.scrollspy = d, this - }, a(window).on("load.bs.scrollspy.data-api", function () { - a('[data-spy="scroll"]').each(function () { - var b = a(this); - c.call(b, b.data()) - }) - }) -}(jQuery), +function (a) { - "use strict"; - function b(b) { - return this.each(function () { - var d = a(this), e = d.data("bs.tab"); - e || d.data("bs.tab", e = new c(this)), "string" == typeof b && e[b]() - }) - } - - var c = function (b) { - this.element = a(b) - }; - c.VERSION = "3.3.5", c.TRANSITION_DURATION = 150, c.prototype.show = function () { - var b = this.element, c = b.closest("ul:not(.dropdown-menu)"), d = b.data("target"); - if (d || (d = b.attr("href"), d = d && d.replace(/.*(?=#[^\s]*$)/, "")), !b.parent("li").hasClass("active")) { - var e = c.find(".active:last a"), f = a.Event("hide.bs.tab", {relatedTarget: b[0]}), g = a.Event("show.bs.tab", {relatedTarget: e[0]}); - if (e.trigger(f), b.trigger(g), !g.isDefaultPrevented() && !f.isDefaultPrevented()) { - var h = a(d); - this.activate(b.closest("li"), c), this.activate(h, h.parent(), function () { - e.trigger({type: "hidden.bs.tab", relatedTarget: b[0]}), b.trigger({ - type: "shown.bs.tab", - relatedTarget: e[0] - }) - }) - } - } - }, c.prototype.activate = function (b, d, e) { - function f() { - g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded", !1), b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded", !0), h ? (b[0].offsetWidth, b.addClass("in")) : b.removeClass("fade"), b.parent(".dropdown-menu").length && b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded", !0), e && e() - } - - var g = d.find("> .active"), h = e && a.support.transition && (g.length && g.hasClass("fade") || !!d.find("> .fade").length); - g.length && h ? g.one("bsTransitionEnd", f).emulateTransitionEnd(c.TRANSITION_DURATION) : f(), g.removeClass("in") - }; - var d = a.fn.tab; - a.fn.tab = b, a.fn.tab.Constructor = c, a.fn.tab.noConflict = function () { - return a.fn.tab = d, this - }; - var e = function (c) { - c.preventDefault(), b.call(a(this), "show") - }; - a(document).on("click.bs.tab.data-api", '[data-toggle="tab"]', e).on("click.bs.tab.data-api", '[data-toggle="pill"]', e) -}(jQuery), +function (a) { - "use strict"; - function b(b) { - return this.each(function () { - var d = a(this), e = d.data("bs.affix"), f = "object" == typeof b && b; - e || d.data("bs.affix", e = new c(this, f)), "string" == typeof b && e[b]() - }) - } - - var c = function (b, d) { - this.options = a.extend({}, c.DEFAULTS, d), this.$target = a(this.options.target).on("scroll.bs.affix.data-api", a.proxy(this.checkPosition, this)).on("click.bs.affix.data-api", a.proxy(this.checkPositionWithEventLoop, this)), this.$element = a(b), this.affixed = null, this.unpin = null, this.pinnedOffset = null, this.checkPosition() - }; - c.VERSION = "3.3.5", c.RESET = "affix affix-top affix-bottom", c.DEFAULTS = { - offset: 0, - target: window - }, c.prototype.getState = function (a, b, c, d) { - var e = this.$target.scrollTop(), f = this.$element.offset(), g = this.$target.height(); - if (null != c && "top" == this.affixed)return c > e ? "top" : !1; - if ("bottom" == this.affixed)return null != c ? e + this.unpin <= f.top ? !1 : "bottom" : a - d >= e + g ? !1 : "bottom"; - var h = null == this.affixed, i = h ? e : f.top, j = h ? g : b; - return null != c && c >= e ? "top" : null != d && i + j >= a - d ? "bottom" : !1 - }, c.prototype.getPinnedOffset = function () { - if (this.pinnedOffset)return this.pinnedOffset; - this.$element.removeClass(c.RESET).addClass("affix"); - var a = this.$target.scrollTop(), b = this.$element.offset(); - return this.pinnedOffset = b.top - a - }, c.prototype.checkPositionWithEventLoop = function () { - setTimeout(a.proxy(this.checkPosition, this), 1) - }, c.prototype.checkPosition = function () { - if (this.$element.is(":visible")) { - var b = this.$element.height(), d = this.options.offset, e = d.top, f = d.bottom, g = Math.max(a(document).height(), a(document.body).height()); - "object" != typeof d && (f = e = d), "function" == typeof e && (e = d.top(this.$element)), "function" == typeof f && (f = d.bottom(this.$element)); - var h = this.getState(g, b, e, f); - if (this.affixed != h) { - null != this.unpin && this.$element.css("top", ""); - var i = "affix" + (h ? "-" + h : ""), j = a.Event(i + ".bs.affix"); - if (this.$element.trigger(j), j.isDefaultPrevented())return; - this.affixed = h, this.unpin = "bottom" == h ? this.getPinnedOffset() : null, this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix", "affixed") + ".bs.affix") - } - "bottom" == h && this.$element.offset({top: g - b - f}) - } - }; - var d = a.fn.affix; - a.fn.affix = b, a.fn.affix.Constructor = c, a.fn.affix.noConflict = function () { - return a.fn.affix = d, this - }, a(window).on("load", function () { - a('[data-spy="affix"]').each(function () { - var c = a(this), d = c.data(); - d.offset = d.offset || {}, null != d.offsetBottom && (d.offset.bottom = d.offsetBottom), null != d.offsetTop && (d.offset.top = d.offsetTop), b.call(c, d) - }) - }) -}(jQuery); \ No newline at end of file diff --git a/src/test/java/com/visualpathit/account/controllerTest/SampleTest.java b/src/test/java/com/visualpathit/account/controllerTest/SampleTest.java deleted file mode 100644 index 5513889c0..000000000 --- a/src/test/java/com/visualpathit/account/controllerTest/SampleTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.visualpathit.account.controllerTest; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -public class SampleTest { - @Test - public void SampleTestHappyFlow(){ - assertEquals("Hello".length(), 5); - } - -} diff --git a/src/test/java/com/visualpathit/account/controllerTest/UserControllerTest.java b/src/test/java/com/visualpathit/account/controllerTest/UserControllerTest.java deleted file mode 100644 index 3097f1adf..000000000 --- a/src/test/java/com/visualpathit/account/controllerTest/UserControllerTest.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.visualpathit.account.controllerTest; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; - -import com.visualpathit.account.controller.UserController; -import com.visualpathit.account.model.User; -import com.visualpathit.account.service.UserService; -import com.visualpathit.account.setup.StandaloneMvcTestViewResolver; - - -public class UserControllerTest { - - @Mock - private UserService controllerSer; - @InjectMocks - private UserController controller; - private MockMvc mockMvc; - - @Before - public void setup(){ - MockitoAnnotations.initMocks(this); - - /*InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); - viewResolver.setPrefix("/WEB-INF/views/"); - viewResolver.setSuffix(".jsp"); - */ - mockMvc = MockMvcBuilders.standaloneSetup(controller) - .setViewResolvers(new StandaloneMvcTestViewResolver()).build(); - } - - @Test - public void registrationTestforHappyFlow() throws Exception{ - User user = new User(); - mockMvc.perform(get("/registration")) - .andExpect(status().isOk()) - .andExpect(view().name("registration")) - .andExpect(forwardedUrl("registration")); - - } - @Test - public void registrationTestforNullValueHappyFlow() throws Exception{ - mockMvc.perform(get("/registration")) - .andExpect(status().isOk()) - .andExpect(view().name("registration")) - .andExpect(forwardedUrl("registration")); - - } - /*@Test - public void registrationTestforPostValueHappyFlow() throws Exception{ - String description =new String("Error String"); - UserValidator userValidator; - BindingResult bindingResult; - when(userValidator.validate(new User(),bindingResult)) - .thenThrow(bindingResult.hasErrors()); - mockMvc.perform(post("/registration").contentType(MediaType.APPLICATION_FORM_URLENCODED) - .param("userForm","userForm")) - - .andExpect(status().isOk()); - //.andExpect(view().name("redirect:/welcome")) - //.andExpect(forwardedUrl("redirect:/welcome")); - - }*/ - @Test - public void loginTestHappyFlow() throws Exception{ - String error = "Your username and password is invalid"; - mockMvc.perform(get("/login").param(error, error)) - .andExpect(status().isOk()) - .andExpect(view().name("login")) - .andExpect(forwardedUrl("login")); - - } - @Test - public void welcomeTestHappyFlow() throws Exception{ - mockMvc.perform(get("/welcome")) - .andExpect(status().isOk()) - .andExpect(view().name("welcome")) - .andExpect(forwardedUrl("welcome")); - - } - @Test - public void welcomeAfterDirectLoginTestHappyFlow() throws Exception{ - mockMvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(view().name("welcome")) - .andExpect(forwardedUrl("welcome")); - - } - @Test - public void indexTestHappyFlow() throws Exception{ - mockMvc.perform(get("/index")) - .andExpect(status().isOk()) - .andExpect(view().name("index_home")) - .andExpect(forwardedUrl("index_home")); - - } - -} diff --git a/src/test/java/com/visualpathit/account/modelTest/RoleTest.java b/src/test/java/com/visualpathit/account/modelTest/RoleTest.java deleted file mode 100644 index 07cede5f4..000000000 --- a/src/test/java/com/visualpathit/account/modelTest/RoleTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.visualpathit.account.modelTest; - -import junit.framework.Assert; - -import java.util.HashSet; -import java.util.Set; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import com.visualpathit.account.model.Role; -import com.visualpathit.account.model.User; - -/** {@author imrant} !*/ -public class RoleTest { - - public static final Long EXPECTED_ID = 1L; - public static final String EXPECTED_ROLENAME = "Admin"; - public static final int EXPECTED_SIZE = 1; - private Role role; - @Before - public void setUp() throws Exception { - User user = new User(); - user.setId(1L); - user.setUsername("Wahidkhan74"); - user.setPassword("Wahidkhan74"); - user.setUserEmail("XXXXX@gmail.com"); - - Set users = new HashSet(); - users.add(user); - role = new Role(); - role.setId(1L); - role.setName("Admin"); - role.setUsers(users); - } - - @After - public void tearDown() throws Exception { - System.out.println("Test Completed"); - - } - - @Test - public void testUserDetailsHappyFlow() throws Exception { - Assert.assertEquals(EXPECTED_ID, role.getId()); - Assert.assertEquals(EXPECTED_ROLENAME, role.getName()); - Assert.assertEquals(EXPECTED_SIZE,role.getUsers().size()); - - } -} \ No newline at end of file diff --git a/src/test/java/com/visualpathit/account/modelTest/UserTest.java b/src/test/java/com/visualpathit/account/modelTest/UserTest.java deleted file mode 100644 index a56950000..000000000 --- a/src/test/java/com/visualpathit/account/modelTest/UserTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.visualpathit.account.modelTest; - -import junit.framework.Assert; - -import java.util.HashSet; -import java.util.Set; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import com.visualpathit.account.model.Role; -import com.visualpathit.account.model.User; - -/** {@author imrant} !*/ -public class UserTest { - - public static final Long EXPECTED_ID = 1L; - public static final int EXPECTED_SIZE = 1; - public static final String EXPECTED_USERNAME = "Wahidkhan74"; - public static final String EXPECTED_PASSWD = "Wahidkhan74"; - public static final String EXPECTED_USEREMAIL = "XXXXX@gmail.com"; - private User user; - @Before - public void setUp() throws Exception { - - Role role = new Role(); - role.setId(1L); - role.setName("Admin"); - Set roles = new HashSet(); - roles.add(role); - - user = new User(); - user.setId(1L); - user.setUsername("Wahidkhan74"); - user.setPassword("Wahidkhan74"); - user.setUserEmail("XXXXX@gmail.com"); - user.setRoles(roles); - } - - @After - public void tearDown() throws Exception { - System.out.println("Test Completed"); - - } - - @Test - public void testUserDetailsHappyFlow() throws Exception { - Assert.assertEquals(EXPECTED_ID, user.getId()); - Assert.assertEquals(EXPECTED_USERNAME, user.getUsername()); - Assert.assertEquals(EXPECTED_PASSWD, user.getPassword()); - Assert.assertEquals(EXPECTED_USEREMAIL, user.getUserEmail()); - Assert.assertEquals(EXPECTED_SIZE,user.getRoles().size()); - - } -} \ No newline at end of file diff --git a/src/test/java/com/visualpathit/account/setup/StandaloneMvcTestViewResolver.java b/src/test/java/com/visualpathit/account/setup/StandaloneMvcTestViewResolver.java deleted file mode 100644 index f282b2a25..000000000 --- a/src/test/java/com/visualpathit/account/setup/StandaloneMvcTestViewResolver.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.visualpathit.account.setup; - -import org.springframework.web.servlet.view.AbstractUrlBasedView; -import org.springframework.web.servlet.view.InternalResourceView; -import org.springframework.web.servlet.view.InternalResourceViewResolver; - -public class StandaloneMvcTestViewResolver extends InternalResourceViewResolver { - - public StandaloneMvcTestViewResolver() { - super(); - } - - @Override - protected AbstractUrlBasedView buildView(final String viewName) throws Exception { - final InternalResourceView view = (InternalResourceView) super.buildView(viewName); - // prevent checking for circular view paths - view.setPreventDispatchLoop(false); - return view; - } -} diff --git a/vagrant/Automated_provisioning_MacOSM1/Vagrantfile b/vagrant/Automated_provisioning_MacOSM1/Vagrantfile deleted file mode 100644 index b1e293cce..000000000 --- a/vagrant/Automated_provisioning_MacOSM1/Vagrantfile +++ /dev/null @@ -1,50 +0,0 @@ -Vagrant.configure("2") do |config| - config.hostmanager.enabled = true - config.hostmanager.manage_host = true - -### DB vm #### - config.vm.define "db01" do |db01| - db01.vm.box = "jacobw/fedora35-arm64" - db01.vm.hostname = "db01" - db01.vm.network "private_network", ip: "192.168.56.25" - db01.vm.provision "shell", path: "mysql.sh" - - end - -### Memcache vm #### - config.vm.define "mc01" do |mc01| - mc01.vm.box = "jacobw/fedora35-arm64" - mc01.vm.hostname = "mc01" - mc01.vm.network "private_network", ip: "192.168.56.24" - mc01.vm.provision "shell", path: "memcache.sh" - end - -### RabbitMQ vm #### - config.vm.define "rmq01" do |rmq01| - rmq01.vm.box = "jacobw/fedora35-arm64" - rmq01.vm.hostname = "rmq01" - rmq01.vm.network "private_network", ip: "192.168.56.23" - rmq01.vm.provision "shell", path: "rabbitmq.sh" - end - -### tomcat vm ### - config.vm.define "app01" do |app01| - app01.vm.box = "jacobw/fedora35-arm64" - app01.vm.hostname = "app01" - app01.vm.network "private_network", ip: "192.168.56.22" - app01.vm.provision "shell", path: "tomcat.sh" - app01.vm.provider "vmware_desktop" do |vb| - vb.memory = "1024" - end - end - - -### Nginx VM ### - config.vm.define "web01" do |web01| - web01.vm.box = "spox/ubuntu-arm" - web01.vm.hostname = "web01" - web01.vm.network "private_network", ip: "192.168.56.21" - web01.vm.provision "shell", path: "nginx.sh" -end - -end diff --git a/vagrant/Automated_provisioning_MacOSM1/backend.sh b/vagrant/Automated_provisioning_MacOSM1/backend.sh deleted file mode 100644 index e993776bb..000000000 --- a/vagrant/Automated_provisioning_MacOSM1/backend.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -DATABASE_PASS='admin123' - -# MEmcache -yum install epel-release -y -yum install memcached -y -systemctl start memcached -systemctl enable memcached -systemctl status memcached -memcached -p 11211 -U 11111 -u memcached -d - -# Rabbit -yum install socat -y -yum install erlang -y -yum install wget -y -wget https://www.rabbitmq.com/releases/rabbitmq-server/v3.6.10/rabbitmq-server-3.6.10-1.el7.noarch.rpm -rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc -yum update -rpm -Uvh rabbitmq-server-3.6.10-1.el7.noarch.rpm -systemctl start rabbitmq-server -systemctl enable rabbitmq-server -systemctl status rabbitmq-server -echo "[{rabbit, [{loopback_users, []}]}]." > /etc/rabbitmq/rabbitmq.config -rabbitmqctl add_user rabbit bunny -rabbitmqctl set_user_tags rabbit administrator -systemctl restart rabbitmq-server - -# Mysql -yum install mariadb-server -y - -#mysql_secure_installation -sed -i 's/^127.0.0.1/0.0.0.0/' /etc/my.cnf - -# starting & enabling mariadb-server -systemctl start mariadb -systemctl enable mariadb - -#restore the dump file for the application -mysqladmin -u root password "$DATABASE_PASS" -mysql -u root -p"$DATABASE_PASS" -e "UPDATE mysql.user SET Password=PASSWORD('$DATABASE_PASS') WHERE User='root'" -mysql -u root -p"$DATABASE_PASS" -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')" -mysql -u root -p"$DATABASE_PASS" -e "DELETE FROM mysql.user WHERE User=''" -mysql -u root -p"$DATABASE_PASS" -e "DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%'" -mysql -u root -p"$DATABASE_PASS" -e "FLUSH PRIVILEGES" -mysql -u root -p"$DATABASE_PASS" -e "create database accounts" -mysql -u root -p"$DATABASE_PASS" -e "grant all privileges on accounts.* TO 'admin'@'localhost' identified by 'admin123'" -mysql -u root -p"$DATABASE_PASS" -e "grant all privileges on accounts.* TO 'admin'@'app01' identified by 'admin123'" -mysql -u root -p"$DATABASE_PASS" accounts < /vagrant/vprofile-repo/src/main/resources/db_backup.sql -mysql -u root -p"$DATABASE_PASS" -e "FLUSH PRIVILEGES" - -# Restart mariadb-server -systemctl restart mariadb \ No newline at end of file diff --git a/vagrant/Automated_provisioning_MacOSM1/memcache.sh b/vagrant/Automated_provisioning_MacOSM1/memcache.sh deleted file mode 100644 index 86554fb72..000000000 --- a/vagrant/Automated_provisioning_MacOSM1/memcache.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -mv /etc/yum.repos.d/fedora-updates.repo /tmp/ -mv /etc/yum.repos.d/fedora-updates-modular.repo /tmp/ -yum clean all -#yum update -sudo yum install epel-release -y -sudo yum install memcached -y -sudo systemctl start memcached -sudo systemctl enable memcached -sudo systemctl status memcached -firewall-cmd --add-port=11211/tcp --permanent -firewall-cmd --reload -sed -i 's/OPTIONS="-l 127.0.0.1"/OPTIONS=""/' /etc/sysconfig/memcached -sudo systemctl restart memcached - -sudo memcached -p 11211 -U 11111 -u memcached -d diff --git a/vagrant/Automated_provisioning_MacOSM1/mysql.sh b/vagrant/Automated_provisioning_MacOSM1/mysql.sh deleted file mode 100644 index d1e9b23cc..000000000 --- a/vagrant/Automated_provisioning_MacOSM1/mysql.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -sudo mv /etc/yum.repos.d/fedora-updates.repo /tmp/ -sudo mv /etc/yum.repos.d/fedora-updates-modular.repo /tmp/ -sudo yum clean all -#sudo yum update -y -DATABASE_PASS='admin123' -#sudo yum install epel-release -y -sudo yum install git zip unzip -y -sudo yum install mariadb-server -y - - -# starting & enabling mariadb-server - -sudo systemctl start mariadb -sudo systemctl enable mariadb -cd /tmp/ -git clone -b main https://github.com/hkhcoder/vprofile-project.git -#restore the dump file for the application -sudo mysqladmin -u root password "$DATABASE_PASS" -#sudo mysql -u root -p"$DATABASE_PASS" -e "UPDATE mysql.user SET Password=PASSWORD('$DATABASE_PASS') WHERE User='root'" -sudo mysql -u root -p"$DATABASE_PASS" -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')" -sudo mysql -u root -p"$DATABASE_PASS" -e "DELETE FROM mysql.user WHERE User=''" -sudo mysql -u root -p"$DATABASE_PASS" -e "DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%'" -sudo mysql -u root -p"$DATABASE_PASS" -e "FLUSH PRIVILEGES" -sudo mysql -u root -p"$DATABASE_PASS" -e "create database accounts" -sudo mysql -u root -p"$DATABASE_PASS" -e "grant all privileges on accounts.* TO 'admin'@'localhost' identified by 'admin123'" -sudo mysql -u root -p"$DATABASE_PASS" -e "grant all privileges on accounts.* TO 'admin'@'%' identified by 'admin123'" -sudo mysql -u root -p"$DATABASE_PASS" accounts < /tmp/vprofile-project/src/main/resources/db_backup.sql -sudo mysql -u root -p"$DATABASE_PASS" -e "FLUSH PRIVILEGES" - -# Restart mariadb-server -sudo systemctl restart mariadb - - -#starting the firewall and allowing the mariadb to access from port no. 3306 -#sudo systemctl start firewalld -#sudo systemctl enable firewalld -#sudo firewall-cmd --get-active-zones -#sudo firewall-cmd --zone=public --add-port=3306/tcp --permanent -#sudo firewall-cmd --reload -sudo systemctl stop firewalld -sudo systemctl disable firewalld -sudo systemctl restart mariadb diff --git a/vagrant/Automated_provisioning_MacOSM1/nginx.sh b/vagrant/Automated_provisioning_MacOSM1/nginx.sh deleted file mode 100644 index c5116f540..000000000 --- a/vagrant/Automated_provisioning_MacOSM1/nginx.sh +++ /dev/null @@ -1,32 +0,0 @@ -# adding repository and installing nginx -apt update -apt install nginx -y -cat < vproapp -upstream vproapp { - - server app01:8080; - -} - -server { - - listen 80; - -location / { - - proxy_pass http://vproapp; - -} - -} - -EOT - -mv vproapp /etc/nginx/sites-available/vproapp -rm -rf /etc/nginx/sites-enabled/default -ln -s /etc/nginx/sites-available/vproapp /etc/nginx/sites-enabled/vproapp - -#starting nginx service and firewall -systemctl start nginx -systemctl enable nginx -systemctl restart nginx diff --git a/vagrant/Automated_provisioning_MacOSM1/rabbitmq.sh b/vagrant/Automated_provisioning_MacOSM1/rabbitmq.sh deleted file mode 100644 index b37e17c9c..000000000 --- a/vagrant/Automated_provisioning_MacOSM1/rabbitmq.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -sudo mv /etc/yum.repos.d/fedora-updates.repo /tmp/ -sudo mv /etc/yum.repos.d/fedora-updates-modular.repo /tmp/ -sudo yum clean all -#sudo yum update -y -echo "SElinux changes." -sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config -setenforce 0 -echo -echo -curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash -sudo yum clean all -sudo yum makecache -sudo yum install erlang -y -curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.rpm.sh | sudo bash -sudo yum install rabbitmq-server -y -rpm -qi rabbitmq-server -systemctl start rabbitmq-server -sudo systemctl enable rabbitmq-server -sudo systemctl status rabbitmq-server -sudo sh -c 'echo "[{rabbit, [{loopback_users, []}]}]." > /etc/rabbitmq/rabbitmq.config' -sudo rabbitmqctl add_user test test -sudo rabbitmqctl set_user_tags test administrator -firewall-cmd --add-port=5671/tcp --permanent -firewall-cmd --add-port=5672/tcp --permanent -firewall-cmd --reload -sudo systemctl restart rabbitmq-server -nohup sleep 30 && reboot & -echo "going to reboot now" diff --git a/vagrant/Automated_provisioning_MacOSM1/tomcat.sh b/vagrant/Automated_provisioning_MacOSM1/tomcat.sh deleted file mode 100644 index d337effd1..000000000 --- a/vagrant/Automated_provisioning_MacOSM1/tomcat.sh +++ /dev/null @@ -1,63 +0,0 @@ -sudo mv /etc/yum.repos.d/fedora-updates.repo /tmp/ -sudo mv /etc/yum.repos.d/fedora-updates-modular.repo /tmp/ -sudo yum clean all -#sudo yum update -TOMURL="https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.75/bin/apache-tomcat-9.0.75.tar.gz" -yum install java-11-openjdk java-11-openjdk-devel -y -yum install git maven wget -y -cd /tmp/ -wget $TOMURL -O tomcatbin.tar.gz -EXTOUT=`tar xzvf tomcatbin.tar.gz` -TOMDIR=`echo $EXTOUT | cut -d '/' -f1` -useradd --shell /sbin/nologin tomcat -rsync -avzh /tmp/$TOMDIR/ /usr/local/tomcat/ -chown -R tomcat.tomcat /usr/local/tomcat - -rm -rf /etc/systemd/system/tomcat.service - -cat <> /etc/systemd/system/tomcat.service -[Unit] -Description=Tomcat -After=network.target - -[Service] - -User=tomcat -Group=tomcat - -WorkingDirectory=/usr/local/tomcat - -#Environment=JRE_HOME=/usr/lib/jvm/jre -Environment=JAVA_HOME=/usr/lib/jvm/jre - -Environment=CATALINA_PID=/var/tomcat/%i/run/tomcat.pid -Environment=CATALINA_HOME=/usr/local/tomcat -Environment=CATALINE_BASE=/usr/local/tomcat - -ExecStart=/usr/local/tomcat/bin/catalina.sh run -ExecStop=/usr/local/tomcat/bin/shutdown.sh - - -RestartSec=10 -Restart=always - -[Install] -WantedBy=multi-user.target - -EOT - -systemctl daemon-reload -systemctl start tomcat -systemctl enable tomcat - -git clone -b main https://github.com/hkhcoder/vprofile-project.git -cd vprofile-project -mvn install -systemctl stop tomcat -sleep 60 -rm -rf /usr/local/tomcat/webapps/ROOT* -cp target/vprofile-v2.war /usr/local/tomcat/webapps/ROOT.war -systemctl start tomcat -firewall-cmd --add-port=8080/tcp --permanent -firewall-cmd --reload -systemctl restart tomcat diff --git a/vagrant/Automated_provisioning_MacOSM1/tomcat_ubuntu.sh b/vagrant/Automated_provisioning_MacOSM1/tomcat_ubuntu.sh deleted file mode 100644 index 762c127d3..000000000 --- a/vagrant/Automated_provisioning_MacOSM1/tomcat_ubuntu.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -sudo apt update -sudo apt upgrade -y -sudo apt install openjdk-8-jdk -y -sudo apt install tomcat8 tomcat8-admin tomcat8-docs tomcat8-common git -y diff --git a/vagrant/Automated_provisioning_WinMacIntel/Vagrantfile b/vagrant/Automated_provisioning_WinMacIntel/Vagrantfile deleted file mode 100644 index c1a17fab5..000000000 --- a/vagrant/Automated_provisioning_WinMacIntel/Vagrantfile +++ /dev/null @@ -1,68 +0,0 @@ -Vagrant.configure("2") do |config| - config.hostmanager.enabled = true - config.hostmanager.manage_host = true - -### DB vm #### - config.vm.define "db01" do |db01| - db01.vm.box = "eurolinux-vagrant/centos-stream-9" - db01.vm.box_version = "9.0.43" - db01.vm.hostname = "db01" - db01.vm.network "private_network", ip: "192.168.56.15" - db01.vm.provider "virtualbox" do |vb| - vb.memory = "600" - end - db01.vm.provision "shell", path: "mysql.sh" - - end - -### Memcache vm #### - config.vm.define "mc01" do |mc01| - mc01.vm.box = "eurolinux-vagrant/centos-stream-9" - mc01.vm.box_version = "9.0.43" - mc01.vm.hostname = "mc01" - mc01.vm.network "private_network", ip: "192.168.56.14" - mc01.vm.provider "virtualbox" do |vb| - vb.memory = "600" - end - mc01.vm.provision "shell", path: "memcache.sh" - end - -### RabbitMQ vm #### - config.vm.define "rmq01" do |rmq01| - rmq01.vm.box = "eurolinux-vagrant/centos-stream-9" - rmq01.vm.box_version = "9.0.43" - rmq01.vm.hostname = "rmq01" - rmq01.vm.network "private_network", ip: "192.168.56.16" - rmq01.vm.provider "virtualbox" do |vb| - vb.memory = "600" - end - rmq01.vm.provision "shell", path: "rabbitmq.sh" - end - -### tomcat vm ### - config.vm.define "app01" do |app01| - app01.vm.box = "eurolinux-vagrant/centos-stream-9" - app01.vm.box_version = "9.0.43" - app01.vm.hostname = "app01" - app01.vm.network "private_network", ip: "192.168.56.12" - app01.vm.provision "shell", path: "tomcat.sh" - app01.vm.provider "virtualbox" do |vb| - vb.memory = "800" - end - end - - -### Nginx VM ### - config.vm.define "web01" do |web01| - web01.vm.box = "ubuntu/jammy64" - web01.vm.hostname = "web01" - web01.vm.network "private_network", ip: "192.168.56.11" -# web01.vm.network "public_network" - web01.vm.provider "virtualbox" do |vb| - vb.gui = true - vb.memory = "800" - end - web01.vm.provision "shell", path: "nginx.sh" -end - -end diff --git a/vagrant/Automated_provisioning_WinMacIntel/application.properties b/vagrant/Automated_provisioning_WinMacIntel/application.properties deleted file mode 100644 index 0540b942c..000000000 --- a/vagrant/Automated_provisioning_WinMacIntel/application.properties +++ /dev/null @@ -1,25 +0,0 @@ -#JDBC Configutation for Database Connection -jdbc.driverClassName=com.mysql.jdbc.Driver -jdbc.url=jdbc:mysql://db01:3306/accounts?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull -jdbc.username=admin -jdbc.password=admin123 - -#Memcached Configuration For Active and StandBy Host -#For Active Host -memcached.active.host=mc01 -memcached.active.port=11211 -#For StandBy Host -memcached.standBy.host=127.0.0.2 -memcached.standBy.port=11211 - -#RabbitMq Configuration -rabbitmq.address=rmq01 -rabbitmq.port=5672 -rabbitmq.username=test -rabbitmq.password=test - -#Elasticesearch Configuration -elasticsearch.host =192.168.1.85 -elasticsearch.port =9300 -elasticsearch.cluster=vprofile -elasticsearch.node=vprofilenode \ No newline at end of file diff --git a/vagrant/Automated_provisioning_WinMacIntel/backend.sh b/vagrant/Automated_provisioning_WinMacIntel/backend.sh deleted file mode 100644 index e993776bb..000000000 --- a/vagrant/Automated_provisioning_WinMacIntel/backend.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -DATABASE_PASS='admin123' - -# MEmcache -yum install epel-release -y -yum install memcached -y -systemctl start memcached -systemctl enable memcached -systemctl status memcached -memcached -p 11211 -U 11111 -u memcached -d - -# Rabbit -yum install socat -y -yum install erlang -y -yum install wget -y -wget https://www.rabbitmq.com/releases/rabbitmq-server/v3.6.10/rabbitmq-server-3.6.10-1.el7.noarch.rpm -rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc -yum update -rpm -Uvh rabbitmq-server-3.6.10-1.el7.noarch.rpm -systemctl start rabbitmq-server -systemctl enable rabbitmq-server -systemctl status rabbitmq-server -echo "[{rabbit, [{loopback_users, []}]}]." > /etc/rabbitmq/rabbitmq.config -rabbitmqctl add_user rabbit bunny -rabbitmqctl set_user_tags rabbit administrator -systemctl restart rabbitmq-server - -# Mysql -yum install mariadb-server -y - -#mysql_secure_installation -sed -i 's/^127.0.0.1/0.0.0.0/' /etc/my.cnf - -# starting & enabling mariadb-server -systemctl start mariadb -systemctl enable mariadb - -#restore the dump file for the application -mysqladmin -u root password "$DATABASE_PASS" -mysql -u root -p"$DATABASE_PASS" -e "UPDATE mysql.user SET Password=PASSWORD('$DATABASE_PASS') WHERE User='root'" -mysql -u root -p"$DATABASE_PASS" -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')" -mysql -u root -p"$DATABASE_PASS" -e "DELETE FROM mysql.user WHERE User=''" -mysql -u root -p"$DATABASE_PASS" -e "DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%'" -mysql -u root -p"$DATABASE_PASS" -e "FLUSH PRIVILEGES" -mysql -u root -p"$DATABASE_PASS" -e "create database accounts" -mysql -u root -p"$DATABASE_PASS" -e "grant all privileges on accounts.* TO 'admin'@'localhost' identified by 'admin123'" -mysql -u root -p"$DATABASE_PASS" -e "grant all privileges on accounts.* TO 'admin'@'app01' identified by 'admin123'" -mysql -u root -p"$DATABASE_PASS" accounts < /vagrant/vprofile-repo/src/main/resources/db_backup.sql -mysql -u root -p"$DATABASE_PASS" -e "FLUSH PRIVILEGES" - -# Restart mariadb-server -systemctl restart mariadb \ No newline at end of file diff --git a/vagrant/Automated_provisioning_WinMacIntel/memcache.sh b/vagrant/Automated_provisioning_WinMacIntel/memcache.sh deleted file mode 100644 index 8c4a33838..000000000 --- a/vagrant/Automated_provisioning_WinMacIntel/memcache.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -sudo dnf install epel-release -y -sudo dnf install memcached -y -sudo systemctl start memcached -sudo systemctl enable memcached -sudo systemctl status memcached -sed -i 's/127.0.0.1/0.0.0.0/g' /etc/sysconfig/memcached -sudo systemctl restart memcached -firewall-cmd --add-port=11211/tcp -firewall-cmd --runtime-to-permanent -firewall-cmd --add-port=11111/udp -firewall-cmd --runtime-to-permanent -sudo memcached -p 11211 -U 11111 -u memcached -d diff --git a/vagrant/Automated_provisioning_WinMacIntel/mysql.sh b/vagrant/Automated_provisioning_WinMacIntel/mysql.sh deleted file mode 100644 index 9beee3777..000000000 --- a/vagrant/Automated_provisioning_WinMacIntel/mysql.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -DATABASE_PASS='admin123' -sudo yum update -y -sudo yum install epel-release -y -sudo yum install git zip unzip -y -sudo yum install mariadb-server -y - - -# starting & enabling mariadb-server -sudo systemctl start mariadb -sudo systemctl enable mariadb -cd /tmp/ -git clone -b main https://github.com/hkhcoder/vprofile-project.git -#restore the dump file for the application -sudo mysqladmin -u root password "$DATABASE_PASS" -sudo mysql -u root -p"$DATABASE_PASS" -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')" -sudo mysql -u root -p"$DATABASE_PASS" -e "DELETE FROM mysql.user WHERE User=''" -sudo mysql -u root -p"$DATABASE_PASS" -e "DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%'" -sudo mysql -u root -p"$DATABASE_PASS" -e "FLUSH PRIVILEGES" -sudo mysql -u root -p"$DATABASE_PASS" -e "create database accounts" -sudo mysql -u root -p"$DATABASE_PASS" -e "grant all privileges on accounts.* TO 'admin'@'localhost' identified by 'admin123'" -sudo mysql -u root -p"$DATABASE_PASS" -e "grant all privileges on accounts.* TO 'admin'@'%' identified by 'admin123'" -sudo mysql -u root -p"$DATABASE_PASS" accounts < /tmp/vprofile-project/src/main/resources/db_backup.sql -sudo mysql -u root -p"$DATABASE_PASS" -e "FLUSH PRIVILEGES" - -# Restart mariadb-server -sudo systemctl restart mariadb - - -#starting the firewall and allowing the mariadb to access from port no. 3306 -sudo systemctl start firewalld -sudo systemctl enable firewalld -sudo firewall-cmd --get-active-zones -sudo firewall-cmd --zone=public --add-port=3306/tcp --permanent -sudo firewall-cmd --reload -sudo systemctl restart mariadb diff --git a/vagrant/Automated_provisioning_WinMacIntel/nginx.sh b/vagrant/Automated_provisioning_WinMacIntel/nginx.sh deleted file mode 100644 index c5116f540..000000000 --- a/vagrant/Automated_provisioning_WinMacIntel/nginx.sh +++ /dev/null @@ -1,32 +0,0 @@ -# adding repository and installing nginx -apt update -apt install nginx -y -cat < vproapp -upstream vproapp { - - server app01:8080; - -} - -server { - - listen 80; - -location / { - - proxy_pass http://vproapp; - -} - -} - -EOT - -mv vproapp /etc/nginx/sites-available/vproapp -rm -rf /etc/nginx/sites-enabled/default -ln -s /etc/nginx/sites-available/vproapp /etc/nginx/sites-enabled/vproapp - -#starting nginx service and firewall -systemctl start nginx -systemctl enable nginx -systemctl restart nginx diff --git a/vagrant/Automated_provisioning_WinMacIntel/rabbitmq.sh b/vagrant/Automated_provisioning_WinMacIntel/rabbitmq.sh deleted file mode 100644 index 2dc18b759..000000000 --- a/vagrant/Automated_provisioning_WinMacIntel/rabbitmq.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -sudo yum install epel-release -y -sudo yum update -y -sudo yum install wget -y -cd /tmp/ -dnf -y install centos-release-rabbitmq-38 - dnf --enablerepo=centos-rabbitmq-38 -y install rabbitmq-server - systemctl enable --now rabbitmq-server - firewall-cmd --add-port=5672/tcp - firewall-cmd --runtime-to-permanent -sudo systemctl start rabbitmq-server -sudo systemctl enable rabbitmq-server -sudo systemctl status rabbitmq-server -sudo sh -c 'echo "[{rabbit, [{loopback_users, []}]}]." > /etc/rabbitmq/rabbitmq.config' -sudo rabbitmqctl add_user test test -sudo rabbitmqctl set_user_tags test administrator -sudo systemctl restart rabbitmq-server diff --git a/vagrant/Automated_provisioning_WinMacIntel/tomcat.sh b/vagrant/Automated_provisioning_WinMacIntel/tomcat.sh deleted file mode 100644 index d850df030..000000000 --- a/vagrant/Automated_provisioning_WinMacIntel/tomcat.sh +++ /dev/null @@ -1,61 +0,0 @@ -TOMURL="https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.75/bin/apache-tomcat-9.0.75.tar.gz" -dnf -y install java-11-openjdk java-11-openjdk-devel -dnf install git maven wget -y -cd /tmp/ -wget $TOMURL -O tomcatbin.tar.gz -EXTOUT=`tar xzvf tomcatbin.tar.gz` -TOMDIR=`echo $EXTOUT | cut -d '/' -f1` -useradd --shell /sbin/nologin tomcat -rsync -avzh /tmp/$TOMDIR/ /usr/local/tomcat/ -chown -R tomcat.tomcat /usr/local/tomcat - -rm -rf /etc/systemd/system/tomcat.service - -cat <> /etc/systemd/system/tomcat.service -[Unit] -Description=Tomcat -After=network.target - -[Service] - -User=tomcat -Group=tomcat - -WorkingDirectory=/usr/local/tomcat - -#Environment=JRE_HOME=/usr/lib/jvm/jre -Environment=JAVA_HOME=/usr/lib/jvm/jre - -Environment=CATALINA_PID=/var/tomcat/%i/run/tomcat.pid -Environment=CATALINA_HOME=/usr/local/tomcat -Environment=CATALINE_BASE=/usr/local/tomcat - -ExecStart=/usr/local/tomcat/bin/catalina.sh run -ExecStop=/usr/local/tomcat/bin/shutdown.sh - - -RestartSec=10 -Restart=always - -[Install] -WantedBy=multi-user.target - -EOT - -systemctl daemon-reload -systemctl start tomcat -systemctl enable tomcat - -git clone -b main https://github.com/hkhcoder/vprofile-project.git -cd vprofile-project -mvn install -systemctl stop tomcat -sleep 20 -rm -rf /usr/local/tomcat/webapps/ROOT* -cp target/vprofile-v2.war /usr/local/tomcat/webapps/ROOT.war -systemctl start tomcat -sleep 20 -systemctl stop firewalld -systemctl disable firewalld -#cp /vagrant/application.properties /usr/local/tomcat/webapps/ROOT/WEB-INF/classes/application.properties -systemctl restart tomcat diff --git a/vagrant/Automated_provisioning_WinMacIntel/tomcat_ubuntu.sh b/vagrant/Automated_provisioning_WinMacIntel/tomcat_ubuntu.sh deleted file mode 100644 index 762c127d3..000000000 --- a/vagrant/Automated_provisioning_WinMacIntel/tomcat_ubuntu.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -sudo apt update -sudo apt upgrade -y -sudo apt install openjdk-8-jdk -y -sudo apt install tomcat8 tomcat8-admin tomcat8-docs tomcat8-common git -y diff --git a/vagrant/Manual_provisioning_MacOSM1/Vagrantfile b/vagrant/Manual_provisioning_MacOSM1/Vagrantfile deleted file mode 100644 index 8994972b6..000000000 --- a/vagrant/Manual_provisioning_MacOSM1/Vagrantfile +++ /dev/null @@ -1,63 +0,0 @@ -Vagrant.configure("2") do |config| - config.hostmanager.enabled = true - config.hostmanager.manage_host = true - -### DB vm #### - config.vm.define "db01" do |db01| - db01.vm.box = "jacobw/fedora35-arm64" - db01.vm.hostname = "db01" - db01.vm.network "private_network", ip: "192.168.56.25" - db01.vm.provider "vmware_desktop" do |vmware| - vmware.gui = true - vmware.allowlist_verified = true - end - end - -### Memcache vm #### - config.vm.define "mc01" do |mc01| - mc01.vm.box = "jacobw/fedora35-arm64" - mc01.vm.hostname = "mc01" - mc01.vm.network "private_network", ip: "192.168.56.24" - mc01.vm.provider "vmware_desktop" do |vmware| - vmware.gui = true - vmware.allowlist_verified = true - end - end - -### RabbitMQ vm #### - config.vm.define "rmq01" do |rmq01| - rmq01.vm.box = "jacobw/fedora35-arm64" - rmq01.vm.hostname = "rmq01" - rmq01.vm.network "private_network", ip: "192.168.56.23" - rmq01.vm.provider "vmware_desktop" do |vmware| - vmware.gui = true - vmware.allowlist_verified = true - end - end - -### tomcat vm ### - config.vm.define "app01" do |app01| - app01.vm.box = "jacobw/fedora35-arm64" - app01.vm.hostname = "app01" - app01.vm.network "private_network", ip: "192.168.56.22" - app01.vm.provider "vmware_desktop" do |vb| - vb.memory = "1024" - vb.gui = true - vb.allowlist_verified = true - end - end - - -### Nginx VM ### - config.vm.define "web01" do |web01| - web01.vm.box = "spox/ubuntu-arm" - web01.vm.hostname = "web01" - web01.vm.network "private_network", ip: "192.168.56.21" - web01.vm.provider "vmware_desktop" do |vmware| - vmware.gui = true - vmware.allowlist_verified = true - end - - end - -end diff --git a/vagrant/Manual_provisioning_MacOSM1/VprofileProjectSetupMacM1M2.pdf b/vagrant/Manual_provisioning_MacOSM1/VprofileProjectSetupMacM1M2.pdf deleted file mode 100644 index 25ac98149..000000000 Binary files a/vagrant/Manual_provisioning_MacOSM1/VprofileProjectSetupMacM1M2.pdf and /dev/null differ diff --git a/vagrant/Manual_provisioning_WinMacIntel/Vagrantfile b/vagrant/Manual_provisioning_WinMacIntel/Vagrantfile deleted file mode 100644 index 0be952002..000000000 --- a/vagrant/Manual_provisioning_WinMacIntel/Vagrantfile +++ /dev/null @@ -1,62 +0,0 @@ -Vagrant.configure("2") do |config| - config.hostmanager.enabled = true - config.hostmanager.manage_host = true - -### DB vm #### - config.vm.define "db01" do |db01| - db01.vm.box = "eurolinux-vagrant/centos-stream-9" - db01.vm.box_version = "9.0.43" - db01.vm.hostname = "db01" - db01.vm.network "private_network", ip: "192.168.56.15" - db01.vm.provider "virtualbox" do |vb| - vb.memory = "600" - end - - end - -### Memcache vm #### - config.vm.define "mc01" do |mc01| - mc01.vm.box = "eurolinux-vagrant/centos-stream-9" - mc01.vm.box_version = "9.0.43" - mc01.vm.hostname = "mc01" - mc01.vm.network "private_network", ip: "192.168.56.14" - mc01.vm.provider "virtualbox" do |vb| - vb.memory = "600" - end - end - -### RabbitMQ vm #### - config.vm.define "rmq01" do |rmq01| - rmq01.vm.box = "eurolinux-vagrant/centos-stream-9" - rmq01.vm.box_version = "9.0.43" - rmq01.vm.hostname = "rmq01" - rmq01.vm.network "private_network", ip: "192.168.56.13" - rmq01.vm.provider "virtualbox" do |vb| - vb.memory = "600" - end - end - -### tomcat vm ### - config.vm.define "app01" do |app01| - app01.vm.box = "eurolinux-vagrant/centos-stream-9" - app01.vm.box_version = "9.0.43" - app01.vm.hostname = "app01" - app01.vm.network "private_network", ip: "192.168.56.12" - app01.vm.provider "virtualbox" do |vb| - vb.memory = "800" - end - end - - -### Nginx VM ### - config.vm.define "web01" do |web01| - web01.vm.box = "ubuntu/jammy64" - web01.vm.hostname = "web01" - web01.vm.network "private_network", ip: "192.168.56.11" - web01.vm.provider "virtualbox" do |vb| - vb.gui = true - vb.memory = "800" - end -end - -end diff --git a/vagrant/Manual_provisioning_WinMacIntel/VprofileProjectSetupWindowsAndMacIntel.pdf b/vagrant/Manual_provisioning_WinMacIntel/VprofileProjectSetupWindowsAndMacIntel.pdf deleted file mode 100644 index 52f5e10a8..000000000 Binary files a/vagrant/Manual_provisioning_WinMacIntel/VprofileProjectSetupWindowsAndMacIntel.pdf and /dev/null differ