Initial commit
This commit is contained in:
commit
315e731234
27 changed files with 3403 additions and 0 deletions
148
templates/about.html
Normal file
148
templates/about.html
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}About - Blake Ridgway{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h1>About Me</h1>
|
||||
<p class="lead">Welcome! I'm Blake Ridgway, a Systems Administrator passionate about cybersecurity, technology, and ultra endurance cycling.</p>
|
||||
|
||||
<h3>Background</h3>
|
||||
<p>
|
||||
As a Systems Administrator, I focus on ensuring the stability, integrity, and performance of critical IT infrastructure.
|
||||
Through hands-on experience managing Linux, AWS, and Azure environments, I've learned that true system stability
|
||||
is impossible without robust security. This realization has driven me to actively develop my skills in both
|
||||
offensive and defensive security, with a particular focus on application security and secure infrastructure.
|
||||
</p>
|
||||
<p>
|
||||
I believe that understanding how to break systems is the best way to learn how to build unbreakable ones.
|
||||
I'm currently documenting my entire cybersecurity learning journey, including all notes and projects, in my
|
||||
<a href="https://gitlab.com/blakeridgway/cybersec-odyssey" target="_blank" rel="noopener noreferrer">
|
||||
CyberSec Odyssey Repository <i class="fas fa-external-link-alt ms-1"></i>
|
||||
</a>.
|
||||
</p>
|
||||
|
||||
<h3>Core Competencies</h3>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="skill-card p-3 bg-light rounded-3 shadow-sm h-100">
|
||||
<h5><i class="fas fa-server text-primary me-2"></i>Systems Administration</h5>
|
||||
<p class="small mb-0">Expertise in deploying, managing, and troubleshooting Linux and Windows server environments.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="skill-card p-3 bg-light rounded-3 shadow-sm h-100">
|
||||
<h5><i class="fas fa-cloud text-success me-2"></i>Cloud Technologies</h5>
|
||||
<p class="small mb-0">Hands-on experience with AWS and Azure, including EC2, S3, Azure VMs, and virtual networking.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="skill-card p-3 bg-light rounded-3 shadow-sm h-100">
|
||||
<h5><i class="fas fa-shield-alt text-danger me-2"></i>Security & Monitoring</h5>
|
||||
<p class="small mb-0">Implementing monitoring solutions and security practices to proactively identify and resolve issues.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="skill-card p-3 bg-light rounded-3 shadow-sm h-100">
|
||||
<h5><i class="fas fa-network-wired text-info me-2"></i>Network Management</h5>
|
||||
<p class="small mb-0">Practical knowledge of Cisco, Fortigate, and Netgate solutions for network optimization.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="skill-card p-3 bg-light rounded-3 shadow-sm h-100">
|
||||
<h5><i class="fas fa-code text-warning me-2"></i>Automation & Scripting</h5>
|
||||
<p class="small mb-0">Using Bash, PowerShell, and Python to automate tasks and improve efficiency.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="skill-card p-3 bg-light rounded-3 shadow-sm h-100">
|
||||
<h5><i class="fas fa-bicycle text-success me-2"></i>Ultra Endurance Cycling</h5>
|
||||
<p class="small mb-0">Combining passion for cycling with data-driven insights and secure development practices.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Technologies & Tools</h3>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="tech-category p-3 bg-white rounded-3 shadow-sm h-100">
|
||||
<h6 class="text-center mb-3">OS & Cloud</h6>
|
||||
<div class="tech-icons d-flex justify-content-center flex-wrap gap-2">
|
||||
<img alt="Linux" width="30" src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/linux/linux-original.svg" title="Linux" />
|
||||
<img alt="Fedora" width="30" src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/fedora/fedora-plain.svg" title="Fedora" />
|
||||
<img alt="Debian" width="30" src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/debian/debian-plain.svg" title="Debian" />
|
||||
<img alt="AWS" width="30" src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/amazonwebservices/amazonwebservices-original-wordmark.svg" title="AWS" />
|
||||
<img alt="Azure" width="30" src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/azure/azure-original.svg" title="Azure" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="tech-category p-3 bg-white rounded-3 shadow-sm h-100">
|
||||
<h6 class="text-center mb-3">DevOps & Automation</h6>
|
||||
<div class="tech-icons d-flex justify-content-center flex-wrap gap-2">
|
||||
<img alt="Docker" width="30" src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/docker/docker-plain.svg" title="Docker" />
|
||||
<img alt="Kubernetes" width="30" src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/kubernetes/kubernetes-plain.svg" title="Kubernetes" />
|
||||
<img alt="Git" width="30" src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/git/git-plain.svg" title="Git" />
|
||||
<img alt="Bash" width="30" src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/bash/bash-original.svg" title="Bash" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="tech-category p-3 bg-white rounded-3 shadow-sm h-100">
|
||||
<h6 class="text-center mb-3">Languages & Frameworks</h6>
|
||||
<div class="tech-icons d-flex justify-content-center flex-wrap gap-2">
|
||||
<img alt="C#" width="30" src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/csharp/csharp-original.svg" title="C#" />
|
||||
<img alt=".NET" width="30" src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/dotnetcore/dotnetcore-original.svg" title=".NET" />
|
||||
<img alt="Python" width="30" src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/python/python-original.svg" title="Python" />
|
||||
<img alt="Ruby" width="30" src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/ruby/ruby-plain.svg" title="Ruby" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="tech-category p-3 bg-white rounded-3 shadow-sm h-100">
|
||||
<h6 class="text-center mb-3">Security & Monitoring</h6>
|
||||
<div class="tech-icons d-flex justify-content-center flex-wrap gap-2">
|
||||
<i class="fab fa-linux fa-2x text-dark" title="Linux Security"></i>
|
||||
<i class="fas fa-shield-alt fa-2x text-primary" title="Security"></i>
|
||||
<i class="fas fa-chart-line fa-2x text-success" title="Monitoring"></i>
|
||||
<i class="fas fa-lock fa-2x text-danger" title="Encryption"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body text-center">
|
||||
<img src="{{ url_for('static', filename='images/profile.jpg') }}"
|
||||
alt="Blake Ridgway Profile Picture" class="img-fluid rounded-circle mb-3"
|
||||
style="max-width: 200px;">
|
||||
<h5>Blake Ridgway</h5>
|
||||
<p class="text-muted">Systems Administrator & Cybersecurity Enthusiast</p>
|
||||
<div class="d-flex justify-content-center gap-2 flex-wrap">
|
||||
<span class="badge bg-primary">Systems Admin</span>
|
||||
<span class="badge bg-success">Cybersecurity Enthusiast</span>
|
||||
<span class="badge bg-info">Linux & Open Source Advocate</span>
|
||||
<span class="badge bg-warning">Ultra Endurance Cyclist</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Quick Links</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="{{ url_for('hardware') }}" class="text-decoration-none"><i class="fas fa-desktop me-2"></i>Hardware Setup</a></li>
|
||||
<li><a href="{{ url_for('biking') }}" class="text-decoration-none"><i class="fas fa-bicycle me-2"></i>Cycling Adventures</a></li>
|
||||
<li><a href="{{ url_for('blog') }}" class="text-decoration-none"><i class="fas fa-blog me-2"></i>Blog Posts</a></li>
|
||||
<li><a href="https://gitlab.com/blakeridgway/cybersec-odyssey" target="_blank" rel="noopener noreferrer" class="text-decoration-none"><i class="fab fa-gitlab me-2"></i>CyberSec Repository</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
76
templates/admin/dashboard.html
Normal file
76
templates/admin/dashboard.html
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Admin Dashboard - Your Name{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Admin Dashboard</h1>
|
||||
<div>
|
||||
<a href="{{ url_for('admin_new_post') }}" class="btn btn-primary">New Post</a>
|
||||
<a href="{{ url_for('admin_logout') }}" class="btn btn-outline-secondary">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Blog Posts ({{ posts|length }})</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if posts %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Category</th>
|
||||
<th>Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for post in posts %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ post.title }}</strong><br>
|
||||
<small class="text-muted">{{ post.excerpt[:100] }}...</small>
|
||||
</td>
|
||||
<td><span class="badge bg-secondary">{{ post.category }}</span></td>
|
||||
<td>{{ post.date }}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{{ url_for('blog_post', post_id=post.id) }}"
|
||||
class="btn btn-outline-primary" target="_blank">View</a>
|
||||
<a href="{{ url_for('admin_edit_post', post_id=post.id) }}"
|
||||
class="btn btn-outline-warning">Edit</a>
|
||||
<form method="POST" action="{{ url_for('admin_delete_post', post_id=post.id) }}"
|
||||
style="display: inline;"
|
||||
onsubmit="return confirm('Are you sure you want to delete this post?')">
|
||||
<button type="submit" class="btn btn-outline-danger">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<p class="text-muted">No blog posts yet.</p>
|
||||
<a href="{{ url_for('admin_new_post') }}" class="btn btn-primary">Create Your First Post</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
98
templates/admin/edit_post.html
Normal file
98
templates/admin/edit_post.html
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ title }} - Admin{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>{{ title }}</h1>
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-outline-secondary">Back to Dashboard</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.title.label(class="form-label") }}
|
||||
{{ form.title(class="form-control") }}
|
||||
{% if form.title.errors %}
|
||||
<div class="text-danger">
|
||||
{% for error in form.title.errors %}
|
||||
<small>{{ error }}</small>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
{{ form.category.label(class="form-label") }}
|
||||
{{ form.category(class="form-select") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.excerpt.label(class="form-label") }}
|
||||
{{ form.excerpt(class="form-control", rows="3", placeholder="Auto-generated if left blank") }}
|
||||
<div class="form-text">Brief description shown in blog listings</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.content.label(class="form-label") }}
|
||||
{{ form.content(class="form-control") }}
|
||||
{% if form.content.errors %}
|
||||
<div class="text-danger">
|
||||
{% for error in form.content.errors %}
|
||||
<small>{{ error }}</small>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
{{ form.submit(class="btn btn-primary") }}
|
||||
{% if post %}
|
||||
<a href="{{ url_for('blog_post', post_id=post.id) }}"
|
||||
class="btn btn-outline-info" target="_blank">Preview</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Writing Tips</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="small">
|
||||
<li>Use clear, descriptive titles</li>
|
||||
<li>Break up long paragraphs</li>
|
||||
<li>Include code examples when relevant</li>
|
||||
<li>Add personal insights and experiences</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Markdown Support</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="small">You can use basic HTML in your posts:</p>
|
||||
<code><strong>bold</strong></code><br>
|
||||
<code><em>italic</em></code><br>
|
||||
<code><code>code</code></code><br>
|
||||
<code><pre>code block</pre></code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
45
templates/admin/login.html
Normal file
45
templates/admin/login.html
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Admin Login - Your Name{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-0">Admin Login</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.username.label(class="form-label") }}
|
||||
{{ form.username(class="form-control") }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.password.label(class="form-label") }}
|
||||
{{ form.password(class="form-control") }}
|
||||
</div>
|
||||
|
||||
<div class="d-grid">
|
||||
{{ form.submit(class="btn btn-primary") }}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
216
templates/admin/traffic.html
Normal file
216
templates/admin/traffic.html
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Traffic Analytics - Admin</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px; }
|
||||
.stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
||||
.stat-number { font-size: 2em; font-weight: bold; color: #2563eb; }
|
||||
.stat-label { color: #666; margin-top: 5px; }
|
||||
.chart-container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 30px; }
|
||||
.table-container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
|
||||
th { background: #f8f9fa; font-weight: 600; }
|
||||
h1 { color: #333; margin-bottom: 30px; }
|
||||
h2 { color: #333; margin-bottom: 15px; }
|
||||
.controls { margin-bottom: 20px; }
|
||||
.controls select { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; }
|
||||
.real-time-btn { background: #10b981; color: white; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; }
|
||||
.real-time-btn:hover { background: #059669; }
|
||||
.activity-item { padding: 8px; margin: 4px 0; background: #f8f9fa; border-radius: 4px; font-size: 0.9em; }
|
||||
.activity-time { color: #666; font-size: 0.8em; }
|
||||
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||
@media (max-width: 768px) { .grid-2 { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px;">
|
||||
<h1>Traffic Analytics Dashboard</h1>
|
||||
<div class="controls">
|
||||
<select id="dateRange">
|
||||
<option value="7" {% if days == 7 %}selected{% endif %}>Last 7 days</option>
|
||||
<option value="30" {% if days == 30 %}selected{% endif %}>Last 30 days</option>
|
||||
<option value="90" {% if days == 90 %}selected{% endif %}>Last 90 days</option>
|
||||
</select>
|
||||
<button id="realTimeBtn" class="real-time-btn">
|
||||
Real-time: <span id="activeUsers">0</span> active
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">{{ "{:,}".format(stats.total_views) }}</div>
|
||||
<div class="stat-label">Total Page Views</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">{{ "{:,}".format(stats.unique_visitors) }}</div>
|
||||
<div class="stat-label">Unique Visitors</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">{{ stats.avg_response_time }}ms</div>
|
||||
<div class="stat-label">Avg Response Time</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">{{ stats.bounce_rate }}%</div>
|
||||
<div class="stat-label">Bounce Rate</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Daily Traffic Chart -->
|
||||
<div class="chart-container">
|
||||
<h2>Daily Traffic (Last {{ days }} Days)</h2>
|
||||
<canvas id="dailyTrafficChart" width="400" height="100"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Tables -->
|
||||
<div class="grid-2">
|
||||
<div class="table-container">
|
||||
<h2>Top Pages</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Page</th>
|
||||
<th>Views</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for page, views in top_pages %}
|
||||
<tr>
|
||||
<td>{{ page }}</td>
|
||||
<td>{{ views }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<h2>Top Referrers</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Referrer</th>
|
||||
<th>Views</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for referrer, views in top_referrers %}
|
||||
<tr>
|
||||
<td>{{ referrer[:50] }}{% if referrer|length > 50 %}...{% endif %}</td>
|
||||
<td>{{ views }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="table-container">
|
||||
<h2>Recent Activity (Last 24 Hours)</h2>
|
||||
<div id="recentActivity" style="max-height: 300px; overflow-y: auto;">
|
||||
{% for activity in recent_activity %}
|
||||
<div class="activity-item">
|
||||
<strong>{{ activity.path }}</strong>
|
||||
{% if activity.referrer %}
|
||||
from {{ activity.referrer[:30] }}{% if activity.referrer|length > 30 %}...{% endif %}
|
||||
{% endif %}
|
||||
<div class="activity-time">{{ activity.timestamp.strftime('%Y-%m-%d %H:%M:%S') }} - {{ activity.ip_address[:8] }}...</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Chart.js configurations - Fixed tojsonfilter to tojson
|
||||
const dailyData = {{ daily_views | tojson | safe }};
|
||||
|
||||
// Daily Traffic Chart
|
||||
const ctx = document.getElementById('dailyTrafficChart').getContext('2d');
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: dailyData.map(d => d.date),
|
||||
datasets: [{
|
||||
label: 'Page Views',
|
||||
data: dailyData.map(d => d.views),
|
||||
borderColor: 'rgb(37, 99, 235)',
|
||||
backgroundColor: 'rgba(37, 99, 235, 0.1)',
|
||||
tension: 0.1,
|
||||
fill: true
|
||||
}, {
|
||||
label: 'Unique Visitors',
|
||||
data: dailyData.map(d => d.unique_visitors),
|
||||
borderColor: 'rgb(16, 185, 129)',
|
||||
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
||||
tension: 0.1,
|
||||
fill: false
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Real-time updates
|
||||
function updateRealTime() {
|
||||
fetch('/admin/traffic/api/realtime')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('activeUsers').textContent = data.active_users;
|
||||
|
||||
const activityDiv = document.getElementById('recentActivity');
|
||||
if (data.recent_views && data.recent_views.length > 0) {
|
||||
activityDiv.innerHTML = data.recent_views.map(view => `
|
||||
<div class="activity-item">
|
||||
<strong>${view.path}</strong>
|
||||
<div class="activity-time">
|
||||
${new Date(view.timestamp).toLocaleString()} - ${view.ip_address}
|
||||
${view.country ? ' (' + view.country + ')' : ''}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Real-time update failed:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Update every 30 seconds
|
||||
setInterval(updateRealTime, 30000);
|
||||
updateRealTime(); // Initial load
|
||||
|
||||
// Date range selector
|
||||
document.getElementById('dateRange').addEventListener('change', function() {
|
||||
window.location.href = `?days=${this.value}`;
|
||||
});
|
||||
|
||||
// Real-time button click
|
||||
document.getElementById('realTimeBtn').addEventListener('click', function() {
|
||||
updateRealTime();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
70
templates/base.html
Normal file
70
templates/base.html
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Blake Ridgway - Personal Website{% endblock %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">Blake Ridgway</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('index') }}">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('about') }}">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('hardware') }}">Hardware</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('biking') }}">Biking Adventures</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('blog') }}">Blog</a>
|
||||
</li>
|
||||
{% if current_user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin_dashboard') }}">Admin</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container mt-4">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-light mt-5 py-4">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6 text-center text-md-start">
|
||||
<p class="mb-0">© 2025 Blake Ridgway. All rights reserved.</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-center text-md-end">
|
||||
<div class="social-links">
|
||||
<a href="mailto:blake@blakeridway.com" class="social-link" title="Email"><i class="fas fa-envelope"></i></a>
|
||||
<a href="https://gitlab.com/blakeridgway" class="social-link" title="GitLab" target="_blank" rel="noopener"><i class="fab fa-gitlab"></i></a>
|
||||
<a href="https://floss.social/@blake" class="social-link" title="Mastodon" target="_blank" rel="noopener"><i class="fab fa-mastodon"></i></a>
|
||||
<a href="https://youtube.com/@BlakeRidgway" class="social-link" title="YouTube" target="_blank" rel="noopener"><i class="fab fa-youtube"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
354
templates/biking.html
Normal file
354
templates/biking.html
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Biking Adventures - Blake Ridgway{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h1>Biking Adventures</h1>
|
||||
<p class="lead">Exploring the world on two wheels as an ultra endurance cyclist - powered by real-time Strava data!</p>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h3><i class="fas fa-route text-primary me-2"></i>Recent Adventures</h3>
|
||||
<button class="btn btn-outline-primary btn-sm" onclick="refreshStravaData()">
|
||||
<span id="refresh-icon">🔄</span> Refresh Data
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="recent-activities">
|
||||
{% if recent_activities %}
|
||||
{% for activity in recent_activities %}
|
||||
<div class="card mb-3 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h5 class="card-title">
|
||||
<i class="fas fa-bicycle text-success me-2"></i>{{ activity.name }}
|
||||
</h5>
|
||||
<p class="card-text text-muted">Latest ride from Strava - automatically synced!</p>
|
||||
</div>
|
||||
<span class="badge bg-primary">{{ activity.date }}</span>
|
||||
</div>
|
||||
<div class="row g-2 mt-2">
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="stat-box p-2 bg-light rounded text-center">
|
||||
<i class="fas fa-road text-primary"></i>
|
||||
<div><strong>{{ activity.distance }}</strong></div>
|
||||
<small class="text-muted">Miles</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="stat-box p-2 bg-light rounded text-center">
|
||||
<i class="fas fa-mountain text-warning"></i>
|
||||
<div><strong>{{ activity.elevation }}</strong></div>
|
||||
<small class="text-muted">Feet</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="stat-box p-2 bg-light rounded text-center">
|
||||
<i class="fas fa-clock text-info"></i>
|
||||
<div><strong>{{ activity.time }}</strong></div>
|
||||
<small class="text-muted">Duration</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="stat-box p-2 bg-light rounded text-center">
|
||||
<i class="fas fa-tachometer-alt text-success"></i>
|
||||
<div><strong>{{ activity.avg_speed or 'N/A' }}</strong></div>
|
||||
<small class="text-muted">Avg MPH</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="alert alert-info border-start border-primary border-4">
|
||||
<i class="fas fa-info-circle me-2"></i>Loading Strava data...
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h3 class="mt-5 mb-4"><i class="fas fa-chart-line text-success me-2"></i>2025 Cycling Performance</h3>
|
||||
<div class="row g-4" id="cycling-stats">
|
||||
{% if ytd_stats %}
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
|
||||
<div class="stat-icon mb-3">
|
||||
<i class="fas fa-road fa-2x text-primary"></i>
|
||||
</div>
|
||||
<h4 class="text-primary mb-2">{{ ytd_stats.distance }}</h4>
|
||||
<p class="mb-0">Miles This Year</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
|
||||
<div class="stat-icon mb-3">
|
||||
<i class="fas fa-bicycle fa-2x text-success"></i>
|
||||
</div>
|
||||
<h4 class="text-success mb-2">{{ ytd_stats.count }}</h4>
|
||||
<p class="mb-0">Rides Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
|
||||
<div class="stat-icon mb-3">
|
||||
<i class="fas fa-mountain fa-2x text-warning"></i>
|
||||
</div>
|
||||
<h4 class="text-warning mb-2">{{ ytd_stats.elevation }}</h4>
|
||||
<p class="mb-0">Feet Climbed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
|
||||
<div class="stat-icon mb-3">
|
||||
<i class="fas fa-clock fa-2x text-info"></i>
|
||||
</div>
|
||||
<h4 class="text-info mb-2">{{ ytd_stats.time }}</h4>
|
||||
<p class="mb-0">Hours Riding</p>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<div class="alert alert-warning border-start border-warning border-4">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>Unable to load Strava stats. Please check your API connection.
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0"><i class="fas fa-bicycle me-2"></i>My Bikes</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="bike-item mb-4">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-road fa-lg text-primary me-3"></i>
|
||||
<div>
|
||||
<h6 class="mb-1">Road Bike</h6>
|
||||
<p class="mb-0 text-muted">Giant TCR Advanced 2</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bike-specs">
|
||||
<span class="badge bg-light text-dark me-1">Carbon Frame</span>
|
||||
<span class="badge bg-light text-dark me-1">Aero</span>
|
||||
<span class="badge bg-light text-dark">Racing</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bike-item">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-mountain fa-lg text-warning me-3"></i>
|
||||
<div>
|
||||
<h6 class="mb-1">Gravel Bike</h6>
|
||||
<p class="mb-0 text-muted">Canyon Endurace CF SL 8</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bike-specs">
|
||||
<span class="badge bg-light text-dark me-1">Endurance</span>
|
||||
<span class="badge bg-light text-dark me-1">All-Road</span>
|
||||
<span class="badge bg-light text-dark">Adventure</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0"><i class="fas fa-trophy me-2"></i>Cycling Goals 2025</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="goal-item mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span><i class="fas fa-road text-primary me-2"></i>Annual Distance</span>
|
||||
<span class="badge bg-primary">5,000 mi</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="goal-item mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span><i class="fas fa-mountain text-warning me-2"></i>Elevation Gain</span>
|
||||
<span class="badge bg-warning">250,000 ft</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="goal-item mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span><i class="fas fa-bicycle text-success me-2"></i>Total Rides</span>
|
||||
<span class="badge bg-success">200 rides</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="goal-item">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span><i class="fas fa-medal text-danger me-2"></i>Ultra Events</span>
|
||||
<span class="badge bg-danger">3 events</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-secondary text-white">
|
||||
<h5 class="mb-0"><i class="fas fa-info-circle me-2"></i>Data Source</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="mb-2">
|
||||
<i class="fab fa-strava text-danger me-2"></i>
|
||||
<strong>Powered by Strava API</strong>
|
||||
</p>
|
||||
<p class="small text-muted mb-3">
|
||||
Real-time cycling data automatically synced from my Strava account.
|
||||
</p>
|
||||
<p class="small text-muted mb-0">
|
||||
<strong>Last updated:</strong> <span id="last-updated">Just now</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function refreshStravaData() {
|
||||
const refreshIcon = document.getElementById('refresh-icon');
|
||||
refreshIcon.style.animation = 'spin 1s linear infinite';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/strava-stats');
|
||||
const data = await response.json();
|
||||
|
||||
// Update stats
|
||||
if (data.ytd_stats) {
|
||||
updateStats(data.ytd_stats);
|
||||
}
|
||||
|
||||
// Update recent activities
|
||||
if (data.recent_activities) {
|
||||
updateRecentActivities(data.recent_activities);
|
||||
}
|
||||
|
||||
// Update timestamp
|
||||
document.getElementById('last-updated').textContent = new Date().toLocaleString();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error refreshing Strava data:', error);
|
||||
} finally {
|
||||
refreshIcon.style.animation = '';
|
||||
}
|
||||
}
|
||||
|
||||
function updateStats(stats) {
|
||||
const statsContainer = document.getElementById('cycling-stats');
|
||||
statsContainer.innerHTML = `
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
|
||||
<div class="stat-icon mb-3">
|
||||
<i class="fas fa-road fa-2x text-primary"></i>
|
||||
</div>
|
||||
<h4 class="text-primary mb-2">${stats.distance}</h4>
|
||||
<p class="mb-0">Miles This Year</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
|
||||
<div class="stat-icon mb-3">
|
||||
<i class="fas fa-bicycle fa-2x text-success"></i>
|
||||
</div>
|
||||
<h4 class="text-success mb-2">${stats.count}</h4>
|
||||
<p class="mb-0">Rides Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
|
||||
<div class="stat-icon mb-3">
|
||||
<i class="fas fa-mountain fa-2x text-warning"></i>
|
||||
</div>
|
||||
<h4 class="text-warning mb-2">${stats.elevation}</h4>
|
||||
<p class="mb-0">Feet Climbed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
|
||||
<div class="stat-icon mb-3">
|
||||
<i class="fas fa-clock fa-2x text-info"></i>
|
||||
</div>
|
||||
<h4 class="text-info mb-2">${stats.time}</h4>
|
||||
<p class="mb-0">Hours Riding</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function updateRecentActivities(activities) {
|
||||
const container = document.getElementById('recent-activities');
|
||||
if (activities.length === 0) {
|
||||
container.innerHTML = '<div class="alert alert-info border-start border-primary border-4"><i class="fas fa-info-circle me-2"></i>No recent activities found.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = activities.map(activity => `
|
||||
<div class="card mb-3 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h5 class="card-title">
|
||||
<i class="fas fa-bicycle text-success me-2"></i>${activity.name}
|
||||
</h5>
|
||||
<p class="card-text text-muted">Latest ride from Strava - automatically synced!</p>
|
||||
</div>
|
||||
<span class="badge bg-primary">${activity.date}</span>
|
||||
</div>
|
||||
<div class="row g-2 mt-2">
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="stat-box p-2 bg-light rounded text-center">
|
||||
<i class="fas fa-road text-primary"></i>
|
||||
<div><strong>${activity.distance}</strong></div>
|
||||
<small class="text-muted">Miles</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="stat-box p-2 bg-light rounded text-center">
|
||||
<i class="fas fa-mountain text-warning"></i>
|
||||
<div><strong>${activity.elevation}</strong></div>
|
||||
<small class="text-muted">Feet</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="stat-box p-2 bg-light rounded text-center">
|
||||
<i class="fas fa-clock text-info"></i>
|
||||
<div><strong>${activity.time}</strong></div>
|
||||
<small class="text-muted">Duration</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="stat-box p-2 bg-light rounded text-center">
|
||||
<i class="fas fa-tachometer-alt text-success"></i>
|
||||
<div><strong>${activity.avg_speed || 'N/A'}</strong></div>
|
||||
<small class="text-muted">Avg MPH</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Auto-refresh every 5 minutes
|
||||
setInterval(refreshStravaData, 300000);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.stat-box:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
78
templates/blog.html
Normal file
78
templates/blog.html
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Blog - Blake Ridgway{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h1><i class="fas fa-blog text-primary me-2"></i>Blog</h1>
|
||||
<p class="lead">Thoughts on cybersecurity, systems administration, technology, cycling, and the journey of continuous learning.</p>
|
||||
|
||||
{% if posts %}
|
||||
{% for post in posts %}
|
||||
<article class="card mb-4 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<div class="flex-grow-1">
|
||||
<h2 class="card-title h4 mb-2">
|
||||
<a href="{{ url_for('blog_post', post_id=post.id) }}" class="text-decoration-none">
|
||||
{{ post.title }}
|
||||
</a>
|
||||
</h2>
|
||||
<div class="post-meta mb-2">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-calendar-alt me-1"></i>{{ post.date }}
|
||||
<i class="fas fa-user ms-3 me-1"></i>Blake Ridgway
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge bg-primary ms-3">{{ post.category }}</span>
|
||||
</div>
|
||||
|
||||
<p class="card-text">{{ post.excerpt }}</p>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<a href="{{ url_for('blog_post', post_id=post.id) }}" class="btn btn-outline-primary btn-sm">
|
||||
<i class="fas fa-arrow-right me-1"></i>Read More
|
||||
</a>
|
||||
<div class="post-tags">
|
||||
{% if post.tags %}
|
||||
{% for tag in post.tags %}
|
||||
<span class="badge bg-light text-dark me-1">#{{ tag }}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Pagination implement it later)
|
||||
<nav aria-label="Blog pagination" class="mt-4">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">Previous</span>
|
||||
</li>
|
||||
<li class="page-item active">
|
||||
<span class="page-link">1</span>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">Next</span>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
-->
|
||||
{% else %}
|
||||
<div class="alert alert-info border-start border-primary border-4 shadow-sm">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-info-circle fa-2x text-primary me-3"></i>
|
||||
<div>
|
||||
<h4 class="alert-heading mb-2">No posts yet!</h4>
|
||||
<p class="mb-0">I'm currently working on some exciting content about cybersecurity, systems administration, and my cycling adventures. Check back soon for new posts!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
287
templates/blog_post.html
Normal file
287
templates/blog_post.html
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ post.title }} - Blake Ridgway{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<article>
|
||||
<header class="mb-4">
|
||||
<nav aria-label="breadcrumb" class="mb-3">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{{ url_for('index') }}" class="text-decoration-none">
|
||||
<i class="fas fa-home me-1"></i>Home
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{{ url_for('blog') }}" class="text-decoration-none">
|
||||
<i class="fas fa-blog me-1"></i>Blog
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ post.title }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<h1 class="display-5 mb-3">{{ post.title }}</h1>
|
||||
|
||||
<div class="post-meta mb-4 p-3 bg-light rounded-3">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<div class="d-flex align-items-center mb-2 mb-md-0">
|
||||
<img src="{{ url_for('static', filename='images/profile.jpg') }}"
|
||||
alt="Blake Ridgway"
|
||||
class="rounded-circle me-3"
|
||||
width="40" height="40">
|
||||
<div>
|
||||
<div class="fw-bold">Blake Ridgway</div>
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-calendar-alt me-1"></i>{{ post.date }}
|
||||
<i class="fas fa-clock ms-3 me-1"></i>~5 min read
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 text-md-end">
|
||||
<span class="badge bg-primary fs-6 px-3 py-2">
|
||||
<i class="fas fa-tag me-1"></i>{{ post.category }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="blog-content">
|
||||
<div class="content-wrapper p-4 bg-white rounded-3 shadow-sm">
|
||||
{{ post.content|safe }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Post Tags (if available) -->
|
||||
{% if post.tags %}
|
||||
<div class="post-tags mt-4">
|
||||
<h6 class="mb-2">Tags:</h6>
|
||||
{% for tag in post.tags %}
|
||||
<span class="badge bg-light text-dark me-2 mb-2">#{{ tag }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</article>
|
||||
|
||||
<hr class="my-5">
|
||||
|
||||
<!-- Navigation and Actions -->
|
||||
<div class="post-actions">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<a href="{{ url_for('blog') }}" class="btn btn-outline-primary">
|
||||
<i class="fas fa-arrow-left me-2"></i>Back to Blog
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<button class="btn btn-outline-success me-2" onclick="sharePost()">
|
||||
<i class="fas fa-share-alt me-2"></i>Share Post
|
||||
</button>
|
||||
<button class="btn btn-outline-info" onclick="printPost()">
|
||||
<i class="fas fa-print me-2"></i>Print
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<!-- Table of Contents -->
|
||||
|
||||
|
||||
<!-- Post Information -->
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0"><i class="fas fa-info-circle me-2"></i>Post Details</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="detail-item mb-3">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span><i class="fas fa-tag text-primary me-2"></i>Category:</span>
|
||||
<span class="badge bg-primary">{{ post.category }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-item mb-3">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span><i class="fas fa-calendar-alt text-success me-2"></i>Published:</span>
|
||||
<span>{{ post.date }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-item mb-3">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span><i class="fas fa-clock text-warning me-2"></i>Reading Time:</span>
|
||||
<span>~5 min</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span><i class="fas fa-user text-info me-2"></i>Author:</span>
|
||||
<span>Blake Ridgway</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Share This Post -->
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0"><i class="fas fa-share-alt me-2"></i>Share This Post</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-outline-primary btn-sm" onclick="sharePost()">
|
||||
<i class="fas fa-link me-2"></i>Copy Link
|
||||
</button>
|
||||
<button class="btn btn-outline-info btn-sm" onclick="shareTwitter()">
|
||||
<i class="fab fa-twitter me-2"></i>Share on Twitter
|
||||
</button>
|
||||
<button class="btn btn-outline-primary btn-sm" onclick="shareLinkedIn()">
|
||||
<i class="fab fa-linkedin me-2"></i>Share on LinkedIn
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Author Bio -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-secondary text-white">
|
||||
<h5 class="mb-0"><i class="fas fa-user-circle me-2"></i>About the Author</h5>
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<img src="{{ url_for('static', filename='images/profile.jpg') }}"
|
||||
alt="Blake Ridgway"
|
||||
class="rounded-circle mb-3"
|
||||
width="80" height="80">
|
||||
<h6>Blake Ridgway</h6>
|
||||
<p class="small text-muted mb-3">
|
||||
Systems Administrator & Cybersecurity Enthusiast passionate about secure infrastructure and continuous learning.
|
||||
</p>
|
||||
<div class="d-flex justify-content-center gap-2">
|
||||
<a href="{{ url_for('about') }}" class="btn btn-outline-primary btn-sm">
|
||||
<i class="fas fa-user me-1"></i>About
|
||||
</a>
|
||||
<a href="https://gitlab.com/blakeridgway/" target="_blank" rel="noopener noreferrer" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="fab fa-gitlab me-1"></i>GitLab
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function sharePost() {
|
||||
if (navigator.share) {
|
||||
navigator.share({
|
||||
title: '{{ post.title }}',
|
||||
text: 'Check out this blog post by Blake Ridgway',
|
||||
url: window.location.href
|
||||
});
|
||||
} else {
|
||||
// Fallback: copy to clipboard
|
||||
navigator.clipboard.writeText(window.location.href).then(() => {
|
||||
showToast('Link copied to clipboard!');
|
||||
}).catch(() => {
|
||||
// Fallback for older browsers
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = window.location.href;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
showToast('Link copied to clipboard!');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function shareTwitter() {
|
||||
const url = encodeURIComponent(window.location.href);
|
||||
const text = encodeURIComponent('{{ post.title }} by @BlakeRidgway');
|
||||
window.open(`https://twitter.com/intent/tweet?url=${url}&text=${text}`, '_blank');
|
||||
}
|
||||
|
||||
function shareLinkedIn() {
|
||||
const url = encodeURIComponent(window.location.href);
|
||||
window.open(`https://www.linkedin.com/sharing/share-offsite/?url=${url}`, '_blank');
|
||||
}
|
||||
|
||||
function printPost() {
|
||||
window.print();
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
// Simple toast notification
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'alert alert-success position-fixed';
|
||||
toast.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 250px;';
|
||||
toast.innerHTML = `<i class="fas fa-check-circle me-2"></i>${message}`;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Smooth scrolling for table of contents
|
||||
document.querySelectorAll('.toc a').forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.blog-content {
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.blog-content h2, .blog-content h3, .blog-content h4 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.blog-content p {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.blog-content code {
|
||||
background-color: #f8f9fa;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.blog-content pre {
|
||||
background-color: #f8f9fa;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.blog-content blockquote {
|
||||
border-left: 4px solid #007bff;
|
||||
padding-left: 1rem;
|
||||
margin: 1.5rem 0;
|
||||
font-style: italic;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.col-md-4 {
|
||||
display: none;
|
||||
}
|
||||
.col-md-8 {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
201
templates/hardware.html
Normal file
201
templates/hardware.html
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Hardware - Blake Ridgway{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h1>My Hardware Setup</h1>
|
||||
<p class="lead">A showcase of my current tech setup and hardware projects focused on systems administration, cybersecurity, and development.</p>
|
||||
|
||||
<h3>Current PC Build</h3>
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h4 class="mb-0"><i class="fas fa-desktop me-2"></i>Main Workstation</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fab fa-fedora text-primary me-2"></i>OS:</strong> Fedora 42 Workstation
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-microchip text-info me-2"></i>CPU:</strong> Intel Core i9-12900K
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-tv text-success me-2"></i>GPU:</strong> Intel Arc A770
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-memory text-warning me-2"></i>RAM:</strong> Corsair LPX 64GB (3200 MHz)
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-hdd text-danger me-2"></i>NVMe:</strong> Samsung 980 PRO 1TB
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-hdd text-secondary me-2"></i>SSD:</strong> Samsung 860 EVO 500GB
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-hdd text-secondary me-2"></i>HDD:</strong> WD Green 1TB
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-hdd text-secondary me-2"></i>Storage:</strong> Seagate Constellation 4TB
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-microchip text-info me-2"></i>Mobo:</strong> ASRock B660M PRO
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-plug text-warning me-2"></i>PSU:</strong> Corsair CX750M
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-cube text-dark me-2"></i>Case:</strong> CORSAIR 4000D
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Peripherals & Accessories</h3>
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h4 class="mb-0"><i class="fas fa-keyboard me-2"></i>Input & Output Devices</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-desktop text-primary me-2"></i>Primary:</strong> LG 34WQ500-B - 34"
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-desktop text-secondary me-2"></i>Secondary:</strong> ASUS VS248H - 24"
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-keyboard text-info me-2"></i>Keyboard:</strong> System76 Launch
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-mouse text-warning me-2"></i>Mouse:</strong> Logitech G305
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-headphones text-success me-2"></i>Headset:</strong> CCZ DC01 PRO
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-sliders-h text-danger me-2"></i>Mixer:</strong> Pupgsis T12
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="spec-item p-2 bg-light rounded">
|
||||
<strong><i class="fas fa-video text-primary me-2"></i>Webcam:</strong> Logitech C920x
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Current Hardware Projects</h3>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="skill-card p-4 bg-light rounded-3 shadow-sm h-100">
|
||||
<div class="project-icon mb-3">
|
||||
<i class="fas fa-server fa-2x text-primary"></i>
|
||||
</div>
|
||||
<h5>Home Server Re-setup</h5>
|
||||
<p class="mb-0">
|
||||
Reinstalling Proxmox on my PowerEdge R720 to host containers and VMs for expanding
|
||||
knowledge in cybersecurity, systems administration, and development projects.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="skill-card p-4 bg-light rounded-3 shadow-sm h-100">
|
||||
<div class="project-icon mb-3">
|
||||
<i class="fas fa-shield-alt fa-2x text-danger"></i>
|
||||
</div>
|
||||
<h5>Open Source SOC</h5>
|
||||
<p class="mb-0">
|
||||
Building a Security Operations Center using open source tools like ELK Stack,
|
||||
Wazuh, and MISP for threat detection and incident response.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0"><i class="fas fa-chart-pie me-2"></i>System Stats</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="stat-item mb-3">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span><strong>Total Storage:</strong></span>
|
||||
<span class="badge bg-primary">6.5TB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item mb-3">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span><strong>RAM:</strong></span>
|
||||
<span class="badge bg-success">64GB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item mb-3">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span><strong>CPU Cores:</strong></span>
|
||||
<span class="badge bg-warning">16C/24T</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item mb-3">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span><strong>Display Area:</strong></span>
|
||||
<span class="badge bg-info">58" Total</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-secondary text-white">
|
||||
<h5 class="mb-0"><i class="fas fa-tools me-2"></i>Hardware Focus</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2"><i class="fas fa-server text-primary me-2"></i>Enterprise Server Hardware</li>
|
||||
<li class="mb-2"><i class="fas fa-network-wired text-success me-2"></i>Network Infrastructure</li>
|
||||
<li class="mb-2"><i class="fas fa-shield-alt text-danger me-2"></i>Security Appliances</li>
|
||||
<li class="mb-2"><i class="fas fa-cloud text-info me-2"></i>Virtualization Platforms</li>
|
||||
<li class="mb-2"><i class="fab fa-linux text-warning me-2"></i>Linux Workstations</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
61
templates/index.html
Normal file
61
templates/index.html
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="hero-section text-center">
|
||||
<div class="container">
|
||||
<!-- Animated Greeting -->
|
||||
<div class="hero-greeting mb-3">
|
||||
<h1 class="mb-3">
|
||||
Greetings, I'm Blake Ridgway
|
||||
<img src="https://media.giphy.com/media/hvRJCLFzcasrR4ia7z/giphy.gif"
|
||||
width="35" height="35"
|
||||
alt="Waving hand"
|
||||
class="wave-animation">
|
||||
</h1>
|
||||
<div class="hero-tagline">
|
||||
<span class="badge bg-primary me-2">Systems Administrator</span>
|
||||
<span class="badge bg-success me-2">Cybersecurity Enthusiast</span>
|
||||
<span class="badge bg-info me-2">Linux & Open Source Advocate</span>
|
||||
<span class="badge bg-warning">Ultra Endurance Cyclist</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mission Statement -->
|
||||
<div class="hero-mission my-4">
|
||||
<p class="lead mb-3">
|
||||
As a Systems Administrator, my focus is on ensuring the stability, integrity, and performance of critical IT infrastructure.
|
||||
Through hands-on experience managing Linux, AWS, and Azure environments, I've learned that
|
||||
<strong class="text-light">true system stability is impossible without robust security</strong>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Personal Projects -->
|
||||
<div class="text-center mb-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card border-0 shadow-lg bg-gradient">
|
||||
<div class="card-body text-white p-4">
|
||||
<h3 class="card-title mb-3">🚴 Personal Projects</h3>
|
||||
<p class="card-text mb-4">
|
||||
Beyond my professional endeavors, I'm an avid cyclist and open-source enthusiast.
|
||||
I'm currently developing a platform to help cyclists train smarter, combining
|
||||
data-driven insights with a strong emphasis on
|
||||
<strong>end-to-end data encryption, user privacy, and secure development practices.</strong>
|
||||
</p>
|
||||
<div class="d-flex justify-content-center gap-3">
|
||||
<a href="/biking" class="btn btn-light">
|
||||
<i class="fas fa-bicycle me-2"></i>View My Cycling Stats
|
||||
</a>
|
||||
<a href="/blog" class="btn btn-outline-light">
|
||||
<i class="fas fa-blog me-2"></i>Read My Blog
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue