audit-labs/audit-tools

A collection of scripts, queries, and other goodies you can use in an audit. audit automation compliance evidence scripts

Commit 6817ff6335

6817ff633541af6b049946ddaa13bad10d732376

parent: cb6fe10542

Unsigned

cmc <hello@cleberg.net> · 2025-04-05 18:43 UTC
committer: <noreply@github.com>

migrate from pylint to ruff (#1)

* migrate from pylint to ruff

* Commit from GitHub Actions (Pylint)

* rename pylint.yml to ruff.yml

* only run ruff-action when python files change

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>

Layout: unified · split

.github/workflows/pylint.yml deleted −23
@@ -1,23 +0,0 @@
1name: Pylint
2
3on: [push]
4
5jobs:
6 build:
7 runs-on: ubuntu-latest
8 strategy:
9 matrix:
10 python-version: ["3.8", "3.9", "3.10"]
11 steps:
12 - uses: actions/checkout@v4
13 - name: Set up Python ${{ matrix.python-version }}
14 uses: actions/setup-python@v5
15 with:
16 python-version: ${{ matrix.python-version }}
17 - name: Install dependencies
18 run: |
19 python -m pip install --upgrade pip
20 pip install pylint pandas dash plotly.express
21 - name: Analysing the code with pylint
22 run: |
23 pylint -d R0801 $(git ls-files '*.py')
.github/workflows/ruff.yml added +37
@@ -0,0 +1,37 @@
1name: Ruff
2
3on:
4 push:
5 paths:
6 - '**.py'
7
8jobs:
9 build:
10 runs-on: ubuntu-latest
11 strategy:
12 matrix:
13 python-version: ["3.x"]
14 steps:
15 - uses: actions/checkout@v4
16 - name: Set up Python ${{ matrix.python-version }}
17 uses: actions/setup-python@v5
18 with:
19 python-version: ${{ matrix.python-version }}
20 ref: ${{ github.event.pull_request.head.ref }}
21 - name: Install dependencies
22 run: |
23 python -m pip install --upgrade pip
24 pip install pandas dash plotly.express
25 - name: Install Ruff
26 uses: astral-sh/ruff-action@v3.2.2
27 - name: Ruff Actions
28 run: |
29 ruff check --fix
30 ruff format
31 - name: Add and Commit
32 uses: EndBug/add-and-commit@v9
33 env:
34 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35 with:
36 default_author: github_actions
37 pathspec_error_handling: ignore\ No newline at end of file
applications/github/github_admins.py +19 −12
@@ -5,54 +5,59 @@ and list each user's permission per repo.
5 5
6import requests 6import requests
7 7
8GITHUB_TOKEN = 'your_personal_access_token' 8GITHUB_TOKEN = "your_personal_access_token"
9ORGANIZATION = 'your_organization' 9ORGANIZATION = "your_organization"
10TIMEOUT = 30 10TIMEOUT = 30
11 11
12# Headers for authentication 12# Headers for authentication
13headers = { 13headers = {
14 'Authorization': f'token {GITHUB_TOKEN}', 14 "Authorization": f"token {GITHUB_TOKEN}",
15 'Accept': 'application/vnd.github.v3+json' 15 "Accept": "application/vnd.github.v3+json",
16} 16}
17 17
18
18def get_org_members(org): 19def get_org_members(org):
19 """ 20 """
20 Get members of an organization 21 Get members of an organization
21 """ 22 """
22 url = f'https://api.github.com/orgs/{org}/members' 23 url = f"https://api.github.com/orgs/{org}/members"
23 response = requests.get(url, headers=headers, timeout=TIMEOUT) 24 response = requests.get(url, headers=headers, timeout=TIMEOUT)
24 response.raise_for_status() 25 response.raise_for_status()
25 return response.json() 26 return response.json()
26 27
28
27def get_org_repos(org): 29def get_org_repos(org):
28 """ 30 """
29 Get repositories of an organization 31 Get repositories of an organization
30 """ 32 """
31 url = f'https://api.github.com/orgs/{org}/repos' 33 url = f"https://api.github.com/orgs/{org}/repos"
32 response = requests.get(url, headers=headers, timeout=TIMEOUT) 34 response = requests.get(url, headers=headers, timeout=TIMEOUT)
33 response.raise_for_status() 35 response.raise_for_status()
34 return response.json() 36 return response.json()
35 37
38
36def get_repo_collaborators(org, repo): 39def get_repo_collaborators(org, repo):
37 """ 40 """
38 Get collaborators of a repository with their permissions 41 Get collaborators of a repository with their permissions
39 """ 42 """
40 url = f'https://api.github.com/repos/{org}/{repo}/collaborators' 43 url = f"https://api.github.com/repos/{org}/{repo}/collaborators"
41 response = requests.get(url, headers=headers, timeout=TIMEOUT) 44 response = requests.get(url, headers=headers, timeout=TIMEOUT)
42 response.raise_for_status() 45 response.raise_for_status()
43 return response.json() 46 return response.json()
44 47
48
45def get_user_permissions(org, repo, user): 49def get_user_permissions(org, repo, user):
46 """ 50 """
47 Get a user's permissions for a repository 51 Get a user's permissions for a repository
48 """ 52 """
49 url = f'https://api.github.com/repos/{org}/{repo}/collaborators/{user}/permission' 53 url = f"https://api.github.com/repos/{org}/{repo}/collaborators/{user}/permission"
50 response = requests.get(url, headers=headers, timeout=TIMEOUT) 54 response = requests.get(url, headers=headers, timeout=TIMEOUT)
51 response.raise_for_status() 55 response.raise_for_status()
52 return response.json() 56 return response.json()
53 57
58
54# Main script 59# Main script
55if __name__ == '__main__': 60if __name__ == "__main__":
56 # Get organization members 61 # Get organization members
57 members = get_org_members(ORGANIZATION) 62 members = get_org_members(ORGANIZATION)
58 print(f"Members of the organization '{ORGANIZATION}':") 63 print(f"Members of the organization '{ORGANIZATION}':")
@@ -67,10 +72,12 @@ if __name__ == '__main__':
67 72
68 # Get collaborators for each repository and their permissions 73 # Get collaborators for each repository and their permissions
69 for repository in repositories: 74 for repository in repositories:
70 repository_name = repository['name'] 75 repository_name = repository["name"]
71 collaborators = get_repo_collaborators(ORGANIZATION, repository_name) 76 collaborators = get_repo_collaborators(ORGANIZATION, repository_name)
72 print(f"\nCollaborators for the repository '{repository_name}':") 77 print(f"\nCollaborators for the repository '{repository_name}':")
73 for collaborator in collaborators: 78 for collaborator in collaborators:
74 user_login = collaborator['login'] 79 user_login = collaborator["login"]
75 permissions = get_user_permissions(ORGANIZATION, repository_name, user_login) 80 permissions = get_user_permissions(
81 ORGANIZATION, repository_name, user_login
82 )
76 print(f"- {user_login}: {permissions['permission']}") 83 print(f"- {user_login}: {permissions['permission']}")
applications/github/github_audit_log.py +16 −10
@@ -6,16 +6,17 @@ NOTE: REQUIRES A GITHUB ENTERPRISE SUBSCRIPTION TO ACCESS THE API.
6 6
7import requests 7import requests
8 8
9GITHUB_TOKEN = 'your_personal_access_token' 9GITHUB_TOKEN = "your_personal_access_token"
10ORGANIZATION = 'your_organization' 10ORGANIZATION = "your_organization"
11TIMEOUT = 30 11TIMEOUT = 30
12 12
13# Headers for authentication 13# Headers for authentication
14headers = { 14headers = {
15 'Authorization': f'token {GITHUB_TOKEN}', 15 "Authorization": f"token {GITHUB_TOKEN}",
16 'Accept': 'application/vnd.github.v3+json' 16 "Accept": "application/vnd.github.v3+json",
17} 17}
18 18
19
19def get_audit_log_events(org, actions): 20def get_audit_log_events(org, actions):
20 """ 21 """
21 Get audit log events for specific actions 22 Get audit log events for specific actions
@@ -23,8 +24,10 @@ def get_audit_log_events(org, actions):
23 events = [] 24 events = []
24 page = 1 25 page = 1
25 while True: 26 while True:
26 url = (f'https://api.github.com/orgs/{org}/audit-log?page={page}&per_page=100' 27 url = (
27 f'&action={",".join(actions)}') 28 f"https://api.github.com/orgs/{org}/audit-log?page={page}&per_page=100"
29 f"&action={','.join(actions)}"
30 )
28 response = requests.get(url, headers=headers, timeout=TIMEOUT) 31 response = requests.get(url, headers=headers, timeout=TIMEOUT)
29 response.raise_for_status() 32 response.raise_for_status()
30 page_events = response.json() 33 page_events = response.json()
@@ -34,12 +37,15 @@ def get_audit_log_events(org, actions):
34 page += 1 37 page += 1
35 return events 38 return events
36 39
37if __name__ == '__main__': 40
41if __name__ == "__main__":
38 try: 42 try:
39 # Define the actions to filter 43 # Define the actions to filter
40 action_filters = ['protected_branch', 44 action_filters = [
41 'repository_branch_protection_evaluation', 45 "protected_branch",
42 'repository_ruleset'] 46 "repository_branch_protection_evaluation",
47 "repository_ruleset",
48 ]
43 49
44 # Get audit log events for the specified actions 50 # Get audit log events for the specified actions
45 audit_log_events = get_audit_log_events(ORGANIZATION, action_filters) 51 audit_log_events = get_audit_log_events(ORGANIZATION, action_filters)
applications/github/github_branch_protections.py +17 −11
@@ -4,16 +4,17 @@ Gathers branch protection rules for a repository.
4 4
5import requests 5import requests
6 6
7GITHUB_TOKEN = 'your_personal_access_token' 7GITHUB_TOKEN = "your_personal_access_token"
8ORGANIZATION = 'your_organization' 8ORGANIZATION = "your_organization"
9REPOSITORY = 'your_repository' 9REPOSITORY = "your_repository"
10TIMEOUT = 30 10TIMEOUT = 30
11 11
12headers = { 12headers = {
13 'Authorization': f'token {GITHUB_TOKEN}', 13 "Authorization": f"token {GITHUB_TOKEN}",
14 'Accept': 'application/vnd.github.v3+json' 14 "Accept": "application/vnd.github.v3+json",
15} 15}
16 16
17
17def get_all_branches(org, repo): 18def get_all_branches(org, repo):
18 """ 19 """
19 Get all branches in a repository 20 Get all branches in a repository
@@ -21,7 +22,7 @@ def get_all_branches(org, repo):
21 all_branches = [] 22 all_branches = []
22 page = 1 23 page = 1
23 while True: 24 while True:
24 url = f'https://api.github.com/repos/{org}/{repo}/branches?page={page}&per_page=100' 25 url = f"https://api.github.com/repos/{org}/{repo}/branches?page={page}&per_page=100"
25 response = requests.get(url, headers=headers, timeout=TIMEOUT) 26 response = requests.get(url, headers=headers, timeout=TIMEOUT)
26 response.raise_for_status() 27 response.raise_for_status()
27 page_branches = response.json() 28 page_branches = response.json()
@@ -31,27 +32,30 @@ def get_all_branches(org, repo):
31 page += 1 32 page += 1
32 return all_branches 33 return all_branches
33 34
35
34def get_branch_protection(org, repo, repo_branch): 36def get_branch_protection(org, repo, repo_branch):
35 """ 37 """
36 Get branch protection settings 38 Get branch protection settings
37 """ 39 """
38 url = f'https://api.github.com/repos/{org}/{repo}/branches/{repo_branch}/protection' 40 url = f"https://api.github.com/repos/{org}/{repo}/branches/{repo_branch}/protection"
39 response = requests.get(url, headers=headers, timeout=TIMEOUT) 41 response = requests.get(url, headers=headers, timeout=TIMEOUT)
40 if response.status_code == 404: 42 if response.status_code == 404:
41 return None # No protection settings for this branch 43 return None # No protection settings for this branch
42 response.raise_for_status() 44 response.raise_for_status()
43 return response.json() 45 return response.json()
44 46
47
45def get_repository_rulesets(org, repo): 48def get_repository_rulesets(org, repo):
46 """ 49 """
47 Get repository rulesets 50 Get repository rulesets
48 """ 51 """
49 url = f'https://api.github.com/repos/{org}/{repo}/rulesets' 52 url = f"https://api.github.com/repos/{org}/{repo}/rulesets"
50 response = requests.get(url, headers=headers, timeout=TIMEOUT) 53 response = requests.get(url, headers=headers, timeout=TIMEOUT)
51 response.raise_for_status() 54 response.raise_for_status()
52 return response.json() 55 return response.json()
53 56
54if __name__ == '__main__': 57
58if __name__ == "__main__":
55 try: 59 try:
56 # Get all branches in the repository 60 # Get all branches in the repository
57 branches = get_all_branches(ORGANIZATION, REPOSITORY) 61 branches = get_all_branches(ORGANIZATION, REPOSITORY)
@@ -59,8 +63,10 @@ if __name__ == '__main__':
59 63
60 # Get protection settings for each branch 64 # Get protection settings for each branch
61 for branch in branches: 65 for branch in branches:
62 branch_name = branch['name'] 66 branch_name = branch["name"]
63 protection_settings = get_branch_protection(ORGANIZATION, REPOSITORY, branch_name) 67 protection_settings = get_branch_protection(
68 ORGANIZATION, REPOSITORY, branch_name
69 )
64 print(f"\nBranch: {branch_name}") 70 print(f"\nBranch: {branch_name}")
65 if protection_settings: 71 if protection_settings:
66 print(f"Protection settings: {protection_settings}") 72 print(f"Protection settings: {protection_settings}")
applications/github/github_commits.py +31 −21
@@ -4,20 +4,21 @@ Gather all commits from a specific branch of a repository in a GitHub organizati
4 4
5import requests 5import requests
6 6
7GITHUB_TOKEN = 'your_personal_access_token' 7GITHUB_TOKEN = "your_personal_access_token"
8ORGANIZATION = 'your_organization' 8ORGANIZATION = "your_organization"
9REPOSITORY = 'your_repository' 9REPOSITORY = "your_repository"
10BRANCH = 'your_branch' 10BRANCH = "your_branch"
11 11
12# Headers for authentication 12# Headers for authentication
13headers = { 13headers = {
14 'Authorization': f'token {GITHUB_TOKEN}', 14 "Authorization": f"token {GITHUB_TOKEN}",
15 'Accept': 'application/vnd.github.v3+json' 15 "Accept": "application/vnd.github.v3+json",
16} 16}
17 17
18# Define a timeout value (in seconds) 18# Define a timeout value (in seconds)
19TIMEOUT = 10 19TIMEOUT = 10
20 20
21
21def get_commit_log(org, repo, branch): 22def get_commit_log(org, repo, branch):
22 """ 23 """
23 Get the full commit log for a repository branch 24 Get the full commit log for a repository branch
@@ -25,8 +26,10 @@ def get_commit_log(org, repo, branch):
25 commits = [] 26 commits = []
26 page = 1 27 page = 1
27 while True: 28 while True:
28 url = (f'https://api.github.com/repos/{org}/{repo}/commits?sha={branch}' 29 url = (
29 f'&page={page}&per_page=100') 30 f"https://api.github.com/repos/{org}/{repo}/commits?sha={branch}"
31 f"&page={page}&per_page=100"
32 )
30 response = requests.get(url, headers=headers, timeout=TIMEOUT) 33 response = requests.get(url, headers=headers, timeout=TIMEOUT)
31 response.raise_for_status() 34 response.raise_for_status()
32 page_commits = response.json() 35 page_commits = response.json()
@@ -36,39 +39,46 @@ def get_commit_log(org, repo, branch):
36 page += 1 39 page += 1
37 return commits 40 return commits
38 41
42
39def get_commit_details(org, repo, sha): 43def get_commit_details(org, repo, sha):
40 """ 44 """
41 Get detailed information for a specific commit 45 Get detailed information for a specific commit
42 """ 46 """
43 url = f'https://api.github.com/repos/{org}/{repo}/commits/{sha}' 47 url = f"https://api.github.com/repos/{org}/{repo}/commits/{sha}"
44 response = requests.get(url, headers=headers, timeout=TIMEOUT) 48 response = requests.get(url, headers=headers, timeout=TIMEOUT)
45 response.raise_for_status() 49 response.raise_for_status()
46 return response.json() 50 return response.json()
47 51
48if __name__ == '__main__': 52
53if __name__ == "__main__":
49 try: 54 try:
50 # Get the full commit log for the specified branch 55 # Get the full commit log for the specified branch
51 commit_log = get_commit_log(ORGANIZATION, REPOSITORY, BRANCH) 56 commit_log = get_commit_log(ORGANIZATION, REPOSITORY, BRANCH)
52 print(f"Total commits in the repository '{REPOSITORY}' on branch " 57 print(
53 f"'{BRANCH}': {len(commit_log)}") 58 f"Total commits in the repository '{REPOSITORY}' on branch "
59 f"'{BRANCH}': {len(commit_log)}"
60 )
54 61
55 # Get detailed information for each commit 62 # Get detailed information for each commit
56 for commit in commit_log: 63 for commit in commit_log:
57 sha_hash = commit['sha'] 64 sha_hash = commit["sha"]
58 commit_details = get_commit_details(ORGANIZATION, REPOSITORY, 65 commit_details = get_commit_details(ORGANIZATION, REPOSITORY, sha_hash)
59 sha_hash)
60 print(f"\nCommit SHA: {commit_details['sha']}") 66 print(f"\nCommit SHA: {commit_details['sha']}")
61 print(f"Author: {commit_details['commit']['author']['name']} " 67 print(
62 f"<{commit_details['commit']['author']['email']}>") 68 f"Author: {commit_details['commit']['author']['name']} "
69 f"<{commit_details['commit']['author']['email']}>"
70 )
63 print(f"Date: {commit_details['commit']['author']['date']}") 71 print(f"Date: {commit_details['commit']['author']['date']}")
64 print(f"Message: {commit_details['commit']['message']}") 72 print(f"Message: {commit_details['commit']['message']}")
65 print(f"URL: {commit_details['html_url']}") 73 print(f"URL: {commit_details['html_url']}")
66 print("Files changed:") 74 print("Files changed:")
67 for file in commit_details['files']: 75 for file in commit_details["files"]:
68 print(f" - {file['filename']} ({file['status']})") 76 print(f" - {file['filename']} ({file['status']})")
69 print(f" Additions: {file['additions']}, " 77 print(
70 f"Deletions: {file['deletions']}, " 78 f" Additions: {file['additions']}, "
71 f"Changes: {file['changes']}") 79 f"Deletions: {file['deletions']}, "
80 f"Changes: {file['changes']}"
81 )
72 except requests.exceptions.Timeout: 82 except requests.exceptions.Timeout:
73 print("The request timed out") 83 print("The request timed out")
74 except requests.exceptions.RequestException as e: 84 except requests.exceptions.RequestException as e:
applications/gitlab/gitlab_admins.py +4 −2
@@ -12,12 +12,14 @@ TIMEOUT = 30
12URL = f"{BASE_URL}/groups/{GROUP_ID}/members" 12URL = f"{BASE_URL}/groups/{GROUP_ID}/members"
13HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN} 13HEADERS = {"PRIVATE-TOKEN": PRIVATE_TOKEN}
14 14
15if __name__ == '__main__': 15if __name__ == "__main__":
16 # Get group members 16 # Get group members
17 response = requests.get(URL, headers=HEADERS, timeout=TIMEOUT) 17 response = requests.get(URL, headers=HEADERS, timeout=TIMEOUT)
18 if response.status_code == 200: 18 if response.status_code == 200:
19 members = response.json() 19 members = response.json()
20 for member in members: 20 for member in members:
21 print(f"Username: {member['username']}, Access Level: {member['access_level']}") 21 print(
22 f"Username: {member['username']}, Access Level: {member['access_level']}"
23 )
22 else: 24 else:
23 print(f"Failed to fetch group members: {response.status_code}, {response.text}") 25 print(f"Failed to fetch group members: {response.status_code}, {response.text}")
databases/passwords/sql/test.py +28 −25
@@ -6,7 +6,8 @@ Checks SQL Server user data for compliance with Windows policies.
6import pandas as pd 6import pandas as pd
7 7
8# Load the data into a pandas DataFrame 8# Load the data into a pandas DataFrame
9df_input = pd.read_csv('./data.csv') 9df_input = pd.read_csv("./data.csv")
10
10 11
11# Function to apply rules and generate report 12# Function to apply rules and generate report
12def apply_rules_and_report(df): 13def apply_rules_and_report(df):
@@ -22,45 +23,46 @@ def apply_rules_and_report(df):
22 report = [] 23 report = []
23 for _, row in df.iterrows(): 24 for _, row in df.iterrows():
24 result = { 25 result = {
25 'Name': row['name'], 26 "Name": row["name"],
26 'Type Check': '', 27 "Type Check": "",
27 'Policy Check': '', 28 "Policy Check": "",
28 'Expiration Check': '', 29 "Expiration Check": "",
29 'Reason': '' 30 "Reason": "",
30 } 31 }
31 32
32 # Check the type_desc 33 # Check the type_desc
33 if row['type_desc'] == 'SQL_LOGIN': 34 if row["type_desc"] == "SQL_LOGIN":
34 result['Type Check'] = 'SQL_LOGIN' 35 result["Type Check"] = "SQL_LOGIN"
35 elif row['type_desc'] == 'WINDOWS_LOGIN': 36 elif row["type_desc"] == "WINDOWS_LOGIN":
36 result['Type Check'] = 'N/A' 37 result["Type Check"] = "N/A"
37 result['Reason'] = 'Refer to Windows password policy.' 38 result["Reason"] = "Refer to Windows password policy."
38 else: 39 else:
39 result['Type Check'] = 'Manual Review' 40 result["Type Check"] = "Manual Review"
40 result['Reason'] = 'Reviewer to manually review.' 41 result["Reason"] = "Reviewer to manually review."
41 42
42 # Check if password policy is enforced 43 # Check if password policy is enforced
43 if row['is_policy_checked'] == 1: 44 if row["is_policy_checked"] == 1:
44 result['Policy Check'] = 'PASS' 45 result["Policy Check"] = "PASS"
45 result['Reason'] += '''Password policy is enforced. Reviewer to 46 result["Reason"] += """Password policy is enforced. Reviewer to
46 check the assigned policy.''' 47 check the assigned policy."""
47 else: 48 else:
48 result['Policy Check'] = 'FAIL' 49 result["Policy Check"] = "FAIL"
49 result['Reason'] += 'Password policy is not enforced.' 50 result["Reason"] += "Password policy is not enforced."
50 51
51 # Check if password expiration is enforced 52 # Check if password expiration is enforced
52 if row['is_expiration_checked'] == 1: 53 if row["is_expiration_checked"] == 1:
53 result['Expiration Check'] = 'PASS' 54 result["Expiration Check"] = "PASS"
54 result['Reason'] += '''Password expiration is enforced. Reviewer to 55 result["Reason"] += """Password expiration is enforced. Reviewer to
55 check the expiration policy.''' 56 check the expiration policy."""
56 else: 57 else:
57 result['Expiration Check'] = 'FAIL' 58 result["Expiration Check"] = "FAIL"
58 result['Reason'] += 'Password expiration is not enforced.' 59 result["Reason"] += "Password expiration is not enforced."
59 60
60 report.append(result) 61 report.append(result)
61 62
62 return report 63 return report
63 64
65
64# Main function to run the script 66# Main function to run the script
65def main(): 67def main():
66 """ 68 """
@@ -73,5 +75,6 @@ def main():
73 # Print the report 75 # Print the report
74 print(report_df) 76 print(report_df)
75 77
78
76if __name__ == "__main__": 79if __name__ == "__main__":
77 main() 80 main()
project_management/dash/app.py +41 −38
@@ -8,51 +8,54 @@ import pandas as pd
8import plotly.express as px 8import plotly.express as px
9 9
10# Incorporate data 10# Incorporate data
11df = pd.read_excel('project_data.xlsx') 11df = pd.read_excel("project_data.xlsx")
12 12
13# Initialize the app 13# Initialize the app
14app = Dash() 14app = Dash()
15 15
16# App layout 16# App layout
17app.layout = [ 17app.layout = [
18 html.H1(children='Project Dashboard', style={'textAlign':'center'}), 18 html.H1(children="Project Dashboard", style={"textAlign": "center"}),
19 html.Div(children = [ 19 html.Div(
20 dcc.Graph( 20 children=[
21 figure=px.histogram( 21 dcc.Graph(
22 df, 22 figure=px.histogram(
23 x='Preparer', 23 df,
24 color='High Priority?', 24 x="Preparer",
25 title='Control Count by Preparer' 25 color="High Priority?",
26 ), 26 title="Control Count by Preparer",
27 style = {'flex-grow':'1'} 27 ),
28 ), 28 style={"flex-grow": "1"},
29 dcc.Graph( 29 ),
30 figure=px.histogram( 30 dcc.Graph(
31 df, 31 figure=px.histogram(
32 x='Preparer', 32 df,
33 y='Projected Hours ', 33 x="Preparer",
34 color='Status ', 34 y="Projected Hours ",
35 title='Project Hours by Preparer' 35 color="Status ",
36 ), 36 title="Project Hours by Preparer",
37 style = {'flex-grow':'1'} 37 ),
38 ) 38 style={"flex-grow": "1"},
39 ], 39 ),
40 style = { 40 ],
41 'display':'flex', 41 style={
42 'flex-wrap':'wrap', 42 "display": "flex",
43 'justify-content':'space-between', 43 "flex-wrap": "wrap",
44'align-items':'center'}), 44 "justify-content": "space-between",
45 dcc.Graph( 45 "align-items": "center",
46 figure=px.pie( 46 },
47 df, 47 ),
48 values = df['Preparer'].value_counts().values, 48 dcc.Graph(
49 names=df['Reviewer'].value_counts().index, 49 figure=px.pie(
50 title='Reviewer Breakdown', 50 df,
51 hole=0.5 51 values=df["Preparer"].value_counts().values,
52 ) 52 names=df["Reviewer"].value_counts().index,
53 ) 53 title="Reviewer Breakdown",
54 hole=0.5,
55 )
56 ),
54] 57]
55 58
56# Run the app 59# Run the app
57if __name__ == '__main__': 60if __name__ == "__main__":
58 app.run(debug=True) 61 app.run(debug=True)