Save all current changes to resume project
ci/woodpecker/push/woodpecker Pipeline failed
Details
ci/woodpecker/push/woodpecker Pipeline failed
Details
This commit is contained in:
parent
be50c5de9c
commit
a1e2afabb5
|
@ -1,23 +0,0 @@
|
|||
---
|
||||
description:
|
||||
globs: *.js,*.css
|
||||
alwaysApply: false
|
||||
---
|
||||
# Asset Hashing and CSP Update Rule
|
||||
|
||||
This rule ensures that all `.js` and `.css` assets are properly hashed and their integrity hashes are updated in `index.html` and added to the Content Security Policy (CSP) in the `Caddyfile` during every build and push process.
|
||||
|
||||
## Process to Follow
|
||||
|
||||
1. **Hash Calculation**: Before building the Docker image, calculate the SHA256 hash for each `.js` and `.css` file in the `docker/resume/` directory using a command like `shasum -a 256 filename | awk '{print $1}' | xxd -r -p | base64`.
|
||||
2. **Update index.html**: Update the `integrity` attribute in the `<script>` and `<link>` tags in `index.html` with the new hashes for each respective file.
|
||||
3. **Update CSP in Caddyfile**: Ensure the CSP in the `Caddyfile` includes the hashes for all scripts and styles under `script-src` and `style-src` directives respectively.
|
||||
4. **Automate in Build Script**: Modify the `build-test-deploy.sh` script to automate the hashing and updating process before the Docker build step.
|
||||
5. **Verification**: After updating, verify that the hashes in `index.html` and `Caddyfile` match the calculated hashes to prevent deployment with mismatched integrity.
|
||||
|
||||
## Relevant Files
|
||||
- [docker/resume/index.html](mdc:docker/resume/index.html)
|
||||
- [docker/resume/Caddyfile](mdc:docker/resume/Caddyfile)
|
||||
- [build-test-deploy.sh](mdc:build-test-deploy.sh)
|
||||
|
||||
This rule must be followed to maintain security integrity and prevent CSP violations during deployment.
|
|
@ -1,62 +0,0 @@
|
|||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
# Deployment Workflow
|
||||
|
||||
## Testing Requirements
|
||||
- All changes must be tested locally before deployment
|
||||
- Tests must pass for both mobile and desktop viewports
|
||||
- Lighthouse tests must maintain perfect scores (100/100) for accessibility and SEO
|
||||
- Playwright tests must pass for all viewport sizes
|
||||
|
||||
## Git Operations
|
||||
- Avoid use direct git commands (`git add`, `git commit`, `git push`) if we do git flows do then as oneliners.
|
||||
- All git operations must be performed through `./build-test-deploy.sh`
|
||||
- The script handles:
|
||||
- Building the Docker container
|
||||
- Running all tests
|
||||
- Committing changes
|
||||
- Pushing to repository
|
||||
|
||||
## Build-Test-Deploy Script
|
||||
The `./build-test-deploy.sh` script is the single source of truth for deployment:
|
||||
1. Builds the Docker container
|
||||
2. Runs the test suite
|
||||
3. Only proceeds with git operations if tests pass
|
||||
4. Handles all git operations in the correct order
|
||||
|
||||
## Testing Process
|
||||
1. Make changes to the codebase
|
||||
2. Run `./build-test-deploy.sh` to:
|
||||
- Build the container
|
||||
- Run tests
|
||||
- Deploy if tests pass
|
||||
3. If tests fail:
|
||||
- Fix the issues
|
||||
- Run the script again
|
||||
- Do not proceed with deployment until all tests pass
|
||||
|
||||
## Viewport Testing
|
||||
The test suite runs against multiple viewport sizes:
|
||||
- Mobile: 375x667
|
||||
- Desktop: 1024x768
|
||||
|
||||
All tests must pass for all viewport sizes before deployment is allowed.
|
||||
|
||||
## Lighthouse Requirements
|
||||
Maintain perfect scores:
|
||||
- Accessibility: 100/100
|
||||
- SEO: 100/100
|
||||
- Performance: 97/100 minimum
|
||||
- Best Practices: 93/100 minimum
|
||||
|
||||
## Playwright Tests
|
||||
Must pass all accessibility tests:
|
||||
- WCAG 2.1 Level AAA compliance
|
||||
- ARIA attributes
|
||||
- Heading structure
|
||||
- External link validation
|
||||
- Color contrast
|
||||
- Image alt text
|
|
@ -0,0 +1,17 @@
|
|||
# HTML Standards
|
||||
|
||||
## Accessibility Requirements
|
||||
- All images must have alt text
|
||||
- Proper heading structure (h1, h2, etc.) must be maintained
|
||||
- ARIA attributes must be used where appropriate
|
||||
- Color contrast must meet WCAG 2.1 Level AAA compliance
|
||||
|
||||
## Security
|
||||
- All script and style tags must include integrity attributes
|
||||
- External links must include rel="noopener noreferrer" attributes
|
||||
- No inline scripts or styles without proper CSP nonce/hash
|
||||
|
||||
## Structure
|
||||
- Use includes.js for template components when possible
|
||||
- Reference includes from [docker/resume/includes/](mdc:docker/resume/includes/)
|
||||
- Follow the pattern in [docker/resume/index-with-includes.html](mdc:docker/resume/index-with-includes.html)
|
|
@ -0,0 +1,15 @@
|
|||
# JavaScript and CSS Standards
|
||||
|
||||
## Security Requirements
|
||||
- All JS and CSS files must be hashed for integrity checks
|
||||
- Hashes must be updated in both HTML and Caddyfile CSP headers
|
||||
- Use `shasum -a 256` followed by base64 encoding for hash generation
|
||||
|
||||
## File References
|
||||
- JavaScript files should be referenced in HTML with integrity attributes
|
||||
- CSS files should be referenced with integrity attributes
|
||||
- The CSP in [docker/resume/Caddyfile](mdc:docker/resume/Caddyfile) must include these hashes
|
||||
|
||||
## Automation
|
||||
- Run `docker/resume/update-csp-hashes.sh` after modifying any JS or CSS file
|
||||
- Verify hashes match between HTML and Caddyfile before deployment
|
|
@ -0,0 +1,23 @@
|
|||
# Project Guidelines
|
||||
|
||||
## Core Principles
|
||||
- Use the `./build-test-deploy.sh` script for all build, test, and deployment operations
|
||||
- Never use interactive CLI prompts in any scripts or processes
|
||||
- Avoid creating duplicate files - use git for version control instead
|
||||
- Don't modify Docker files unless absolutely necessary, especially production Dockerfiles
|
||||
|
||||
## File Structure
|
||||
- Main resume content is in `docker/resume/index.html`
|
||||
- Styles are in `docker/resume/styles.css`
|
||||
- Server configuration is in `docker/resume/Caddyfile`
|
||||
|
||||
## Security Requirements
|
||||
- All JS and CSS assets must have integrity hashes in HTML and CSP headers
|
||||
- The Caddyfile must include proper Content-Security-Policy headers
|
||||
- Use `update-csp-hashes.sh` to update security hashes
|
||||
|
||||
## Testing Standards
|
||||
- All changes require passing tests before deployment
|
||||
- Tests must pass for both mobile and desktop viewports
|
||||
- Maintain Lighthouse scores: 100/100 for accessibility and SEO
|
||||
- Tests must be meaningful with actual assertions, not placeholders
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
# Test Suite Integrity Rule
|
||||
|
||||
This rule ensures that the test suite for the project contains meaningful tests that perform actual checks and validations, rather than being empty placeholder boilerplate. All tests MUST do SOMETHING significant to verify the functionality, security, or performance of the application.
|
||||
|
||||
## Guidelines to Follow
|
||||
|
||||
1. **Non-Empty Tests**: Every test file in the `tests/` directory must contain at least one test case that performs a specific validation or check. Empty test files or test cases with no assertions are not allowed.
|
||||
2. **Meaningful Assertions**: Each test must include assertions or expectations that verify the behavior of the application. Tests should not simply run without checking outcomes (e.g., no empty `expect()` calls or trivial assertions like `expect(true).toBe(true)`).
|
||||
3. **Coverage of Key Features**: The test suite must cover critical aspects of the application, including but not limited to:
|
||||
- Accessibility (WCAG compliance)
|
||||
- Security headers and CSP compliance
|
||||
- Functional features like the PDF download button
|
||||
- Performance metrics (via tools like Lighthouse)
|
||||
4. **Regular Review**: Before running `build-test-deploy.sh`, review the test suite to ensure no placeholder or boilerplate tests remain. If a test is temporarily skipped or incomplete, it must be annotated with a clear `TODO` comment explaining the reason and a plan for completion.
|
||||
5. **Automation in Build Script**: If possible, add a pre-test step in `build-test-deploy.sh` to scan test files for empty or placeholder content (e.g., using grep to detect empty test blocks or missing assertions) and fail the build if such issues are found.
|
||||
|
||||
## Relevant Files and Directories
|
||||
- [tests/](mdc:tests)
|
||||
- [build-test-deploy.sh](mdc:build-test-deploy.sh)
|
||||
|
||||
This rule must be adhered to in order to maintain a robust and reliable testing framework that ensures the quality and security of the application.
|
|
@ -0,0 +1,11 @@
|
|||
# Ignore all files and directories in the root except for the docker directory.
|
||||
/build-test-deploy.sh
|
||||
/docker-compose.production.yml
|
||||
/docker-compose.staging.yml
|
||||
/package-lock.json
|
||||
/package.json
|
||||
/playwright.config.js
|
||||
/README.md
|
||||
/stack.production.yml
|
||||
/stack.staging.yml
|
||||
/tests/
|
|
@ -6,28 +6,8 @@ CONTAINER_NAME="resume_test_container"
|
|||
DOCKER_DIR="docker"
|
||||
RESUME_DIR="$DOCKER_DIR/resume"
|
||||
|
||||
# Recalculate SHA256 hash for styles.css and update integrity in index.html
|
||||
STYLES_HASH=$(shasum -a 256 $RESUME_DIR/styles.css | awk '{print $1}' | xxd -r -p | base64)
|
||||
sed -i -E "s|href=\"styles.css\" integrity=\"sha256-[^\"]*\"|href=\"styles.css\" integrity=\"sha256-$STYLES_HASH\"|" $RESUME_DIR/index.html
|
||||
|
||||
# Verify the hash update
|
||||
if ! grep -q "integrity=\"sha256-$STYLES_HASH\"" $RESUME_DIR/index.html; then
|
||||
echo "Error: Integrity hash update failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test the CSS loading
|
||||
if ! curl -s http://localhost:8080/styles.css > /dev/null; then
|
||||
echo "Error: CSS file not accessible."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify the hash in the container
|
||||
CONTAINER_HASH=$(docker exec $CONTAINER_NAME cat /srv/styles.css | shasum -a 256 | awk '{print $1}' | xxd -r -p | base64)
|
||||
if [ "$CONTAINER_HASH" != "$STYLES_HASH" ]; then
|
||||
echo "Error: Integrity hash mismatch in container."
|
||||
exit 1
|
||||
fi
|
||||
# Note: We no longer need to manually update the CSS hash here
|
||||
# as it's handled by the update-csp-hashes.sh script during Docker build
|
||||
|
||||
# Build Docker image
|
||||
cd "$DOCKER_DIR"
|
||||
|
|
|
@ -29,7 +29,7 @@ colinknapp.com {
|
|||
Cache-Control "public, max-age=31536000, immutable"
|
||||
|
||||
# CSP with hashes for scripts and styles
|
||||
Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-BASkmAmg7eoYCMd6odA6kQ8yGsFnoxaX48WbQvMkehs='; style-src 'self' 'sha256-Mo+7o3oPEKpX7fqRvTtunvQHlIDhJ0SxAMG1PCNniCI='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
|
||||
Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
|
||||
}
|
||||
|
||||
# Handle 404s
|
||||
|
@ -79,7 +79,7 @@ colinknapp.com {
|
|||
Cache-Control "public, max-age=31536000, immutable"
|
||||
|
||||
# CSP with hashes for scripts and styles
|
||||
Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-BASkmAmg7eoYCMd6odA6kQ8yGsFnoxaX48WbQvMkehs='; style-src 'self' 'sha256-Mo+7o3oPEKpX7fqRvTtunvQHlIDhJ0SxAMG1PCNniCI='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
|
||||
Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
|
||||
}
|
||||
|
||||
# Handle 404s
|
||||
|
@ -98,4 +98,4 @@ colinknapp.com {
|
|||
precompressed
|
||||
browse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# =====================================================================
|
||||
# Caddyfile.local - Local development server configuration
|
||||
# =====================================================================
|
||||
# To manage the server, use the unified script:
|
||||
# ./caddy.sh start # Start the server
|
||||
# ./caddy.sh stop # Stop the server
|
||||
# ./caddy.sh restart # Restart the server
|
||||
# ./caddy.sh status # Check server status
|
||||
#
|
||||
# DO NOT run caddy directly from other directories as it may not find
|
||||
# this configuration file correctly.
|
||||
# =====================================================================
|
||||
|
||||
:8080 {
|
||||
root * .
|
||||
file_server
|
||||
encode gzip
|
||||
|
||||
# Performance optimizations
|
||||
header {
|
||||
# Remove default Caddy headers
|
||||
-Server
|
||||
-X-Powered-By
|
||||
|
||||
# Basic security headers
|
||||
X-Frame-Options "DENY"
|
||||
X-Content-Type-Options "nosniff"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
|
||||
# Permissions policy
|
||||
Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
|
||||
|
||||
# Cross-origin isolation headers
|
||||
Cross-Origin-Embedder-Policy "require-corp"
|
||||
Cross-Origin-Resource-Policy "same-origin"
|
||||
Cross-Origin-Opener-Policy "same-origin"
|
||||
|
||||
# Cache control for static assets
|
||||
Cache-Control "public, max-age=31536000, immutable"
|
||||
|
||||
# CSP with hashes for scripts and styles
|
||||
Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
|
||||
}
|
||||
}
|
|
@ -1,19 +1,33 @@
|
|||
FROM caddy:2-alpine
|
||||
|
||||
# Install required tools for hash calculation
|
||||
RUN apk add --no-cache bash coreutils findutils grep sed xxd perl gawk
|
||||
|
||||
# Copy update scripts first
|
||||
COPY update-csp-hashes.sh /srv/update-csp-hashes.sh
|
||||
COPY caddy.sh /srv/caddy.sh
|
||||
|
||||
# Copy Caddyfile and static content
|
||||
COPY Caddyfile /etc/caddy/Caddyfile
|
||||
COPY index.html /srv/
|
||||
COPY theme.js /srv/
|
||||
COPY utils.js /srv/
|
||||
COPY styles.css /srv/
|
||||
COPY pdf-download.js /srv/
|
||||
COPY favicon.ico /srv/
|
||||
|
||||
# Expose port 8080
|
||||
EXPOSE 8080
|
||||
# Copy one-pager-tools directory
|
||||
COPY one-pager-tools /srv/one-pager-tools/
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /srv
|
||||
|
||||
# Run the update-csp-hashes.sh script to update CSP hashes
|
||||
RUN chmod +x /srv/caddy.sh /srv/update-csp-hashes.sh && \
|
||||
cd /srv && \
|
||||
./update-csp-hashes.sh
|
||||
|
||||
# Expose port 8080
|
||||
EXPOSE 8080
|
||||
|
||||
# Run Caddy
|
||||
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"]
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# Resume Website
|
||||
|
||||
## Local Development
|
||||
|
||||
To run the local development server:
|
||||
|
||||
1. Navigate to the `docker/resume` directory
|
||||
2. Use the unified Caddy management script:
|
||||
```
|
||||
./caddy.sh start # Start the server
|
||||
./caddy.sh stop # Stop the server
|
||||
./caddy.sh restart # Restart the server
|
||||
./caddy.sh status # Check server status
|
||||
```
|
||||
3. Open http://localhost:8080 in your browser
|
||||
|
||||
### Important Notes
|
||||
|
||||
- Always use the `caddy.sh` script to manage the server
|
||||
- Do not run Caddy directly from other directories
|
||||
|
||||
## Content Security Policy (CSP)
|
||||
|
||||
When adding new scripts or styles, you need to update the CSP hashes:
|
||||
|
||||
1. Make your changes to JS/CSS files
|
||||
2. Run the update script:
|
||||
```
|
||||
./update-csp-hashes.sh
|
||||
```
|
||||
3. Restart the server using `./caddy.sh restart`
|
||||
|
||||
## Tools
|
||||
|
||||
- **CSV Viewer**: A tool for viewing CSV data in tabular format
|
||||
- Available at http://localhost:8080/one-pager-tools/csv-tool.html
|
||||
- Simply paste CSV data to view it as a formatted table
|
|
@ -0,0 +1,109 @@
|
|||
{"level":"info","ts":1751739278.708652,"msg":"maxprocs: Leaving GOMAXPROCS=8: CPU quota undefined"}
|
||||
{"level":"info","ts":1751739278.70898,"msg":"GOMEMLIMIT is updated","package":"github.com/KimMachineGun/automemlimit/memlimit","GOMEMLIMIT":7730941132,"previous":9223372036854775807}
|
||||
{"level":"info","ts":1751739278.709314,"msg":"using config from file","file":"Caddyfile"}
|
||||
{"level":"info","ts":1751739278.7104902,"msg":"adapted config to JSON","adapter":"caddyfile"}
|
||||
{"level":"warn","ts":1751739278.710495,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"Caddyfile","line":2}
|
||||
{"level":"info","ts":1751739278.7137249,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//127.0.0.1:2019"]}
|
||||
{"level":"info","ts":1751739278.713872,"logger":"http.auto_https","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443}
|
||||
{"level":"info","ts":1751739278.7138822,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
|
||||
{"level":"info","ts":1751739278.714001,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0x140005ff300"}
|
||||
{"level":"info","ts":1751739278.714232,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
|
||||
{"level":"info","ts":1751739278.714817,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
|
||||
{"level":"warn","ts":1751739278.714853,"logger":"http","msg":"HTTP/2 skipped because it requires TLS","network":"tcp","addr":":8080"}
|
||||
{"level":"warn","ts":1751739278.7148569,"logger":"http","msg":"HTTP/3 skipped because it requires TLS","network":"tcp","addr":":8080"}
|
||||
{"level":"info","ts":1751739278.714859,"logger":"http.log","msg":"server running","name":"srv1","protocols":["h1","h2","h3"]}
|
||||
{"level":"warn","ts":1751739278.7148852,"logger":"http","msg":"HTTP/2 skipped because it requires TLS","network":"tcp","addr":":80"}
|
||||
{"level":"warn","ts":1751739278.7148879,"logger":"http","msg":"HTTP/3 skipped because it requires TLS","network":"tcp","addr":":80"}
|
||||
{"level":"info","ts":1751739278.714891,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
|
||||
{"level":"info","ts":1751739278.714894,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["colinknapp.com"]}
|
||||
{"level":"info","ts":1751739278.715245,"msg":"autosaved config (load with --resume flag)","file":"/Users/computerpro/Library/Application Support/Caddy/autosave.json"}
|
||||
{"level":"info","ts":1751739278.7152488,"msg":"serving initial configuration"}
|
||||
{"level":"info","ts":1751739278.722911,"logger":"tls.obtain","msg":"acquiring lock","identifier":"colinknapp.com"}
|
||||
{"level":"info","ts":1751739278.7230961,"logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/Users/computerpro/Library/Application Support/Caddy","instance":"bb1987a6-f2f6-4230-a2aa-e3b16b9f988e","try_again":1751825678.7230961,"try_again_in":86399.999999833}
|
||||
{"level":"info","ts":1751739278.723574,"logger":"tls","msg":"finished cleaning storage units"}
|
||||
{"level":"info","ts":1751739278.727818,"logger":"tls.obtain","msg":"lock acquired","identifier":"colinknapp.com"}
|
||||
{"level":"info","ts":1751739278.72785,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"colinknapp.com"}
|
||||
{"level":"info","ts":1751739278.729119,"logger":"http","msg":"waiting on internal rate limiter","identifiers":["colinknapp.com"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}
|
||||
{"level":"info","ts":1751739278.729126,"logger":"http","msg":"done waiting on internal rate limiter","identifiers":["colinknapp.com"],"ca":"https://acme-v02.api.letsencrypt.org/directory","account":""}
|
||||
{"level":"info","ts":1751739278.729134,"logger":"http","msg":"using ACME account","account_id":"https://acme-v02.api.letsencrypt.org/acme/acct/2509915601","account_contact":[]}
|
||||
{"level":"info","ts":1751739279.476387,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}
|
||||
{"level":"error","ts":1751739280.142849,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739280.142943,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"order":"https://acme-v02.api.letsencrypt.org/acme/order/2509915601/403011757641","attempt":1,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739281.218956,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"colinknapp.com","issuer":"acme-v02.api.letsencrypt.org-directory","error":"HTTP 429 urn:ietf:params:acme:error:rateLimited - too many failed authorizations (5) for \"colinknapp.com\" in the last 1h0m0s, retry after 2025-07-05 18:26:22 UTC: see https://letsencrypt.org/docs/rate-limits/#authorization-failures-per-hostname-per-account"}
|
||||
{"level":"error","ts":1751739281.2190888,"logger":"tls.obtain","msg":"will retry","error":"[colinknapp.com] Obtain: [colinknapp.com] creating new order: attempt 1: https://acme-v02.api.letsencrypt.org/acme/new-order: HTTP 429 urn:ietf:params:acme:error:rateLimited - too many failed authorizations (5) for \"colinknapp.com\" in the last 1h0m0s, retry after 2025-07-05 18:26:22 UTC: see https://letsencrypt.org/docs/rate-limits/#authorization-failures-per-hostname-per-account (ca=https://acme-v02.api.letsencrypt.org/directory)","attempt":1,"retrying_in":60,"elapsed":2.491265416,"max_duration":2592000}
|
||||
{"level":"info","ts":1751739292.2583349,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57078","client_ip":"::1","proto":"HTTP/1.1","method":"HEAD","host":"localhost:8080","uri":"/stories/open-source-success.html","headers":{"Accept":["*/*"],"User-Agent":["curl/8.7.1"]}},"bytes_read":0,"user_id":"","duration":0.003503292,"size":0,"status":200,"resp_headers":{"Referrer-Policy":["strict-origin-when-cross-origin"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Accept-Ranges":["bytes"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Etag":["\"db4bqupxyvmr9pd\""],"Cross-Origin-Resource-Policy":["same-origin"],"Content-Length":["12577"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"Vary":["Accept-Encoding"],"Content-Type":["text/html; charset=utf-8"]}}
|
||||
{"level":"info","ts":1751739341.2217758,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"colinknapp.com"}
|
||||
{"level":"info","ts":1751739341.2275689,"logger":"http","msg":"using ACME account","account_id":"https://acme-staging-v02.api.letsencrypt.org/acme/acct/210713203","account_contact":[]}
|
||||
{"level":"info","ts":1751739341.618192,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"http-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
|
||||
{"level":"error","ts":1751739342.312726,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"http-01","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/9OE0nikzS9fkyPjN-_qTWq8gGI4tAL5YNSht0zwiHWU: 404","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739342.3132029,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/9OE0nikzS9fkyPjN-_qTWq8gGI4tAL5YNSht0zwiHWU: 404","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840385953","attempt":1,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"info","ts":1751739343.446597,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
|
||||
{"level":"error","ts":1751739344.154269,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739344.1546292,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840386333","attempt":2,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739344.155127,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"colinknapp.com","issuer":"acme-v02.api.letsencrypt.org-directory","error":"HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name"}
|
||||
{"level":"error","ts":1751739344.155275,"logger":"tls.obtain","msg":"will retry","error":"[colinknapp.com] Obtain: [colinknapp.com] solving challenge: colinknapp.com: [colinknapp.com] authorization failed: HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name (ca=https://acme-staging-v02.api.letsencrypt.org/directory)","attempt":2,"retrying_in":120,"elapsed":65.42754925,"max_duration":2592000}
|
||||
{"level":"info","ts":1751739464.158327,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"colinknapp.com"}
|
||||
{"level":"info","ts":1751739464.163527,"logger":"http","msg":"using ACME account","account_id":"https://acme-staging-v02.api.letsencrypt.org/acme/acct/210713203","account_contact":[]}
|
||||
{"level":"info","ts":1751739464.347884,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"http-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
|
||||
{"level":"error","ts":1751739465.0466619,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"http-01","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/dTh_lQhbTgg9J_PW4LPviVSbTIv0f_Rb4rwIU9woF5A: 404","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739465.046904,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/dTh_lQhbTgg9J_PW4LPviVSbTIv0f_Rb4rwIU9woF5A: 404","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840417013","attempt":1,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"info","ts":1751739466.273396,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
|
||||
{"level":"error","ts":1751739466.958394,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739466.9587898,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840417363","attempt":2,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739466.9591172,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"colinknapp.com","issuer":"acme-v02.api.letsencrypt.org-directory","error":"HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name"}
|
||||
{"level":"error","ts":1751739466.959261,"logger":"tls.obtain","msg":"will retry","error":"[colinknapp.com] Obtain: [colinknapp.com] solving challenge: colinknapp.com: [colinknapp.com] authorization failed: HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name (ca=https://acme-staging-v02.api.letsencrypt.org/directory)","attempt":3,"retrying_in":120,"elapsed":188.231714583,"max_duration":2592000}
|
||||
{"level":"info","ts":1751739586.962073,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"colinknapp.com"}
|
||||
{"level":"info","ts":1751739586.96677,"logger":"http","msg":"using ACME account","account_id":"https://acme-staging-v02.api.letsencrypt.org/acme/acct/210713203","account_contact":[]}
|
||||
{"level":"info","ts":1751739587.159206,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"http-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
|
||||
{"level":"error","ts":1751739587.860424,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"http-01","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/hKt6trbDmfbC05PqWuSSWcIDbCiNKb7eDfiiolzfohQ: 404","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739587.860965,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/hKt6trbDmfbC05PqWuSSWcIDbCiNKb7eDfiiolzfohQ: 404","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840446493","attempt":1,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"info","ts":1751739588.98678,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
|
||||
{"level":"error","ts":1751739589.68759,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739589.688067,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840447093","attempt":2,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739589.688373,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"colinknapp.com","issuer":"acme-v02.api.letsencrypt.org-directory","error":"HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name"}
|
||||
{"level":"error","ts":1751739589.68851,"logger":"tls.obtain","msg":"will retry","error":"[colinknapp.com] Obtain: [colinknapp.com] solving challenge: colinknapp.com: [colinknapp.com] authorization failed: HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name (ca=https://acme-staging-v02.api.letsencrypt.org/directory)","attempt":4,"retrying_in":300,"elapsed":310.961173125,"max_duration":2592000}
|
||||
{"level":"info","ts":1751739673.5404959,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57250","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.html","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"If-None-Match":["\"db4bbbwr4oyk2n1-gzip\""],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"],"Priority":["u=0, i"],"Sec-Fetch-Site":["same-origin"],"If-Modified-Since":["Sat, 05 Jul 2025 17:54:12 GMT"],"Referer":["http://localhost:8080/"],"Sec-Fetch-User":["?1"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Mode":["navigate"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Dnt":["1"],"Connection":["keep-alive"]}},"bytes_read":0,"user_id":"","duration":0.015525791,"size":1665,"status":200,"resp_headers":{"Cross-Origin-Resource-Policy":["same-origin"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Vary":["Accept-Encoding"],"Content-Type":["text/html; charset=utf-8"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Etag":["\"db4bqunxd6yo3gd-gzip\""],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Content-Encoding":["gzip"]}}
|
||||
{"level":"info","ts":1751739675.868175,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57250","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.html","headers":{"Sec-Fetch-Site":["same-origin"],"If-Modified-Since":["Sat, 05 Jul 2025 18:14:29 GMT"],"Sec-Fetch-Dest":["document"],"Connection":["keep-alive"],"If-None-Match":["\"db4bqunxd6yo3gd-gzip\""],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["navigate"],"Accept-Language":["en-US,en;q=0.5"],"Priority":["u=0, i"],"Sec-Fetch-User":["?1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Upgrade-Insecure-Requests":["1"],"Referer":["http://localhost:8080/"],"Dnt":["1"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"]}},"bytes_read":0,"user_id":"","duration":0.000579666,"size":0,"status":304,"resp_headers":{"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Frame-Options":["DENY"],"Vary":["Accept-Encoding"],"Etag":["\"db4bqunxd6yo3gd\""],"Cross-Origin-Embedder-Policy":["require-corp"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"],"Cross-Origin-Opener-Policy":["same-origin"]}}
|
||||
{"level":"info","ts":1751739677.53351,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57250","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.html","headers":{"Connection":["keep-alive"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-User":["?1"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept-Encoding":["gzip, deflate, br, zstd"],"If-None-Match":["\"db4bqunxd6yo3gd-gzip\""],"Referer":["http://localhost:8080/"],"Dnt":["1"],"Upgrade-Insecure-Requests":["1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Priority":["u=0, i"],"Accept-Language":["en-US,en;q=0.5"],"Sec-Fetch-Site":["same-origin"],"If-Modified-Since":["Sat, 05 Jul 2025 18:14:29 GMT"]}},"bytes_read":0,"user_id":"","duration":0.001523959,"size":0,"status":304,"resp_headers":{"Vary":["Accept-Encoding"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"],"Etag":["\"db4bqunxd6yo3gd\""],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"]}}
|
||||
{"level":"info","ts":1751739678.708853,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57251","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.html","headers":{"Connection":["keep-alive"],"Upgrade-Insecure-Requests":["1"],"Priority":["u=0, i"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-User":["?1"],"Cache-Control":["no-cache"],"Referer":["http://localhost:8080/"],"Dnt":["1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-origin"],"Pragma":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.004137583,"size":1665,"status":200,"resp_headers":{"Content-Encoding":["gzip"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"X-Content-Type-Options":["nosniff"],"Etag":["\"db4bqunxd6yo3gd-gzip\""],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Cross-Origin-Resource-Policy":["same-origin"],"Content-Type":["text/html; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Frame-Options":["DENY"],"Vary":["Accept-Encoding"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"]}}
|
||||
{"level":"info","ts":1751739678.819917,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57251","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/styles.css","headers":{"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept":["text/css,*/*;q=0.1"],"Priority":["u=2"],"Pragma":["no-cache"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Sec-Fetch-Mode":["no-cors"],"Cache-Control":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Dnt":["1"],"Accept-Language":["en-US,en;q=0.5"],"Connection":["keep-alive"],"Sec-Fetch-Dest":["style"],"Sec-Fetch-Site":["same-origin"]}},"bytes_read":0,"user_id":"","duration":0.003274375,"size":1594,"status":200,"resp_headers":{"Cross-Origin-Embedder-Policy":["require-corp"],"Vary":["Accept-Encoding"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Content-Type-Options":["nosniff"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Content-Encoding":["gzip"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Frame-Options":["DENY"],"Cross-Origin-Opener-Policy":["same-origin"],"Last-Modified":["Sat, 05 Jul 2025 18:02:46 GMT"],"Cache-Control":["public, max-age=31536000, immutable"],"Etag":["\"db4bhvy0bbjy4iu-gzip\""],"Content-Type":["text/css; charset=utf-8"],"Referrer-Policy":["strict-origin-when-cross-origin"]}}
|
||||
{"level":"info","ts":1751739678.821028,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57253","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/theme.js","headers":{"Sec-Fetch-Site":["same-origin"],"Pragma":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Dnt":["1"],"Priority":["u=2"],"Sec-Fetch-Mode":["no-cors"],"Accept-Language":["en-US,en;q=0.5"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Sec-Fetch-Dest":["script"],"Cache-Control":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept":["*/*"],"Connection":["keep-alive"]}},"bytes_read":0,"user_id":"","duration":0.003416792,"size":678,"status":200,"resp_headers":{"Content-Type":["text/javascript; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Vary":["Accept-Encoding"],"Etag":["\"daerkqziy4dn1is-gzip\""],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Content-Encoding":["gzip"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"],"Cross-Origin-Opener-Policy":["same-origin"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"]}}
|
||||
{"level":"info","ts":1751739678.821409,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57255","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.js","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["script"],"Pragma":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept":["*/*"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Sec-Fetch-Mode":["no-cors"],"Dnt":["1"],"Connection":["keep-alive"],"Sec-Fetch-Site":["same-origin"],"Cache-Control":["no-cache"],"Accept-Language":["en-US,en;q=0.5"]}},"bytes_read":0,"user_id":"","duration":0.00490175,"size":1984,"status":200,"resp_headers":{"X-Content-Type-Options":["nosniff"],"Content-Type":["text/javascript; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Vary":["Accept-Encoding"],"Last-Modified":["Sat, 05 Jul 2025 17:31:10 GMT"],"Content-Encoding":["gzip"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Frame-Options":["DENY"],"Etag":["\"db4atop72u2i4ye-gzip\""],"Referrer-Policy":["strict-origin-when-cross-origin"]}}
|
||||
{"level":"info","ts":1751739678.824568,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57252","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/tool-styles.css","headers":{"Priority":["u=2"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Dnt":["1"],"Accept":["text/css,*/*;q=0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Cache-Control":["no-cache"],"Connection":["keep-alive"],"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Site":["same-origin"],"Accept-Language":["en-US,en;q=0.5"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Sec-Fetch-Dest":["style"]}},"bytes_read":0,"user_id":"","duration":0.003773125,"size":1469,"status":200,"resp_headers":{"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Cross-Origin-Opener-Policy":["same-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Etag":["\"db4b2xr4l0jf44o-gzip\""],"Last-Modified":["Sat, 05 Jul 2025 17:43:15 GMT"],"Cross-Origin-Resource-Policy":["same-origin"],"Cross-Origin-Embedder-Policy":["require-corp"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Vary":["Accept-Encoding"],"Content-Type":["text/css; charset=utf-8"],"Content-Encoding":["gzip"]}}
|
||||
{"level":"info","ts":1751739678.8256042,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57254","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/utils.js","headers":{"Pragma":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Dnt":["1"],"Accept-Language":["en-US,en;q=0.5"],"Sec-Fetch-Dest":["script"],"Priority":["u=2"],"Cache-Control":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Connection":["keep-alive"],"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Site":["same-origin"],"Accept":["*/*"]}},"bytes_read":0,"user_id":"","duration":0.005408458,"size":775,"status":200,"resp_headers":{"Content-Type":["text/javascript; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Resource-Policy":["same-origin"],"Etag":["\"daerkqzizlwo1eq-gzip\""],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Vary":["Accept-Encoding"],"X-Frame-Options":["DENY"],"Content-Encoding":["gzip"],"Cross-Origin-Opener-Policy":["same-origin"],"Referrer-Policy":["strict-origin-when-cross-origin"],"X-Content-Type-Options":["nosniff"]}}
|
||||
{"level":"info","ts":1751739678.857478,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57255","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/favicon.ico","headers":{"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Accept":["image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5"],"Accept-Language":["en-US,en;q=0.5"],"Dnt":["1"],"Sec-Fetch-Site":["same-origin"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["no-cors"],"Priority":["u=6"],"Connection":["keep-alive"],"Sec-Fetch-Dest":["image"],"Pragma":["no-cache"],"Cache-Control":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.000408458,"size":1,"status":200,"resp_headers":{"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Type":["image/x-icon"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Length":["1"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Vary":["Accept-Encoding"],"Etag":["\"daerkqzimk0j1\""],"Accept-Ranges":["bytes"],"Cross-Origin-Embedder-Policy":["require-corp"],"X-Frame-Options":["DENY"]}}
|
||||
{"level":"info","ts":1751739679.9043121,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57256","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.html","headers":{"Cache-Control":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Dnt":["1"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-User":["?1"],"Sec-Fetch-Dest":["document"],"Referer":["http://localhost:8080/"],"Priority":["u=0, i"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.5"],"Connection":["keep-alive"],"Upgrade-Insecure-Requests":["1"],"Pragma":["no-cache"],"Sec-Fetch-Site":["same-origin"]}},"bytes_read":0,"user_id":"","duration":0.000926833,"size":1665,"status":200,"resp_headers":{"Content-Encoding":["gzip"],"Cross-Origin-Opener-Policy":["same-origin"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"],"Vary":["Accept-Encoding"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Embedder-Policy":["require-corp"],"Etag":["\"db4bqunxd6yo3gd-gzip\""],"Content-Type":["text/html; charset=utf-8"],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"X-Frame-Options":["DENY"]}}
|
||||
{"level":"info","ts":1751739679.972051,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57256","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/styles.css","headers":{"Dnt":["1"],"Sec-Fetch-Dest":["style"],"Sec-Fetch-Mode":["no-cors"],"Pragma":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Cache-Control":["no-cache"],"Accept-Language":["en-US,en;q=0.5"],"Sec-Fetch-Site":["same-origin"],"Accept":["text/css,*/*;q=0.1"],"Connection":["keep-alive"],"Priority":["u=2"]}},"bytes_read":0,"user_id":"","duration":0.000524958,"size":1594,"status":200,"resp_headers":{"Etag":["\"db4bhvy0bbjy4iu-gzip\""],"Cross-Origin-Resource-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Last-Modified":["Sat, 05 Jul 2025 18:02:46 GMT"],"X-Frame-Options":["DENY"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"Vary":["Accept-Encoding"],"Content-Type":["text/css; charset=utf-8"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Encoding":["gzip"],"Cross-Origin-Embedder-Policy":["require-corp"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"]}}
|
||||
{"level":"info","ts":1751739679.972645,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57257","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/tool-styles.css","headers":{"Pragma":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Dnt":["1"],"Sec-Fetch-Dest":["style"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Connection":["keep-alive"],"Priority":["u=2"],"Cache-Control":["no-cache"],"Accept":["text/css,*/*;q=0.1"],"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Site":["same-origin"]}},"bytes_read":0,"user_id":"","duration":0.000355708,"size":1469,"status":200,"resp_headers":{"Vary":["Accept-Encoding"],"Content-Encoding":["gzip"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"X-Content-Type-Options":["nosniff"],"Content-Type":["text/css; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Etag":["\"db4b2xr4l0jf44o-gzip\""],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Cross-Origin-Resource-Policy":["same-origin"],"Last-Modified":["Sat, 05 Jul 2025 17:43:15 GMT"],"X-Frame-Options":["DENY"]}}
|
||||
{"level":"info","ts":1751739679.972853,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57258","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/theme.js","headers":{"Accept-Language":["en-US,en;q=0.5"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Dnt":["1"],"Sec-Fetch-Dest":["script"],"Cache-Control":["no-cache"],"Connection":["keep-alive"],"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Site":["same-origin"],"Priority":["u=2"],"Pragma":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept":["*/*"]}},"bytes_read":0,"user_id":"","duration":0.000476167,"size":678,"status":200,"resp_headers":{"X-Frame-Options":["DENY"],"Cache-Control":["public, max-age=31536000, immutable"],"Etag":["\"daerkqziy4dn1is-gzip\""],"Cross-Origin-Resource-Policy":["same-origin"],"Cross-Origin-Embedder-Policy":["require-corp"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Content-Encoding":["gzip"],"X-Content-Type-Options":["nosniff"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Vary":["Accept-Encoding"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Type":["text/javascript; charset=utf-8"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"]}}
|
||||
{"level":"info","ts":1751739679.973341,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57260","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/one-pager-tools/csv-tool.js","headers":{"Accept-Language":["en-US,en;q=0.5"],"Dnt":["1"],"Pragma":["no-cache"],"Accept":["*/*"],"Sec-Fetch-Dest":["script"],"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Site":["same-origin"],"Cache-Control":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Connection":["keep-alive"]}},"bytes_read":0,"user_id":"","duration":0.000869166,"size":1984,"status":200,"resp_headers":{"Cross-Origin-Embedder-Policy":["require-corp"],"Content-Type":["text/javascript; charset=utf-8"],"Last-Modified":["Sat, 05 Jul 2025 17:31:10 GMT"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Encoding":["gzip"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"],"Cross-Origin-Opener-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Vary":["Accept-Encoding"],"Etag":["\"db4atop72u2i4ye-gzip\""],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"]}}
|
||||
{"level":"info","ts":1751739679.973473,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57259","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/utils.js","headers":{"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept-Language":["en-US,en;q=0.5"],"Dnt":["1"],"Sec-Fetch-Dest":["script"],"Priority":["u=2"],"Accept":["*/*"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Connection":["keep-alive"],"Sec-Fetch-Site":["same-origin"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["no-cors"],"Pragma":["no-cache"],"Cache-Control":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.000989667,"size":775,"status":200,"resp_headers":{"Vary":["Accept-Encoding"],"X-Frame-Options":["DENY"],"Content-Encoding":["gzip"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Etag":["\"daerkqzizlwo1eq-gzip\""],"Cross-Origin-Opener-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"],"Content-Type":["text/javascript; charset=utf-8"],"Referrer-Policy":["strict-origin-when-cross-origin"]}}
|
||||
{"level":"info","ts":1751739679.999177,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"57260","client_ip":"127.0.0.1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/favicon.ico","headers":{"Connection":["keep-alive"],"Sec-Fetch-Dest":["image"],"Priority":["u=6"],"Accept-Language":["en-US,en;q=0.5"],"Referer":["http://localhost:8080/one-pager-tools/csv-tool.html"],"Sec-Fetch-Site":["same-origin"],"Pragma":["no-cache"],"Cache-Control":["no-cache"],"Accept":["image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5"],"Sec-Fetch-Mode":["no-cors"],"Dnt":["1"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.000280666,"size":1,"status":200,"resp_headers":{"Content-Type":["image/x-icon"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"Vary":["Accept-Encoding"],"Accept-Ranges":["bytes"],"Content-Length":["1"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Etag":["\"daerkqzimk0j1\""],"Cross-Origin-Opener-Policy":["same-origin"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cross-Origin-Resource-Policy":["same-origin"],"Cross-Origin-Embedder-Policy":["require-corp"]}}
|
||||
{"level":"info","ts":1751739720.884835,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57287","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/","headers":{"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"If-None-Match":["\"db4b159qb1xccqw-gzip\""],"Accept-Language":["en-US,en;q=0.9"],"Sec-Fetch-Dest":["document"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Site":["none"],"Sec-Ch-Ua-Mobile":["?0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Accept-Encoding":["gzip, deflate, br, zstd"],"If-Modified-Since":["Sat, 05 Jul 2025 17:40:54 GMT"],"Cache-Control":["max-age=0"],"Connection":["keep-alive"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-User":["?1"]}},"bytes_read":0,"user_id":"","duration":0.006238541,"size":5186,"status":200,"resp_headers":{"X-Content-Type-Options":["nosniff"],"Cross-Origin-Opener-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Etag":["\"db4bquo9k394d1r-gzip\""],"Content-Encoding":["gzip"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Frame-Options":["DENY"],"Cross-Origin-Embedder-Policy":["require-corp"],"Vary":["Accept-Encoding"],"Content-Type":["text/html; charset=utf-8"]}}
|
||||
{"level":"info","ts":1751739721.273846,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57287","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/","headers":{"Upgrade-Insecure-Requests":["1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Sec-Fetch-User":["?1"],"Connection":["keep-alive"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Accept-Language":["en-US,en;q=0.9"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Dest":["document"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Sec-Fetch-Site":["none"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.000861208,"size":5186,"status":200,"resp_headers":{"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Content-Encoding":["gzip"],"Cross-Origin-Embedder-Policy":["require-corp"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Vary":["Accept-Encoding"],"Etag":["\"db4bquo9k394d1r-gzip\""],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Opener-Policy":["same-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"Content-Type":["text/html; charset=utf-8"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"]}}
|
||||
{"level":"info","ts":1751739721.283733,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57287","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/styles.css","headers":{"Sec-Fetch-Site":["same-origin"],"Connection":["keep-alive"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Accept":["text/css,*/*;q=0.1"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Accept-Language":["en-US,en;q=0.9"],"Cache-Control":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["style"],"Referer":["http://localhost:8080/"],"Sec-Fetch-Mode":["no-cors"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.002707125,"size":1594,"status":200,"resp_headers":{"Cross-Origin-Embedder-Policy":["require-corp"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Content-Type-Options":["nosniff"],"Vary":["Accept-Encoding"],"Etag":["\"db4bhvy0bbjy4iu-gzip\""],"Content-Type":["text/css; charset=utf-8"],"Last-Modified":["Sat, 05 Jul 2025 18:02:46 GMT"],"Cross-Origin-Opener-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Frame-Options":["DENY"],"Content-Encoding":["gzip"],"Cross-Origin-Resource-Policy":["same-origin"]}}
|
||||
{"level":"info","ts":1751739721.2837389,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57288","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/theme.js","headers":{"Accept":["*/*"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Sec-Fetch-Mode":["no-cors"],"Referer":["http://localhost:8080/"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Accept-Language":["en-US,en;q=0.9"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["script"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-origin"],"Cache-Control":["no-cache"],"Connection":["keep-alive"]}},"bytes_read":0,"user_id":"","duration":0.002064334,"size":678,"status":200,"resp_headers":{"Cross-Origin-Resource-Policy":["same-origin"],"X-Frame-Options":["DENY"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Content-Type":["text/javascript; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Etag":["\"daerkqziy4dn1is-gzip\""],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Content-Type-Options":["nosniff"],"Vary":["Accept-Encoding"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Content-Encoding":["gzip"],"Cross-Origin-Opener-Policy":["same-origin"],"Referrer-Policy":["strict-origin-when-cross-origin"]}}
|
||||
{"level":"info","ts":1751739721.32471,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57288","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/favicon.ico","headers":{"Pragma":["no-cache"],"Sec-Fetch-Site":["same-origin"],"Referer":["http://localhost:8080/"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Connection":["keep-alive"],"Accept-Language":["en-US,en;q=0.9"],"Accept-Encoding":["gzip, deflate, br, zstd"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Fetch-Mode":["no-cors"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Sec-Ch-Ua-Mobile":["?0"],"Accept":["image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"],"Sec-Fetch-Dest":["image"]}},"bytes_read":0,"user_id":"","duration":0.000416791,"size":1,"status":200,"resp_headers":{"Accept-Ranges":["bytes"],"X-Content-Type-Options":["nosniff"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Vary":["Accept-Encoding"],"Etag":["\"daerkqzimk0j1\""],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"X-Frame-Options":["DENY"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Length":["1"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cache-Control":["public, max-age=31536000, immutable"],"Cross-Origin-Resource-Policy":["same-origin"],"Content-Type":["image/x-icon"]}}
|
||||
{"level":"info","ts":1751739723.5364192,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57291","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/","headers":{"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Sec-Fetch-Mode":["navigate"],"Pragma":["no-cache"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Sec-Fetch-User":["?1"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["document"],"Accept-Language":["en-US,en;q=0.9"],"Connection":["keep-alive"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Site":["none"]}},"bytes_read":0,"user_id":"","duration":0.006212709,"size":5186,"status":200,"resp_headers":{"Etag":["\"db4bquo9k394d1r-gzip\""],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Cache-Control":["public, max-age=31536000, immutable"],"Vary":["Accept-Encoding"],"Content-Type":["text/html; charset=utf-8"],"Cross-Origin-Resource-Policy":["same-origin"],"Content-Encoding":["gzip"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"]}}
|
||||
{"level":"info","ts":1751739723.549154,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57292","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/theme.js","headers":{"Sec-Ch-Ua-Platform":["\"macOS\""],"Sec-Fetch-Site":["same-origin"],"Referer":["http://localhost:8080/"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Accept":["*/*"],"Sec-Fetch-Mode":["no-cors"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["script"],"Pragma":["no-cache"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-US,en;q=0.9"],"Connection":["keep-alive"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.000694583,"size":678,"status":200,"resp_headers":{"Cross-Origin-Embedder-Policy":["require-corp"],"Vary":["Accept-Encoding"],"Cache-Control":["public, max-age=31536000, immutable"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Content-Encoding":["gzip"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Frame-Options":["DENY"],"Etag":["\"daerkqziy4dn1is-gzip\""],"Content-Type":["text/javascript; charset=utf-8"],"Cross-Origin-Opener-Policy":["same-origin"],"X-Content-Type-Options":["nosniff"]}}
|
||||
{"level":"info","ts":1751739723.549674,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57291","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/styles.css","headers":{"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Sec-Fetch-Mode":["no-cors"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Accept":["text/css,*/*;q=0.1"],"Referer":["http://localhost:8080/"],"Cache-Control":["no-cache"],"Sec-Fetch-Dest":["style"],"Accept-Language":["en-US,en;q=0.9"],"Connection":["keep-alive"],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Fetch-Site":["same-origin"]}},"bytes_read":0,"user_id":"","duration":0.000732458,"size":1594,"status":200,"resp_headers":{"Cross-Origin-Embedder-Policy":["require-corp"],"Content-Type":["text/css; charset=utf-8"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Frame-Options":["DENY"],"Etag":["\"db4bhvy0bbjy4iu-gzip\""],"Content-Encoding":["gzip"],"X-Content-Type-Options":["nosniff"],"Vary":["Accept-Encoding"],"Last-Modified":["Sat, 05 Jul 2025 18:02:46 GMT"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"]}}
|
||||
{"level":"info","ts":1751739723.565779,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57291","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/favicon.ico","headers":{"Referer":["http://localhost:8080/"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept":["image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"],"Sec-Fetch-Site":["same-origin"],"Sec-Fetch-Dest":["image"],"Sec-Ch-Ua-Mobile":["?0"],"Connection":["keep-alive"],"Pragma":["no-cache"],"Sec-Fetch-Mode":["no-cors"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Accept-Language":["en-US,en;q=0.9"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""]}},"bytes_read":0,"user_id":"","duration":0.000751042,"size":1,"status":200,"resp_headers":{"Content-Length":["1"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"Cross-Origin-Resource-Policy":["same-origin"],"X-Frame-Options":["DENY"],"Content-Type":["image/x-icon"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Cross-Origin-Opener-Policy":["same-origin"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Content-Type-Options":["nosniff"],"Cross-Origin-Embedder-Policy":["require-corp"],"Etag":["\"daerkqzimk0j1\""],"Vary":["Accept-Encoding"],"Last-Modified":["Thu, 05 Jun 2025 17:09:29 GMT"],"Accept-Ranges":["bytes"]}}
|
||||
{"level":"info","ts":1751739726.127846,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57291","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/stories/","headers":{"Sec-Fetch-Mode":["navigate"],"Accept-Language":["en-US,en;q=0.9"],"Connection":["keep-alive"],"Sec-Ch-Ua-Platform":["\"macOS\""],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Site":["same-origin"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Sec-Fetch-Dest":["document"],"Referer":["http://localhost:8080/"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Sec-Ch-Ua-Mobile":["?0"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Fetch-User":["?1"]}},"bytes_read":0,"user_id":"","duration":0.00319225,"size":1987,"status":200,"resp_headers":{"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Content-Type-Options":["nosniff"],"Vary":["Accept-Encoding"],"Etag":["\"db4bqupjqm554sq-gzip\""],"Content-Type":["text/html; charset=utf-8"],"Cross-Origin-Embedder-Policy":["require-corp"],"Cross-Origin-Opener-Policy":["same-origin"],"Content-Encoding":["gzip"],"Cross-Origin-Resource-Policy":["same-origin"],"Last-Modified":["Sat, 05 Jul 2025 18:14:29 GMT"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Cache-Control":["public, max-age=31536000, immutable"],"X-Frame-Options":["DENY"]}}
|
||||
{"level":"info","ts":1751739726.1533198,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"::1","remote_port":"57291","client_ip":"::1","proto":"HTTP/1.1","method":"GET","host":"localhost:8080","uri":"/stories/stories.css","headers":{"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Dest":["style"],"Referer":["http://localhost:8080/stories/"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\""],"Accept":["text/css,*/*;q=0.1"],"Sec-Fetch-Site":["same-origin"],"Accept-Language":["en-US,en;q=0.9"],"Connection":["keep-alive"],"Sec-Ch-Ua-Platform":["\"macOS\""]}},"bytes_read":0,"user_id":"","duration":0.0008405,"size":1226,"status":200,"resp_headers":{"Content-Encoding":["gzip"],"Cross-Origin-Opener-Policy":["same-origin"],"Cross-Origin-Embedder-Policy":["require-corp"],"Vary":["Accept-Encoding"],"Content-Type":["text/css; charset=utf-8"],"Content-Security-Policy":["default-src 'none'; script-src 'self' 'sha256-nyj4rRbjhJDz0uLB5qgDfrvwLYUJSg9YWnHihpFA8rk=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-/v9rQU2KOOA3o+QIcyY9sJYiY6V+pBRixwWCntlYMz8=' 'sha256-Y37imeLroP7YzkjRmiLxoP6J3M6m1v6nek3IpWObGZI=' 'sha256-9SeZBz89VCv28LEByMlQjCC8UalqDwwjouZGacpHeJk='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["DENY"],"Cross-Origin-Resource-Policy":["same-origin"],"Permissions-Policy":["accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"],"Last-Modified":["Sat, 05 Jul 2025 18:14:20 GMT"],"Cache-Control":["public, max-age=31536000, immutable"],"Etag":["\"db4bqqjbe6x53bg-gzip\""],"Referrer-Policy":["strict-origin-when-cross-origin"]}}
|
||||
{"level":"info","ts":1751739889.693784,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"colinknapp.com"}
|
||||
{"level":"info","ts":1751739889.7038321,"logger":"http","msg":"using ACME account","account_id":"https://acme-staging-v02.api.letsencrypt.org/acme/acct/210713203","account_contact":[]}
|
||||
{"level":"info","ts":1751739890.085554,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"http-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
|
||||
{"level":"error","ts":1751739890.770806,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"http-01","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/DWUN2FubCp8-Utbvg1U3sQzEN9ohjNM7v505ji-DDLI: 404","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739890.773333,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:unauthorized","title":"","detail":"2604:a880:cad:d0::ef5:1001: Invalid response from http://colinknapp.com/.well-known/acme-challenge/DWUN2FubCp8-Utbvg1U3sQzEN9ohjNM7v505ji-DDLI: 404","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840531423","attempt":1,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"info","ts":1751739891.900807,"msg":"trying to solve challenge","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
|
||||
{"level":"error","ts":1751739892.895112,"msg":"challenge failed","identifier":"colinknapp.com","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"stacktrace":"github.com/mholt/acmez/v3.(*Client).pollAuthorization\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:557\ngithub.com/mholt/acmez/v3.(*Client).solveChallenges\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:378\ngithub.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:136\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739892.895329,"msg":"validating authorization","identifier":"colinknapp.com","problem":{"type":"urn:ietf:params:acme:error:tls","title":"","detail":"138.197.167.216: remote error: tls: unrecognized name","instance":"","subproblems":null},"order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/210713203/25840531943","attempt":2,"max_attempts":3,"stacktrace":"github.com/mholt/acmez/v3.(*Client).ObtainCertificate\n\tgithub.com/mholt/acmez/v3@v3.1.2/client.go:152\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).doIssue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:489\ngithub.com/caddyserver/certmagic.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/certmagic@v0.23.0/acmeissuer.go:382\ngithub.com/caddyserver/caddy/v2/modules/caddytls.(*ACMEIssuer).Issue\n\tgithub.com/caddyserver/caddy/v2@v2.10.0/modules/caddytls/acmeissuer.go:288\ngithub.com/caddyserver/certmagic.(*Config).obtainCert.func2\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:626\ngithub.com/caddyserver/certmagic.doWithRetry\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:104\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:700\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:505\ngithub.com/caddyserver/certmagic.(*Config).manageOne.func1\n\tgithub.com/caddyserver/certmagic@v0.23.0/config.go:415\ngithub.com/caddyserver/certmagic.(*jobManager).worker\n\tgithub.com/caddyserver/certmagic@v0.23.0/async.go:73"}
|
||||
{"level":"error","ts":1751739892.895437,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"colinknapp.com","issuer":"acme-v02.api.letsencrypt.org-directory","error":"HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name"}
|
||||
{"level":"error","ts":1751739892.895788,"logger":"tls.obtain","msg":"will retry","error":"[colinknapp.com] Obtain: [colinknapp.com] solving challenge: colinknapp.com: [colinknapp.com] authorization failed: HTTP 400 urn:ietf:params:acme:error:tls - 138.197.167.216: remote error: tls: unrecognized name (ca=https://acme-staging-v02.api.letsencrypt.org/directory)","attempt":5,"retrying_in":600,"elapsed":614.168914,"max_duration":2592000}
|
||||
{"level":"info","ts":1751739910.3035798,"msg":"shutting down apps, then terminating","signal":"SIGTERM"}
|
||||
{"level":"warn","ts":1751739910.3039591,"msg":"exiting; byeee!! 👋","signal":"SIGTERM"}
|
||||
{"level":"info","ts":1751739910.3044071,"logger":"http","msg":"servers shutting down with eternal grace period"}
|
||||
{"level":"info","ts":1751739910.3074849,"logger":"tls.obtain","msg":"releasing lock","identifier":"colinknapp.com"}
|
||||
{"level":"error","ts":1751739910.310326,"msg":"unable to clean up lock in storage backend","signal":"SIGTERM","storage":"FileStorage:/Users/computerpro/Library/Application Support/Caddy","lock_key":"issue_cert_colinknapp.com","error":"remove /Users/computerpro/Library/Application Support/Caddy/locks/issue_cert_colinknapp.com.lock: no such file or directory"}
|
||||
{"level":"info","ts":1751739910.310804,"logger":"admin","msg":"stopped previous server","address":"localhost:2019"}
|
||||
{"level":"info","ts":1751739910.310834,"msg":"shutdown complete","signal":"SIGTERM","exit_code":0}
|
|
@ -0,0 +1,58 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# caddy.sh - Simple script to start Caddy server
|
||||
# =====================================================================
|
||||
# Usage:
|
||||
# ./caddy.sh - Start/restart the Caddy server
|
||||
#
|
||||
# This script handles all Caddy server operations from the correct directory
|
||||
# =====================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# Ensure we're in the correct directory (where this script is located)
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Stop any existing Caddy processes
|
||||
echo "=== Stopping Caddy Server ==="
|
||||
killall caddy 2>/dev/null || true
|
||||
pkill -f "caddy run" 2>/dev/null || true
|
||||
sleep 1
|
||||
echo "All Caddy processes stopped."
|
||||
|
||||
# Update CSP hashes
|
||||
echo "=== Updating CSP Hashes ==="
|
||||
# Run the update-csp-hashes.sh script
|
||||
if [ -f "./update-csp-hashes.sh" ]; then
|
||||
./update-csp-hashes.sh
|
||||
else
|
||||
echo "WARNING: update-csp-hashes.sh not found. Skipping CSP hash updates."
|
||||
fi
|
||||
|
||||
# Start Caddy
|
||||
echo "=== Starting Caddy Server ==="
|
||||
echo "Working directory: $(pwd)"
|
||||
|
||||
# Check if Caddyfile.local exists
|
||||
if [ ! -f "Caddyfile.local" ]; then
|
||||
echo "ERROR: Caddyfile.local not found in $(pwd)"
|
||||
echo "Please ensure you're running this script from the directory containing Caddyfile.local"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Launch Caddy
|
||||
echo "Starting Caddy with Caddyfile.local..."
|
||||
caddy run --config Caddyfile.local &
|
||||
|
||||
# Wait a moment to check if Caddy started successfully
|
||||
sleep 2
|
||||
if ! pgrep -f "caddy run" > /dev/null; then
|
||||
echo "ERROR: Caddy failed to start. Check the error messages above."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Caddy Server Started Successfully ==="
|
||||
echo "Local server running at: http://localhost:8080"
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* Script to help convert existing HTML files to use the includes system
|
||||
*
|
||||
* Usage:
|
||||
* 1. Include this script in your HTML file temporarily
|
||||
* 2. Open the page in a browser
|
||||
* 3. Open the browser console
|
||||
* 4. The script will output the modified HTML content that uses includes
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Extract the head content
|
||||
const headContent = document.head.innerHTML;
|
||||
|
||||
// Extract the navigation content
|
||||
const navContent = document.querySelector('.main-nav')?.outerHTML || '';
|
||||
|
||||
// Extract the theme switcher content
|
||||
const themeSwitcherContent = document.querySelector('.theme-switch')?.outerHTML || '';
|
||||
|
||||
// Extract the footer content (accessibility notice)
|
||||
const footerContent = document.querySelector('.accessibility-notice')?.parentElement?.innerHTML || '';
|
||||
|
||||
// Extract the main content
|
||||
const mainContentElement = document.querySelector('.container-fluid');
|
||||
let mainContent = '';
|
||||
|
||||
if (mainContentElement) {
|
||||
// Clone the main content to avoid modifying the original
|
||||
const mainContentClone = mainContentElement.cloneNode(true);
|
||||
|
||||
// Remove the accessibility notice from the clone if it exists
|
||||
const accessibilityNotice = mainContentClone.querySelector('.accessibility-notice');
|
||||
if (accessibilityNotice && accessibilityNotice.parentElement) {
|
||||
accessibilityNotice.parentElement.removeChild(accessibilityNotice);
|
||||
}
|
||||
|
||||
mainContent = mainContentClone.innerHTML;
|
||||
}
|
||||
|
||||
// Create the modified HTML content
|
||||
const modifiedHTML = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
${headContent}
|
||||
<script src="${getRelativePath()}includes.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
${mainContent}
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
// Output the modified HTML content to the console
|
||||
console.log('=== MODIFIED HTML CONTENT ===');
|
||||
console.log(modifiedHTML);
|
||||
console.log('=== END MODIFIED HTML CONTENT ===');
|
||||
|
||||
// Helper function to get the relative path to the root
|
||||
function getRelativePath() {
|
||||
const path = window.location.pathname;
|
||||
const depth = (path.match(/\//g) || []).length - 1;
|
||||
return depth > 0 ? '../'.repeat(depth) : '';
|
||||
}
|
||||
});
|
|
@ -0,0 +1,113 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - CSV Processing Tool">
|
||||
<title>CSV Viewer - Colin Knapp</title>
|
||||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css">
|
||||
<link rel="stylesheet" href="tool-styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
|
||||
<link rel="stylesheet" href="csv-tool-fix.css?v=2" integrity="sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
|
||||
<script src="../utils.js" integrity="sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544="></script>
|
||||
<style>
|
||||
/* Additional inline styles to fix layout */
|
||||
.container-fluid {
|
||||
max-width: 100%;
|
||||
padding: 0 15px;
|
||||
}
|
||||
.tool-container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
.form-group.full-width {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
#csvInput {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* More aggressive fixes for textarea */
|
||||
textarea#csvInput {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 !important;
|
||||
padding: 12px !important;
|
||||
font-family: 'Courier New', monospace !important;
|
||||
}
|
||||
|
||||
/* Fix container width */
|
||||
body {
|
||||
max-width: 100% !important;
|
||||
padding: 20px !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<div class="container-fluid" role="main">
|
||||
<h1>CSV Viewer</h1>
|
||||
<p>Simply paste CSV data below to view it as a formatted table.</p>
|
||||
|
||||
<div class="tool-container">
|
||||
<div class="tool-controls">
|
||||
<h3>Paste CSV Data</h3>
|
||||
<div class="form-group full-width">
|
||||
<textarea id="csvInput" class="form-control" rows="15" placeholder="Paste your CSV data here to automatically view it as a table..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="delimiter">Delimiter:</label>
|
||||
<select id="delimiter" class="form-control">
|
||||
<option value="," selected>Comma (,)</option>
|
||||
<option value=";">Semicolon (;)</option>
|
||||
<option value="\t">Tab</option>
|
||||
<option value="|">Pipe (|)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="hasHeader">First row is header:</label>
|
||||
<input type="checkbox" id="hasHeader" checked>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tool-output" id="output">
|
||||
<h3>Output</h3>
|
||||
<p class="alert alert-info">Paste CSV data above to view it as a table.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>About This Tool</h2>
|
||||
<p>This CSV Viewer allows you to:</p>
|
||||
<ul>
|
||||
<li>Paste and preview CSV data directly in your browser</li>
|
||||
<li>Automatically view your data in a table format</li>
|
||||
<li>Sort columns by clicking on column headers</li>
|
||||
</ul>
|
||||
<p>The tool processes everything in your browser - no data is sent to any server.</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
|
||||
<!-- Load PapaParse first (local version) -->
|
||||
<script src="../papaparse.min.js" integrity="sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc="></script>
|
||||
<!-- Then load our script -->
|
||||
<script src="csv-tool.js?v=3" integrity="sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI="></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* Includes.js - Handles the inclusion of header and footer files
|
||||
* and applies the correct active states to navigation items
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Function to include HTML content
|
||||
async function includeHTML(elementId, filePath, callback) {
|
||||
try {
|
||||
const response = await fetch(filePath);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load ${filePath}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const content = await response.text();
|
||||
document.getElementById(elementId).innerHTML = content;
|
||||
if (callback) callback();
|
||||
} catch (error) {
|
||||
console.error('Error including HTML:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to set active navigation item
|
||||
function setActiveNavItem() {
|
||||
const currentPath = window.location.pathname;
|
||||
|
||||
// Wait for the navigation to be loaded
|
||||
setTimeout(() => {
|
||||
// Remove all active classes first
|
||||
document.querySelectorAll('.main-nav a').forEach(link => {
|
||||
link.classList.remove('active');
|
||||
});
|
||||
|
||||
// Set active class based on current path
|
||||
if (currentPath === '/' || currentPath === '/index.html') {
|
||||
const portfolioLink = document.getElementById('nav-portfolio');
|
||||
if (portfolioLink) portfolioLink.classList.add('active');
|
||||
} else if (currentPath.includes('/stories/')) {
|
||||
const storiesLink = document.getElementById('nav-stories');
|
||||
if (storiesLink) storiesLink.classList.add('active');
|
||||
|
||||
// Check for specific story pages
|
||||
if (currentPath.includes('viperwire.html')) {
|
||||
const link = document.getElementById('nav-viperwire');
|
||||
if (link) link.classList.add('active');
|
||||
} else if (currentPath.includes('fawe-plotsquared.html')) {
|
||||
const link = document.getElementById('nav-fawe');
|
||||
if (link) link.classList.add('active');
|
||||
} else if (currentPath.includes('healthcare-platform.html')) {
|
||||
const link = document.getElementById('nav-healthcare');
|
||||
if (link) link.classList.add('active');
|
||||
} else if (currentPath.includes('wordpress-security.html')) {
|
||||
const link = document.getElementById('nav-wordpress');
|
||||
if (link) link.classList.add('active');
|
||||
} else if (currentPath.includes('airport-dns.html')) {
|
||||
const link = document.getElementById('nav-airport');
|
||||
if (link) link.classList.add('active');
|
||||
} else if (currentPath.includes('nitric-leadership.html')) {
|
||||
const link = document.getElementById('nav-nitric');
|
||||
if (link) link.classList.add('active');
|
||||
} else if (currentPath.includes('open-source-success.html')) {
|
||||
const link = document.getElementById('nav-opensource');
|
||||
if (link) link.classList.add('active');
|
||||
}
|
||||
} else if (currentPath.includes('/one-pager-tools/')) {
|
||||
const toolsLink = document.getElementById('nav-tools');
|
||||
if (toolsLink) toolsLink.classList.add('active');
|
||||
|
||||
// Check for specific tool pages
|
||||
if (currentPath.includes('csv-tool.html')) {
|
||||
const link = document.getElementById('nav-csv');
|
||||
if (link) link.classList.add('active');
|
||||
}
|
||||
}
|
||||
}, 100); // Small delay to ensure the DOM is updated
|
||||
}
|
||||
|
||||
// Process header and footer placeholders
|
||||
const headerElement = document.getElementById('header-include');
|
||||
const footerElement = document.getElementById('footer-include');
|
||||
|
||||
if (headerElement) {
|
||||
includeHTML('header-include', '/includes/header.html', setActiveNavItem);
|
||||
}
|
||||
|
||||
if (footerElement) {
|
||||
includeHTML('footer-include', '/includes/footer.html');
|
||||
}
|
||||
});
|
|
@ -0,0 +1,83 @@
|
|||
# Implementation Guide: Converting to the Includes System
|
||||
|
||||
This guide explains how to convert the existing portfolio website to use the includes system for headers and footers.
|
||||
|
||||
## What We've Created
|
||||
|
||||
1. **Header and Footer Templates**
|
||||
- `includes/header.html`: Contains the common header elements
|
||||
- `includes/footer.html`: Contains the common footer elements
|
||||
|
||||
2. **JavaScript for Includes**
|
||||
- `includes.js`: Handles the inclusion of header and footer files and applies the correct active states to navigation items
|
||||
|
||||
3. **Example Files**
|
||||
- `template-with-includes.html`: Basic template
|
||||
- `index-with-includes.html`: Example of the index page
|
||||
- `stories/story-with-includes.html`: Example of a story page
|
||||
- `one-pager-tools/tool-with-includes.html`: Example of a tool page
|
||||
|
||||
4. **Conversion Helper**
|
||||
- `convert-to-includes.js`: A script to help convert existing HTML files to use the includes system
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### 1. Update the CSP in Caddyfile and Caddyfile.local
|
||||
|
||||
Add the includes.js script hash to the Content-Security-Policy:
|
||||
|
||||
```
|
||||
Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-HASH_FOR_INCLUDES_JS' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; ...
|
||||
```
|
||||
|
||||
### 2. Convert Existing Pages
|
||||
|
||||
For each HTML page:
|
||||
|
||||
1. Add the includes.js script to the head section:
|
||||
```html
|
||||
<script src="../includes.js"></script>
|
||||
```
|
||||
(Adjust the path as needed based on the location of the HTML file)
|
||||
|
||||
2. Replace the header content (everything from the opening `<body>` tag to the opening `<div class="container-fluid">` tag) with:
|
||||
```html
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
```
|
||||
|
||||
3. Replace the footer content (everything from the closing `</div>` of the main container to the closing `</body>` tag) with:
|
||||
```html
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
```
|
||||
|
||||
You can use the `convert-to-includes.js` script to help with this process:
|
||||
|
||||
1. Temporarily add this script to an existing page:
|
||||
```html
|
||||
<script src="convert-to-includes.js"></script>
|
||||
```
|
||||
2. Open the page in a browser and check the console output
|
||||
3. Use the generated HTML as a starting point for your conversion
|
||||
|
||||
### 3. Testing
|
||||
|
||||
After converting each page:
|
||||
|
||||
1. Test the page in a browser to ensure it loads correctly
|
||||
2. Verify that the navigation active states work as expected
|
||||
3. Check that all CSS and JavaScript files are loaded correctly
|
||||
|
||||
## Benefits
|
||||
|
||||
- **Easier Maintenance**: Changes to the header or footer only need to be made in one place
|
||||
- **Consistency**: All pages will have the same header and footer structure
|
||||
- **Reduced File Size**: Each HTML file will be smaller since the common elements are externalized
|
||||
- **Improved Developer Experience**: Easier to focus on the unique content of each page
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- **Dynamic Meta Tags**: Enhance the includes system to support dynamic meta tags and titles
|
||||
- **Page-Specific CSS/JS**: Add support for page-specific CSS and JavaScript files
|
||||
- **Breadcrumbs**: Implement a breadcrumb system that works with the includes system
|
|
@ -0,0 +1,81 @@
|
|||
# HTML Includes System
|
||||
|
||||
This system allows for separating headers and footers into external HTML files that can be included in all individual pages, making maintenance easier and ensuring consistency across the site.
|
||||
|
||||
## Files
|
||||
|
||||
- `header.html`: Contains the common header elements for all pages
|
||||
- `footer.html`: Contains the common footer elements for all pages
|
||||
- `includes.js`: JavaScript file that handles the inclusion of header and footer files and applies the correct active states to navigation items
|
||||
|
||||
## How to Use
|
||||
|
||||
### 1. Include the JavaScript
|
||||
|
||||
Add the `includes.js` script to your HTML file:
|
||||
|
||||
```html
|
||||
<script src="../includes.js"></script>
|
||||
```
|
||||
|
||||
(Adjust the path as needed based on the location of your HTML file)
|
||||
|
||||
### 2. Add Include Placeholders
|
||||
|
||||
Add placeholder divs where you want the header and footer to be included:
|
||||
|
||||
```html
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<!-- Your page content here -->
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
```
|
||||
|
||||
### 3. Example Structure
|
||||
|
||||
Here's a basic template for a page using includes:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Your description here">
|
||||
<title>Your Title - Colin Knapp</title>
|
||||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css">
|
||||
<!-- Additional CSS files as needed -->
|
||||
<script src="../theme.js"></script>
|
||||
<script src="../includes.js"></script>
|
||||
<!-- Additional JS files as needed -->
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<h1>Your Page Title</h1>
|
||||
<p>Your page content...</p>
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Navigation Active States
|
||||
|
||||
The `includes.js` file automatically sets the active state for navigation items based on the current page. The navigation items in `header.html` have IDs that are used to identify which item should be active.
|
||||
|
||||
## Example Files
|
||||
|
||||
See the following example files that demonstrate how to use the includes system:
|
||||
|
||||
- `/template-with-includes.html`: Basic template
|
||||
- `/index-with-includes.html`: Example of the index page
|
||||
- `/stories/story-with-includes.html`: Example of a story page
|
||||
- `/one-pager-tools/tool-with-includes.html`: Example of a tool page
|
|
@ -0,0 +1,6 @@
|
|||
<hr>
|
||||
|
||||
<p class="accessibility-notice"><strong>Accessibility:</strong> This website is designed and developed to meet WCAG 2.1 Level AAA standards, ensuring the highest level of accessibility for all users. Features include high contrast ratios, keyboard navigation, screen reader compatibility, and responsive design. The site supports both light and dark modes with automatic system preference detection.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,36 @@
|
|||
<div class="theme-switch">
|
||||
<button
|
||||
id="themeToggle"
|
||||
aria-label="Theme mode: Auto"
|
||||
role="switch"
|
||||
aria-checked="false"
|
||||
title="Toggle between light, dark, and auto theme modes"
|
||||
tabindex="0"
|
||||
>🌓</button>
|
||||
</div>
|
||||
|
||||
<nav class="main-nav">
|
||||
<ul>
|
||||
<li><a href="/" id="nav-portfolio">Portfolio</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="/stories/" id="nav-stories">Stories</a>
|
||||
<div class="dropdown-content">
|
||||
<a href="/stories/viperwire.html" id="nav-viperwire">ViperWire</a>
|
||||
<a href="/stories/fawe-plotsquared.html" id="nav-fawe">FastAsyncWorldEdit</a>
|
||||
<a href="/stories/healthcare-platform.html" id="nav-healthcare">Healthcare Platform</a>
|
||||
<a href="/stories/wordpress-security.html" id="nav-wordpress">WordPress Security</a>
|
||||
<a href="/stories/airport-dns.html" id="nav-airport">Airport DNS</a>
|
||||
<a href="/stories/nitric-leadership.html" id="nav-nitric">NitricConcepts</a>
|
||||
<a href="/stories/open-source-success.html" id="nav-opensource">Open Source Success</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="/one-pager-tools/csv-tool.html" id="nav-tools">Tools</a>
|
||||
<div class="dropdown-content">
|
||||
<a href="/one-pager-tools/csv-tool.html" id="nav-csv">CSV Tool</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid" role="main">
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - Portfolio">
|
||||
<title>Colin Knapp - Portfolio</title>
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
|
||||
<script src="theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
|
||||
</head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<h1>Colin Knapp</h1>
|
||||
<p><strong>Location:</strong> Kitchener-Waterloo, Ontario, Canada<br>
|
||||
<strong>Contact:</strong> <a href="mailto:recruitme2025@colinknapp.com">recruitme2025@colinknapp.com</a> | <a href="https://colinknapp.com">colinknapp.com</a><br>
|
||||
<strong>Schedule a Meeting:</strong> <a href="https://cal.com/colink/30min" target="_blank">30 Minute Meeting</a></p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Highlights & Measurables</h2>
|
||||
<ul>
|
||||
<li><strong>Cybersecurity Leadership:</strong> Currently spearheading <em><a href="http://ViperWire.ca">ViperWire.ca</a></em>, the public-facing arm of my AI-powered cybersecurity and development consultancy, delivering cutting-edge protection for digital assets (2023-Present).</li>
|
||||
<li><strong>Open-Source Impact:</strong> Co-created <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, revolutionizing Minecraft development by enabling massive transformative edits—scaling from 50,000 server-crashing edits to billions without interruption—powering a $2 billion game brand with global contributor support (2014-Present).</li>
|
||||
<!-- Additional list items would go here -->
|
||||
</ul>
|
||||
|
||||
<!-- Additional content would go here -->
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -3,31 +3,18 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - Cybersecurity Expert and Software Developer Portfolio">
|
||||
<title>Colin Knapp Portfolio</title>
|
||||
<meta name="description" content="Colin Knapp - Portfolio">
|
||||
<title>Colin Knapp - Portfolio</title>
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="styles.css" integrity="sha256-Ps1dklCHzk1leTAfqkeA64YDuDJxx5QZBjC2UQhSdz0=" crossorigin="anonymous">
|
||||
<script src="theme.js" integrity="sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=" crossorigin="anonymous"></script>
|
||||
<script src="pdf-download.js" integrity="sha256-BASkmAmg7eoYCMd6odA6kQ8yGsFnoxaX48WbQvMkehs=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
|
||||
<script src="theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
|
||||
</head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
|
||||
<body>
|
||||
<div class="theme-switch">
|
||||
<button
|
||||
id="themeToggle"
|
||||
aria-label="Theme mode: Auto"
|
||||
role="switch"
|
||||
aria-checked="false"
|
||||
title="Toggle between light, dark, and auto theme modes"
|
||||
tabindex="0"
|
||||
>🌓</button>
|
||||
<button
|
||||
id="downloadPDF"
|
||||
aria-label="Download as PDF"
|
||||
title="Download resume as PDF"
|
||||
tabindex="0"
|
||||
>📄</button>
|
||||
</div>
|
||||
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<div class="container-fluid" role="main">
|
||||
<h1>Colin Knapp</h1>
|
||||
<p><strong>Location:</strong> Kitchener-Waterloo, Ontario, Canada<br>
|
||||
|
@ -102,7 +89,7 @@
|
|||
<li>Utilized Jenkins and GitLab CI/CD for streamlined workflows, leveraging a robust toolchain for rapid development.</li>
|
||||
<li>Managed complex systems and ensured WCAG 2.0 AA accessibility standards.</li>
|
||||
<li>Provided technical guidance and detailed client documentation, drawing on broad experience to resolve diverse issues.<br>
|
||||
<strong>Impact:</strong> Enhanced project delivery speed and quality for diverse computing environments through prolific development practices.</li>
|
||||
<strong>Impact:</strong> Enhanced project delivery speed and quality for diverse computing environments through prolific and efficient development practices.</li>
|
||||
</ul>
|
||||
|
||||
<h3>App Development for Influencers</h3>
|
||||
|
@ -203,5 +190,8 @@
|
|||
|
||||
<p class="accessibility-notice"><strong>Accessibility:</strong> This website is designed and developed to meet WCAG 2.1 Level AAA standards, ensuring the highest level of accessibility for all users. Features include high contrast ratios, keyboard navigation, screen reader compatibility, and responsive design. The site supports both light and dark modes with automatic system preference detection.</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -1,207 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - Cybersecurity Expert and Software Developer Portfolio">
|
||||
<title>Colin Knapp Portfolio</title>
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="styles.css" integrity="sha256-Ps1dklCHzk1leTAfqkeA64YDuDJxx5QZBjC2UQhSdz0=" crossorigin="anonymous">
|
||||
<script src="theme.js" integrity="sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=" crossorigin="anonymous"></script>
|
||||
<script src="pdf-download.js" integrity="sha256-BASkmAmg7eoYCMd6odA6kQ8yGsFnoxaX48WbQvMkehs=" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switch">
|
||||
<button
|
||||
id="themeToggle"
|
||||
aria-label="Theme mode: Auto"
|
||||
role="switch"
|
||||
aria-checked="false"
|
||||
title="Toggle between light, dark, and auto theme modes"
|
||||
tabindex="0"
|
||||
>🌓</button>
|
||||
<button
|
||||
id="downloadPDF"
|
||||
aria-label="Download as PDF"
|
||||
title="Download resume as PDF"
|
||||
tabindex="0"
|
||||
>📄</button>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid" role="main">
|
||||
<h1>Colin Knapp</h1>
|
||||
<p><strong>Location:</strong> Kitchener-Waterloo, Ontario, Canada<br>
|
||||
<strong>Contact:</strong> <a href="mailto:recruitme2025@colinknapp.com">recruitme2025@colinknapp.com</a> | <a href="https://colinknapp.com">colinknapp.com</a><br>
|
||||
<strong>Schedule a Meeting:</strong> <a href="https://cal.com/colink/30min" target="_blank">30 Minute Meeting</a></p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Highlights & Measurables</h2>
|
||||
<ul>
|
||||
<li><strong>Cybersecurity Leadership:</strong> Currently spearheading <em><a href="http://ViperWire.ca">ViperWire.ca</a></em>, the public-facing arm of my AI-powered cybersecurity and development consultancy, delivering cutting-edge protection for digital assets (2023-Present).</li>
|
||||
<li><strong>Open-Source Impact:</strong> Co-created <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, revolutionizing Minecraft development by enabling massive transformative edits—scaling from 50,000 server-crashing edits to billions without interruption—powering a $2 billion game brand with global contributor support (2014-Present).</li>
|
||||
<li><strong>Team Leadership:</strong> Managed a distributed team of 45 contractors at NitricConcepts, fostering collaboration and deploying advanced DevSecOps practices (2018-2021).</li>
|
||||
<li><strong>On-Premises Innovation:</strong> Architected self-managed, bare-metal infrastructure with orchestration for on-premises deployments, delivering performant, scalable systems compliant with WCAG 2.0 AA for clients like <a href="https://showerloop.cc">ShowerLoop</a>, meeting stringent government accessibility and compliance goals (2020-Present).</li>
|
||||
<li><strong>Government Projects:</strong> Delivered scalable, secure learning management systems for the US government and consulted on <a href="https://bishopairport.org">Flint Bishop International Airport</a>'s website and domain infrastructure via Addis Enterprises, building a geographically redundant DNS cluster with an A+ standard resilient to extreme scenarios (2019-Present).</li>
|
||||
<li><strong>Healthcare Infrastructure:</strong> Developed and deployed infrastructure for <a href="https://improvingmipractices.org">Improving MI Practices</a>, a critical healthcare education platform, ensuring high availability and security for sensitive medical training content (2023-Present).</li>
|
||||
<li><strong>Security Automation:</strong> Created a Docker-based utility for automated WordPress malware removal and hardening, successfully deployed to protect <a href="https://mlpp.org">MLPP</a> from persistent cyber attacks, reducing infection frequency from daily to zero (2023).</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Project Experience</h2>
|
||||
<h3>DevSecOps at Addis Enterprises</h3>
|
||||
<p><strong>Timeframe:</strong> 2019-Present<br>
|
||||
<strong>Overview:</strong> Collaborated on US government projects and airport infrastructure, focusing on scalable, secure systems and domain resilience.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Partnered with senior professionals to deliver learning management systems meeting WCAG 2.0 AA compliance for government clients.</li>
|
||||
<li>Consulted for Flint Bishop International Airport, architecting a geographically redundant DNS cluster achieving an A+ standard, capable of withstanding extreme disruptions.</li>
|
||||
<li>Provided exceptional client service through effective communication and tailored solutions.<br>
|
||||
<strong>Impact:</strong> Strengthened government digital infrastructure and ensured robust, resilient airport domain systems.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Healthcare Platform Infrastructure</h3>
|
||||
<p><strong>Timeframe:</strong> 2019-Present<br>
|
||||
<strong>Overview:</strong> Led infrastructure design and operations for <a href="https://improvingmipractices.org">Improving MI Practices</a> (<a href="https://archive.is/D5HIb">archive</a>) through Addis Enterprises, a critical healthcare education platform.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Designed and implemented secure, scalable infrastructure for healthcare education content delivery.</li>
|
||||
<li>Administered CIS Level 1 and 2 security standards implementation for enhanced system hardening and security controls.</li>
|
||||
<li>Implemented automated deployment pipelines and monitoring systems for high availability.<br>
|
||||
<strong>Impact:</strong> Enabled reliable delivery of critical healthcare training content to medical professionals while maintaining robust security standards.</li>
|
||||
</ul>
|
||||
|
||||
<h3>WordPress Security Automation</h3>
|
||||
<p><strong>Timeframe:</strong> 2023<br>
|
||||
<strong>Overview:</strong> Developed an automated solution for WordPress malware removal and hardening.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Created a Docker-based utility for automated malware detection and removal.</li>
|
||||
<li>Implemented hardening measures to prevent reinfection.</li>
|
||||
<li>Successfully deployed to protect MLPP from persistent cyber attacks.<br>
|
||||
<strong>Impact:</strong> Reduced infection frequency from daily/weekly to zero, significantly improving site security and reliability.</li>
|
||||
</ul>
|
||||
|
||||
<h3>YouTube Game Development & Cybersecurity</h3>
|
||||
<p><strong>Timeframe:</strong> 2009-2022<br>
|
||||
<strong>Overview:</strong> Designed custom video games for prominent online creators, integrating advanced cybersecurity measures.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Built immersive gaming experiences for large audiences.</li>
|
||||
<li>Implemented DDoS defense, anti-phishing protocols, and data privacy measures.</li>
|
||||
<li>Managed hardware/software lifecycles and created comprehensive documentation.<br>
|
||||
<strong>Impact:</strong> Delivered secure, seamless gaming experiences to millions of users.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Web Design & Java Plugin Development</h3>
|
||||
<p><strong>Timeframe:</strong> 2009-2023<br>
|
||||
<strong>Overview:</strong> Developed web solutions and Java plugins focusing on CI/CD efficiency and client satisfaction.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Utilized Jenkins and GitLab CI/CD for streamlined workflows, leveraging a robust toolchain for rapid development.</li>
|
||||
<li>Managed complex systems and ensured WCAG 2.0 AA accessibility standards.</li>
|
||||
<li>Provided technical guidance and detailed client documentation, drawing on broad experience to resolve diverse issues.<br>
|
||||
<strong>Impact:</strong> Enhanced project delivery speed and quality for diverse computing environments through prolific development practices.</li>
|
||||
</ul>
|
||||
|
||||
<h3>App Development for Influencers</h3>
|
||||
<p><strong>Timeframe:</strong> 2013-2018<br>
|
||||
<strong>Overview:</strong> Created an ad revenue tracking app to optimize earnings and strategies for content creators.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Designed a user-friendly tool for real-time revenue monitoring using an optimized toolchain for efficiency.</li>
|
||||
<li>Ensured secure data handling and system performance with extensive problem-solving expertise.<br>
|
||||
<strong>Impact:</strong> Empowered creators to maximize earnings and refine content strategies.</li>
|
||||
</ul>
|
||||
|
||||
<h3>DevOps & Co-Founder at NitricConcepts</h3>
|
||||
<p><strong>Timeframe:</strong> 2018-2021<br>
|
||||
<strong>Overview:</strong> Led a global team in building secure, scalable gaming solutions.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Managed 45 contractors worldwide, implementing Docker, Fail2Ban, and Salt Stack as part of a comprehensive toolchain.</li>
|
||||
<li>Co-developed <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, enabling billions of seamless edits for Minecraft creators.</li>
|
||||
<li>Fostered a collaborative, innovative team culture.<br>
|
||||
<strong>Impact:</strong> Transformed NitricConcepts into a thriving multinational entity through prolific and efficient development.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Entrepreneurial Ventures</h3>
|
||||
<h4><a href="http://Athion.net">Athion.net</a> Turnaround</h4>
|
||||
<p><strong>Timeframe:</strong> 2013-2017<br>
|
||||
<strong>Overview:</strong> Revitalized a struggling business into a self-sustaining operation in two weeks.<br>
|
||||
<strong>Key Contributions:</strong> Optimized systems and streamlined operations with rapid, effective solutions.<br>
|
||||
<strong>Impact:</strong> Created a profitable, independent venture.</p>
|
||||
|
||||
<h4><a href="http://MotherboardRepair.ca">MotherboardRepair.ca</a></h4>
|
||||
<p><strong>Timeframe:</strong> 2019-Present<br>
|
||||
<strong>Overview:</strong> Co-founded a company reducing e-waste through circuit board repairs.<br>
|
||||
<strong>Key Contributions:</strong> Leveraged industry expertise and a versatile toolchain for sustainable tech solutions.<br>
|
||||
<strong>Impact:</strong> Promoted environmental responsibility in electronics.</p>
|
||||
|
||||
<h4><a href="https://showerloop.cc">ShowerLoop Project</a></h4>
|
||||
<p><strong>Timeframe:</strong> 2016<br>
|
||||
<strong>Overview:</strong> Revamped the website for an eco-friendly recirculating shower system project, implementing WCAG 2.0 AA compliance and modern design principles.<br>
|
||||
<strong>Key Contributions:</strong> Designed and implemented a responsive, accessible website with improved user experience and technical documentation.<br>
|
||||
<strong>Impact:</strong> Enhanced the project's online presence and accessibility while maintaining the site's functionality through periodic maintenance.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Additional Information</h2>
|
||||
<h3>Personal Development</h3>
|
||||
<p><strong>Timeframe:</strong> 2009-Present</p>
|
||||
<ul>
|
||||
<li><strong>Self-Taught Mastery:</strong> Continuously honed cybersecurity and systems management skills, building a broad knowledge base to tackle unique challenges with a passion for innovation and problem-solving.</li>
|
||||
<li><strong>Open-Source Contributions:</strong> Actively maintain smaller self-run open-source projects; previously led <em>OhMyForm</em> (retired in favor of FormBricks) and contributed to <em>PlotSquared</em>, <em>FastAsyncWorldEdit</em>, and <em>PlotHider</em>, reflecting a prolific commitment to advancing technology.</li>
|
||||
<li><strong>Skill Maintenance:</strong> Regularly run Woodpecker CI and Gitea for on-premise source management, testing, and deployment, employing security scanning and unit testing to ensure core functionality and security baselines, alongside self-hosting exercises to sustain rapid, high-volume development capabilities across a vast array of innovative projects.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Relevant Links & Web Impact</h3>
|
||||
<ul>
|
||||
<li><strong>Repositories:</strong> <a href="https://github.com/IntellectualSites/PlotSquared">PlotSquared</a>, <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit">FastAsyncWorldEdit</a>, <a href="https://github.com/OhMyForm/OhMyForm">OhMyForm</a>, <a href="https://github.com/IntellectualSites/plothider">PlotHider</a></li>
|
||||
<li><strong>Projects:</strong> <a href="https://viperwire.ca">ViperWire.ca</a>, <a href="https://nitricconcepts.com">NitricConcepts</a>, <a href="https://showerloop.cc">ShowerLoop</a></li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="section" role="region" aria-labelledby="open-source-heading">
|
||||
<h2 id="open-source-heading">Open Source & Infrastructure</h2>
|
||||
<div class="entry">
|
||||
<h3>PlotSquared & FastAsyncWorldEdit</h3>
|
||||
<p class="date">2013-Present</p>
|
||||
<p class="overview">Contributor to major Minecraft server plugins, focusing on performance optimization and security enhancements.</p>
|
||||
<ul>
|
||||
<li>Contributed to <a href="https://github.com/IntellectualSites/PlotSquared" target="_blank">PlotSquared</a>, a land management plugin with 572+ stars and 809+ forks</li>
|
||||
<li>Enhanced <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit" target="_blank">FastAsyncWorldEdit</a>, improving world manipulation performance with 664+ stars</li>
|
||||
<li>Implemented security improvements and performance optimizations for large-scale server operations</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<h3>Athion.net Infrastructure</h3>
|
||||
<p class="date">2013-Present</p>
|
||||
<p class="overview">Established and maintained critical infrastructure for Minecraft development community.</p>
|
||||
<ul>
|
||||
<li>Set up and maintained <a href="https://ci.athion.net/" target="_blank">Jenkins CI/CD pipeline</a> since 2013, supporting continuous integration for game content development</li>
|
||||
<li>Hosted infrastructure enabling collaboration between developers and Microsoft for game content creation</li>
|
||||
<li>Implemented robust security measures and performance optimizations for high-traffic development environments</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="experience-item">
|
||||
<h3>Software Engineer</h3>
|
||||
<p class="company">Oh My Form</p>
|
||||
<p class="date">2020 - Present</p>
|
||||
<p class="achievement">Led development of Oh My Form, achieving over 1.5 million Docker pulls as verified by <a href="https://hub.docker.com/u/ohmyform" target="_blank" rel="noopener noreferrer">Docker Hub</a> and <a href="https://archive.is/lZHAT" target="_blank" rel="noopener noreferrer">archived</a>.</p>
|
||||
<ul>
|
||||
<li>Developed and maintained a secure, high-performance form builder application</li>
|
||||
<li>Implemented robust security measures and best practices</li>
|
||||
<li>Optimized application performance and user experience</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<p class="accessibility-notice"><strong>Accessibility:</strong> This website is designed and developed to meet WCAG 2.1 Level AAA standards, ensuring the highest level of accessibility for all users. Features include high contrast ratios, keyboard navigation, screen reader compatibility, and responsive design. The site supports both light and dark modes with automatic system preference detection.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,35 @@
|
|||
/* Additional inline styles to fix layout */
|
||||
.container-fluid {
|
||||
max-width: 100%;
|
||||
padding: 0 15px;
|
||||
}
|
||||
.tool-container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
.form-group.full-width {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
#csvInput {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* More aggressive fixes for textarea */
|
||||
textarea#csvInput {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 !important;
|
||||
padding: 12px !important;
|
||||
font-family: 'Courier New', monospace !important;
|
||||
}
|
||||
|
||||
/* Fix container width */
|
||||
body {
|
||||
max-width: 100% !important;
|
||||
padding: 20px !important;
|
|
@ -1,187 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - Cybersecurity Expert and Software Developer Portfolio">
|
||||
<title>Colin Knapp Portfolio</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="theme.js" integrity="sha384-naUim5JriO81FOtYsICLhgAY4mC9xffM8+Zcsc2ztUoQLKKLrDgLFgBRau98yEN1" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switch">
|
||||
<button
|
||||
id="themeToggle"
|
||||
aria-label="Theme mode: Auto"
|
||||
aria-pressed="false"
|
||||
role="switch"
|
||||
aria-controls="html"
|
||||
title="Toggle between light, dark, and auto theme modes"
|
||||
>🌓</button>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<h1>Colin Knapp</h1>
|
||||
<p><strong>Location:</strong> Kitchener-Waterloo, Ontario, Canada<br>
|
||||
<strong>Contact:</strong> <a href="mailto:recruitme2023@colinknapp.com">recruitme2023@colinknapp.com</a> | <a href="https://colinknapp.com">colinknapp.com</a></p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Highlights & Measurables</h2>
|
||||
<ul>
|
||||
<li><strong>Cybersecurity Leadership:</strong> Currently spearheading <em><a href="http://ViperWire.ca">ViperWire.ca</a></em>, the public-facing arm of my AI-powered cybersecurity and development consultancy, delivering cutting-edge protection for digital assets (2023-Present).</li>
|
||||
<li><strong>Open-Source Impact:</strong> Co-created <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, revolutionizing Minecraft development by enabling massive transformative edits—scaling from 50,000 server-crashing edits to billions without interruption—powering a $2 billion game brand with global contributor support (2014-Present).</li>
|
||||
<li><strong>Team Leadership:</strong> Managed a distributed team of 45 contractors at NitricConcepts, fostering collaboration and deploying advanced DevSecOps practices (2018-2021).</li>
|
||||
<li><strong>On-Premises Innovation:</strong> Architected self-managed, bare-metal infrastructure with orchestration for on-premises deployments, delivering performant, scalable systems compliant with WCAG 2.0 AA for clients like <a href="https://showerloop.cc">ShowerLoop</a>, meeting stringent government accessibility and compliance goals (2020-Present).</li>
|
||||
<li><strong>Government Projects:</strong> Delivered scalable, secure learning management systems for the US government and consulted on <a href="https://bishopairport.org">Flint Bishop International Airport</a>'s website and domain infrastructure via Addis Enterprises, building a geographically redundant DNS cluster with an A+ standard resilient to extreme scenarios (2019-Present).</li>
|
||||
<li><strong>Healthcare Infrastructure:</strong> Developed and deployed infrastructure for <a href="https://improvingmipractices.org">Improving MI Practices</a>, a critical healthcare education platform, ensuring high availability and security for sensitive medical training content (2023-Present).</li>
|
||||
<li><strong>Security Automation:</strong> Created a Docker-based utility for automated WordPress malware removal and hardening, successfully deployed to protect <a href="https://mlpp.org">MLPP</a> from persistent cyber attacks, reducing infection frequency from daily to zero (2023).</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Project Experience</h2>
|
||||
<h3>DevSecOps at Addis Enterprises</h3>
|
||||
<p><strong>Timeframe:</strong> 2019-Present<br>
|
||||
<strong>Overview:</strong> Collaborated on US government projects and airport infrastructure, focusing on scalable, secure systems and domain resilience.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Partnered with senior professionals to deliver learning management systems meeting WCAG 2.0 AA compliance for government clients.</li>
|
||||
<li>Consulted for Flint Bishop International Airport, architecting a geographically redundant DNS cluster achieving an A+ standard, capable of withstanding extreme disruptions.</li>
|
||||
<li>Provided exceptional client service through effective communication and tailored solutions.<br>
|
||||
<strong>Impact:</strong> Strengthened government digital infrastructure and ensured robust, resilient airport domain systems.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Healthcare Platform Infrastructure</h3>
|
||||
<p><strong>Timeframe:</strong> 2019-Present<br>
|
||||
<strong>Overview:</strong> Led infrastructure design and operations for <a href="https://improvingmipractices.org">Improving MI Practices</a> (<a href="https://archive.is/D5HIb">archive</a>) through Addis Enterprises, a critical healthcare education platform.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Designed and implemented secure, scalable infrastructure for healthcare education content delivery.</li>
|
||||
<li>Administered CIS Level 1 and 2 security standards implementation for enhanced system hardening and security controls.</li>
|
||||
<li>Implemented automated deployment pipelines and monitoring systems for high availability.<br>
|
||||
<strong>Impact:</strong> Enabled reliable delivery of critical healthcare training content to medical professionals while maintaining robust security standards.</li>
|
||||
</ul>
|
||||
|
||||
<h3>WordPress Security Automation</h3>
|
||||
<p><strong>Timeframe:</strong> 2023<br>
|
||||
<strong>Overview:</strong> Developed an automated solution for WordPress malware removal and hardening.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Created a Docker-based utility for automated malware detection and removal.</li>
|
||||
<li>Implemented hardening measures to prevent reinfection.</li>
|
||||
<li>Successfully deployed to protect MLPP from persistent cyber attacks.<br>
|
||||
<strong>Impact:</strong> Reduced infection frequency from daily/weekly to zero, significantly improving site security and reliability.</li>
|
||||
</ul>
|
||||
|
||||
<h3>YouTube Game Development & Cybersecurity</h3>
|
||||
<p><strong>Timeframe:</strong> 2009-2022<br>
|
||||
<strong>Overview:</strong> Designed custom video games for prominent online creators, integrating advanced cybersecurity measures.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Built immersive gaming experiences for large audiences.</li>
|
||||
<li>Implemented DDoS defense, anti-phishing protocols, and data privacy measures.</li>
|
||||
<li>Managed hardware/software lifecycles and created comprehensive documentation.<br>
|
||||
<strong>Impact:</strong> Delivered secure, seamless gaming experiences to millions of users.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Web Design & Java Plugin Development</h3>
|
||||
<p><strong>Timeframe:</strong> 2009-2023<br>
|
||||
<strong>Overview:</strong> Developed web solutions and Java plugins focusing on CI/CD efficiency and client satisfaction.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Utilized Jenkins and GitLab CI/CD for streamlined workflows, leveraging a robust toolchain for rapid development.</li>
|
||||
<li>Managed complex systems and ensured WCAG 2.0 AA accessibility standards.</li>
|
||||
<li>Provided technical guidance and detailed client documentation, drawing on broad experience to resolve diverse issues.<br>
|
||||
<strong>Impact:</strong> Enhanced project delivery speed and quality for diverse computing environments through prolific development practices.</li>
|
||||
</ul>
|
||||
|
||||
<h3>App Development for Influencers</h3>
|
||||
<p><strong>Timeframe:</strong> 2013-2018<br>
|
||||
<strong>Overview:</strong> Created an ad revenue tracking app to optimize earnings and strategies for content creators.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Designed a user-friendly tool for real-time revenue monitoring using an optimized toolchain for efficiency.</li>
|
||||
<li>Ensured secure data handling and system performance with extensive problem-solving expertise.<br>
|
||||
<strong>Impact:</strong> Empowered creators to maximize earnings and refine content strategies.</li>
|
||||
</ul>
|
||||
|
||||
<h3>DevOps & Co-Founder at NitricConcepts</h3>
|
||||
<p><strong>Timeframe:</strong> 2018-2021<br>
|
||||
<strong>Overview:</strong> Led a global team in building secure, scalable gaming solutions.<br>
|
||||
<strong>Key Contributions:</strong></p>
|
||||
<ul>
|
||||
<li>Managed 45 contractors worldwide, implementing Docker, Fail2Ban, and Salt Stack as part of a comprehensive toolchain.</li>
|
||||
<li>Co-developed <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, enabling billions of seamless edits for Minecraft creators.</li>
|
||||
<li>Fostered a collaborative, innovative team culture.<br>
|
||||
<strong>Impact:</strong> Transformed NitricConcepts into a thriving multinational entity through prolific and efficient development.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Entrepreneurial Ventures</h3>
|
||||
<h4><a href="http://Athion.net">Athion.net</a> Turnaround</h4>
|
||||
<p><strong>Timeframe:</strong> 2013-2017<br>
|
||||
<strong>Overview:</strong> Revitalized a struggling business into a self-sustaining operation in two weeks.<br>
|
||||
<strong>Key Contributions:</strong> Optimized systems and streamlined operations with rapid, effective solutions.<br>
|
||||
<strong>Impact:</strong> Created a profitable, independent venture.</p>
|
||||
|
||||
<h4><a href="http://MotherboardRepair.ca">MotherboardRepair.ca</a></h4>
|
||||
<p><strong>Timeframe:</strong> 2019-Present<br>
|
||||
<strong>Overview:</strong> Co-founded a company reducing e-waste through circuit board repairs.<br>
|
||||
<strong>Key Contributions:</strong> Leveraged industry expertise and a versatile toolchain for sustainable tech solutions.<br>
|
||||
<strong>Impact:</strong> Promoted environmental responsibility in electronics.</p>
|
||||
|
||||
<h4><a href="https://showerloop.cc">ShowerLoop Project</a></h4>
|
||||
<p><strong>Timeframe:</strong> 2016<br>
|
||||
<strong>Overview:</strong> Revamped the website for an eco-friendly recirculating shower system project, implementing WCAG 2.0 AA compliance and modern design principles.<br>
|
||||
<strong>Key Contributions:</strong> Designed and implemented a responsive, accessible website with improved user experience and technical documentation.<br>
|
||||
<strong>Impact:</strong> Enhanced the project's online presence and accessibility while maintaining the site's functionality through periodic maintenance.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Additional Information</h2>
|
||||
<h3>Personal Development</h3>
|
||||
<p><strong>Timeframe:</strong> 2009-Present</p>
|
||||
<ul>
|
||||
<li><strong>Self-Taught Mastery:</strong> Continuously honed cybersecurity and systems management skills, building a broad knowledge base to tackle unique challenges with a passion for innovation and problem-solving.</li>
|
||||
<li><strong>Open-Source Contributions:</strong> Actively maintain smaller self-run open-source projects; previously led <em>OhMyForm</em> (retired in favor of FormBricks) and contributed to <em>PlotSquared</em>, <em>FastAsyncWorldEdit</em>, and <em>PlotHider</em>, reflecting a prolific commitment to advancing technology.</li>
|
||||
<li><strong>Skill Maintenance:</strong> Regularly run Woodpecker CI and Gitea for on-premise source management, testing, and deployment, employing security scanning and unit testing to ensure core functionality and security baselines, alongside self-hosting exercises to sustain rapid, high-volume development capabilities across a vast array of innovative projects.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Relevant Links & Web Impact</h3>
|
||||
<ul>
|
||||
<li><strong>Repositories:</strong> <a href="https://github.com/IntellectualSites/PlotSquared">PlotSquared</a>, <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit">FastAsyncWorldEdit</a>, <a href="https://github.com/OhMyForm/OhMyForm">OhMyForm</a>, <a href="https://github.com/IntellectualSites/plothider">PlotHider</a></li>
|
||||
<li><strong>Projects:</strong> <a href="https://viperwire.ca">ViperWire.ca</a>, <a href="https://nitricconcepts.com">NitricConcepts</a>, <a href="https://showerloop.cc">ShowerLoop</a></li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="section">
|
||||
<h2>Open Source & Infrastructure</h2>
|
||||
<div class="entry">
|
||||
<h3>PlotSquared & FastAsyncWorldEdit</h3>
|
||||
<p class="date">2013-Present</p>
|
||||
<p class="overview">Contributor to major Minecraft server plugins, focusing on performance optimization and security enhancements.</p>
|
||||
<ul>
|
||||
<li>Contributed to <a href="https://github.com/IntellectualSites/PlotSquared" target="_blank">PlotSquared</a>, a land management plugin with 572+ stars and 809+ forks</li>
|
||||
<li>Enhanced <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit" target="_blank">FastAsyncWorldEdit</a>, improving world manipulation performance with 664+ stars</li>
|
||||
<li>Implemented security improvements and performance optimizations for large-scale server operations</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<h3>Athion.net Infrastructure</h3>
|
||||
<p class="date">2013-Present</p>
|
||||
<p class="overview">Established and maintained critical infrastructure for Minecraft development community.</p>
|
||||
<ul>
|
||||
<li>Set up and maintained <a href="https://ci.athion.net/" target="_blank">Jenkins CI/CD pipeline</a> since 2013, supporting continuous integration for game content development</li>
|
||||
<li>Hosted infrastructure enabling collaboration between developers and Microsoft for game content creation</li>
|
||||
<li>Implemented robust security measures and performance optimizations for high-traffic development environments</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<p class="accessibility-notice"><strong>Accessibility:</strong> This website is designed and developed to meet WCAG 2.1 Level AAA standards, ensuring the highest level of accessibility for all users. Features include high contrast ratios, keyboard navigation, screen reader compatibility, and responsive design. The site supports both light and dark modes with automatic system preference detection.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,132 @@
|
|||
/* CSV Tool specific styles with aggressive fixes */
|
||||
|
||||
/* Fix container width */
|
||||
body {
|
||||
max-width: 100% !important;
|
||||
padding: 20px !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
padding: 0 15px !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 auto !important;
|
||||
}
|
||||
|
||||
.tool-container {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 1.5rem !important;
|
||||
margin: 2rem 0 !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.tool-controls {
|
||||
background-color: var(--bg-secondary) !important;
|
||||
padding: 1.5rem !important;
|
||||
border-radius: 0.5rem !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 1rem !important;
|
||||
margin-bottom: 1.5rem !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
display: block !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* Aggressive fixes for textarea */
|
||||
#csvInput,
|
||||
textarea#csvInput {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 !important;
|
||||
padding: 12px !important;
|
||||
font-family: 'Courier New', monospace !important;
|
||||
min-height: 250px !important;
|
||||
white-space: pre !important;
|
||||
tab-size: 4 !important;
|
||||
-moz-tab-size: 4 !important;
|
||||
resize: vertical !important;
|
||||
overflow-x: auto !important;
|
||||
line-height: 1.5 !important;
|
||||
font-size: 14px !important;
|
||||
letter-spacing: -0.2px !important;
|
||||
border: 1px solid var(--border-color) !important;
|
||||
background-color: var(--bg-primary) !important;
|
||||
color: var(--text-primary) !important;
|
||||
border-radius: 0.25rem !important;
|
||||
}
|
||||
|
||||
/* Ensure the output area is also full width */
|
||||
.tool-output {
|
||||
background-color: var(--bg-secondary) !important;
|
||||
padding: 1.5rem !important;
|
||||
border-radius: 0.5rem !important;
|
||||
overflow-x: auto !important;
|
||||
margin-bottom: 1.5rem !important;
|
||||
width: 100% !important;
|
||||
border: 2px solid var(--accent-color) !important;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
|
||||
box-sizing: border-box !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
/* Fix table display */
|
||||
.table-responsive {
|
||||
overflow-x: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
margin-bottom: 2rem !important;
|
||||
border-radius: 0.25rem !important;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
|
||||
.tool-table {
|
||||
width: 100% !important;
|
||||
border-collapse: separate !important;
|
||||
border-spacing: 0 !important;
|
||||
margin-bottom: 1.5rem !important;
|
||||
text-align: left !important;
|
||||
border: 1px solid var(--border-color) !important;
|
||||
table-layout: auto !important;
|
||||
}
|
||||
|
||||
/* Fix form controls */
|
||||
.form-control {
|
||||
width: 100% !important;
|
||||
padding: 0.75rem !important;
|
||||
border: 1px solid var(--border-color) !important;
|
||||
background-color: var(--bg-primary) !important;
|
||||
color: var(--text-primary) !important;
|
||||
border-radius: 0.25rem !important;
|
||||
font-family: inherit !important;
|
||||
font-size: 1rem !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* Fix empty cell display */
|
||||
.empty-cell {
|
||||
color: #999 !important;
|
||||
font-style: italic !important;
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - CSV Processing Tool">
|
||||
<title>CSV Viewer - Colin Knapp</title>
|
||||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css">
|
||||
<link rel="stylesheet" href="tool-styles.css?v=2" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
|
||||
<link rel="stylesheet" href="csv-tool-fix.css?v=2" integrity="sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
|
||||
<script src="../utils.js" integrity="sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544="></script>
|
||||
<style>
|
||||
/* Additional inline styles to fix layout */
|
||||
.container-fluid {
|
||||
max-width: 100%;
|
||||
padding: 0 15px;
|
||||
}
|
||||
.tool-container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
.form-group.full-width {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
#csvInput {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* More aggressive fixes for textarea */
|
||||
textarea#csvInput {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 !important;
|
||||
padding: 12px !important;
|
||||
font-family: 'Courier New', monospace !important;
|
||||
}
|
||||
|
||||
/* Fix container width */
|
||||
body {
|
||||
max-width: 100% !important;
|
||||
padding: 20px !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<div class="container-fluid" role="main">
|
||||
<h1>CSV Viewer</h1>
|
||||
<p>Simply paste CSV data below to view it as a formatted table.</p>
|
||||
|
||||
<div class="tool-container">
|
||||
<div class="tool-controls">
|
||||
<h3>Paste CSV Data</h3>
|
||||
<div class="form-group full-width">
|
||||
<textarea id="csvInput" class="form-control" rows="15" placeholder="Paste your CSV data here to automatically view it as a table..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="delimiter">Delimiter:</label>
|
||||
<select id="delimiter" class="form-control">
|
||||
<option value="," selected>Comma (,)</option>
|
||||
<option value=";">Semicolon (;)</option>
|
||||
<option value="\t">Tab</option>
|
||||
<option value="|">Pipe (|)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="hasHeader">First row is header:</label>
|
||||
<input type="checkbox" id="hasHeader" checked>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tool-output" id="output">
|
||||
<h3>Output</h3>
|
||||
<p class="alert alert-info">Paste CSV data above to view it as a table.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>About This Tool</h2>
|
||||
<p>This CSV Viewer allows you to:</p>
|
||||
<ul>
|
||||
<li>Paste and preview CSV data directly in your browser</li>
|
||||
<li>Automatically view your data in a table format</li>
|
||||
<li>Sort columns by clicking on column headers</li>
|
||||
</ul>
|
||||
<p>The tool processes everything in your browser - no data is sent to any server.</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
|
||||
<!-- Load PapaParse first (local version) -->
|
||||
<script src="../papaparse.min.js" integrity="sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc="></script>
|
||||
<!-- Then load our script -->
|
||||
<script src="csv-tool.js?v=3" integrity="sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI="></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,329 @@
|
|||
/**
|
||||
* CSV Viewer functionality
|
||||
* Automatically processes and displays CSV data when pasted
|
||||
* Using Papa Parse for robust CSV handling
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// DOM Elements
|
||||
const csvInput = document.getElementById('csvInput');
|
||||
const delimiterSelect = document.getElementById('delimiter');
|
||||
const hasHeaderCheckbox = document.getElementById('hasHeader');
|
||||
const outputDiv = document.getElementById('output');
|
||||
|
||||
// Variables to store data
|
||||
let csvData = [];
|
||||
let headers = [];
|
||||
let currentSortColumn = null;
|
||||
let sortDirection = 1; // 1 for ascending, -1 for descending
|
||||
|
||||
// Add input event listener with debounce to process CSV when pasted
|
||||
let debounceTimer;
|
||||
csvInput.addEventListener('input', function() {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(function() {
|
||||
if (csvInput.value.trim() !== '') {
|
||||
processCSV();
|
||||
}
|
||||
}, 300); // 300ms debounce delay
|
||||
});
|
||||
|
||||
// Add paste event listener to format CSV data on paste
|
||||
csvInput.addEventListener('paste', function(e) {
|
||||
// Let the paste happen naturally, then process after a brief delay
|
||||
setTimeout(function() {
|
||||
const text = csvInput.value;
|
||||
if (text && text.length > 0) {
|
||||
// Auto-detect delimiter
|
||||
autoDetectDelimiter(text);
|
||||
// Process immediately after paste
|
||||
processCSV();
|
||||
}
|
||||
}, 50); // Slightly longer delay to ensure paste completes
|
||||
});
|
||||
|
||||
// Add change listeners to delimiter and header options to reprocess data
|
||||
delimiterSelect.addEventListener('change', function() {
|
||||
if (csvInput.value.trim() !== '') {
|
||||
processCSV();
|
||||
}
|
||||
});
|
||||
|
||||
hasHeaderCheckbox.addEventListener('change', function() {
|
||||
if (csvInput.value.trim() !== '') {
|
||||
processCSV();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Auto-detect the delimiter in pasted CSV data
|
||||
*/
|
||||
function autoDetectDelimiter(text) {
|
||||
// Count occurrences of common delimiters
|
||||
const firstFewLines = text.split('\n').slice(0, 5).join('\n');
|
||||
const counts = {
|
||||
',': (firstFewLines.match(/,/g) || []).length,
|
||||
';': (firstFewLines.match(/;/g) || []).length,
|
||||
'\t': (firstFewLines.match(/\t/g) || []).length,
|
||||
'|': (firstFewLines.match(/\|/g) || []).length
|
||||
};
|
||||
|
||||
// Find the most common delimiter
|
||||
let maxCount = 0;
|
||||
let detectedDelimiter = ','; // default
|
||||
|
||||
for (const [delimiter, count] of Object.entries(counts)) {
|
||||
if (count > maxCount) {
|
||||
maxCount = count;
|
||||
detectedDelimiter = delimiter;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the delimiter dropdown
|
||||
if (maxCount > 0) {
|
||||
delimiterSelect.value = detectedDelimiter === '\t' ? '\\t' : detectedDelimiter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the CSV data based on selected options
|
||||
*/
|
||||
function processCSV() {
|
||||
const csvText = csvInput.value.trim();
|
||||
if (!csvText) {
|
||||
outputDiv.innerHTML = '<h3>Output</h3><p class="alert alert-info">Paste CSV data above to view it as a table.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Show processing message
|
||||
outputDiv.innerHTML = '<h3>Output</h3><p class="alert alert-info">Processing data...</p>';
|
||||
|
||||
// Parse CSV using Papa Parse
|
||||
const delimiter = delimiterSelect.value;
|
||||
const hasHeader = hasHeaderCheckbox.checked;
|
||||
|
||||
// Enhanced parsing options
|
||||
Papa.parse(csvText, {
|
||||
delimiter: delimiter,
|
||||
header: hasHeader,
|
||||
skipEmptyLines: 'greedy', // Skip truly empty lines
|
||||
dynamicTyping: true, // Automatically convert numeric values
|
||||
trimHeaders: true, // Trim whitespace from headers
|
||||
complete: function(results) {
|
||||
if (results.errors.length > 0) {
|
||||
showError('Error parsing CSV: ' + results.errors[0].message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (results.data.length === 0 || (results.data.length === 1 && Object.keys(results.data[0]).length === 0)) {
|
||||
showError('No valid data found. Please check your CSV format and delimiter.');
|
||||
return;
|
||||
}
|
||||
|
||||
csvData = results.data;
|
||||
|
||||
// Handle headers
|
||||
if (hasHeader) {
|
||||
if (results.meta.fields && results.meta.fields.length > 0) {
|
||||
headers = results.meta.fields.map(h => h.trim());
|
||||
} else {
|
||||
// Fallback if no headers detected
|
||||
headers = Object.keys(results.data[0] || {}).map((_, i) => `Column${i + 1}`);
|
||||
}
|
||||
} else {
|
||||
headers = Object.keys(results.data[0] || {}).map((_, i) => `Column${i + 1}`);
|
||||
}
|
||||
|
||||
// Preview the data
|
||||
previewData();
|
||||
},
|
||||
error: function(error) {
|
||||
showError('Error processing CSV: ' + error.message);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
showError('Error processing CSV: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview the CSV data in a table with sortable columns
|
||||
*/
|
||||
function previewData() {
|
||||
if (csvData.length === 0) {
|
||||
showError('No data to preview.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit preview to first 500 rows
|
||||
const previewData = csvData.slice(0, 500);
|
||||
|
||||
// Generate table HTML
|
||||
let tableHtml = '<h3>Data Preview</h3>';
|
||||
tableHtml += `<p>Showing ${previewData.length} of ${csvData.length} rows</p>`;
|
||||
tableHtml += '<div class="table-responsive"><table class="tool-table">';
|
||||
|
||||
// Table headers with sort functionality
|
||||
tableHtml += '<thead><tr>';
|
||||
headers.forEach(header => {
|
||||
const isSorted = header === currentSortColumn;
|
||||
const sortClass = isSorted ? (sortDirection > 0 ? 'sort-asc' : 'sort-desc') : '';
|
||||
tableHtml += `<th class="${sortClass}" data-column="${header}">${header} ${isSorted ? (sortDirection > 0 ? '↑' : '↓') : ''}</th>`;
|
||||
});
|
||||
tableHtml += '</tr></thead>';
|
||||
|
||||
// Table body with improved cell formatting
|
||||
tableHtml += '<tbody>';
|
||||
previewData.forEach(row => {
|
||||
tableHtml += '<tr>';
|
||||
headers.forEach(header => {
|
||||
const cellValue = row[header];
|
||||
// Format cell value based on type
|
||||
let formattedValue = '';
|
||||
|
||||
if (cellValue === null || cellValue === undefined) {
|
||||
formattedValue = '<span class="empty-cell">(empty)</span>';
|
||||
} else if (typeof cellValue === 'string') {
|
||||
formattedValue = escapeHtml(cellValue);
|
||||
} else {
|
||||
formattedValue = String(cellValue);
|
||||
}
|
||||
|
||||
tableHtml += `<td>${formattedValue}</td>`;
|
||||
});
|
||||
tableHtml += '</tr>';
|
||||
});
|
||||
tableHtml += '</tbody></table></div>';
|
||||
|
||||
// Add stats summary
|
||||
const totalRows = csvData.length;
|
||||
const totalColumns = headers.length;
|
||||
tableHtml += `<div class="data-stats">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Total Rows:</span>
|
||||
<span class="stat-value">${totalRows}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Total Columns:</span>
|
||||
<span class="stat-value">${totalColumns}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Display in output div
|
||||
outputDiv.innerHTML = tableHtml;
|
||||
|
||||
// Add click event listeners to table headers for sorting
|
||||
const tableHeaders = outputDiv.querySelectorAll('th');
|
||||
tableHeaders.forEach(th => {
|
||||
th.addEventListener('click', () => {
|
||||
const column = th.getAttribute('data-column');
|
||||
sortData(column);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape HTML special characters to prevent XSS
|
||||
*/
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort data by column
|
||||
*/
|
||||
function sortData(column) {
|
||||
// Toggle sort direction if clicking the same column
|
||||
if (column === currentSortColumn) {
|
||||
sortDirection *= -1;
|
||||
} else {
|
||||
currentSortColumn = column;
|
||||
sortDirection = 1;
|
||||
}
|
||||
|
||||
// Sort the data
|
||||
csvData.sort((a, b) => {
|
||||
const valueA = a[column] !== undefined ? a[column] : '';
|
||||
const valueB = b[column] !== undefined ? b[column] : '';
|
||||
|
||||
// Try to sort numerically if possible
|
||||
if (typeof valueA === 'number' && typeof valueB === 'number') {
|
||||
return (valueA - valueB) * sortDirection;
|
||||
}
|
||||
|
||||
// Handle dates
|
||||
const dateA = new Date(valueA);
|
||||
const dateB = new Date(valueB);
|
||||
if (!isNaN(dateA) && !isNaN(dateB)) {
|
||||
return (dateA - dateB) * sortDirection;
|
||||
}
|
||||
|
||||
// Otherwise sort alphabetically
|
||||
return String(valueA).localeCompare(String(valueB)) * sortDirection;
|
||||
});
|
||||
|
||||
// Update the preview
|
||||
previewData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show error message
|
||||
*/
|
||||
function showError(message) {
|
||||
// Clear any existing alerts
|
||||
clearAlerts();
|
||||
|
||||
const alert = document.createElement('div');
|
||||
alert.className = 'alert alert-error';
|
||||
alert.textContent = message;
|
||||
|
||||
// Insert at the top of the output div
|
||||
const firstChild = outputDiv.querySelector('h3') ?
|
||||
outputDiv.querySelector('h3').nextSibling :
|
||||
outputDiv.firstChild;
|
||||
|
||||
outputDiv.insertBefore(alert, firstChild);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show success message
|
||||
*/
|
||||
function showSuccess(message) {
|
||||
const alert = document.createElement('div');
|
||||
alert.className = 'alert alert-success';
|
||||
alert.textContent = message;
|
||||
|
||||
// Insert after the heading
|
||||
const firstChild = outputDiv.querySelector('h3') ?
|
||||
outputDiv.querySelector('h3').nextSibling :
|
||||
outputDiv.firstChild;
|
||||
|
||||
outputDiv.insertBefore(alert, firstChild);
|
||||
|
||||
// Auto-hide success message after 3 seconds
|
||||
setTimeout(() => {
|
||||
if (alert.parentNode === outputDiv) {
|
||||
alert.remove();
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all alert messages
|
||||
*/
|
||||
function clearAlerts() {
|
||||
const alerts = outputDiv.querySelectorAll('.alert');
|
||||
alerts.forEach(alert => alert.remove());
|
||||
}
|
||||
|
||||
// Check if there's already content in the textarea on page load
|
||||
if (csvInput.value.trim() !== '') {
|
||||
processCSV();
|
||||
}
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
});
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - One Pager Tools">
|
||||
<title>Colin Knapp Tools</title>
|
||||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="tool-styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=" crossorigin="anonymous"></script>
|
||||
<script src="../utils.js" integrity="sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544=" crossorigin="anonymous"></script>
|
||||
<!-- Add tool-specific scripts here -->
|
||||
</head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<div class="container-fluid" role="main">
|
||||
<h1>Tool Name</h1>
|
||||
<p>A brief description of what this tool does.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Tool-specific content goes here -->
|
||||
<div class="tool-container">
|
||||
<!-- This section would be customized for each tool -->
|
||||
<div class="tool-controls">
|
||||
<!-- Input controls -->
|
||||
</div>
|
||||
|
||||
<div class="tool-output">
|
||||
<!-- Results/output -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,423 @@
|
|||
/* Additional styles for one-pager tools - UPDATED */
|
||||
|
||||
.tool-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
margin: 2rem 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.tool-controls {
|
||||
background-color: var(--bg-secondary);
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.tool-output {
|
||||
background-color: var(--bg-secondary);
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 1.5rem;
|
||||
width: 100%;
|
||||
border: 2px solid var(--accent-color);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tool-output h3 {
|
||||
color: var(--accent-color);
|
||||
margin-top: 0;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--accent-color);
|
||||
}
|
||||
|
||||
#output {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#output::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
/* Form controls styling to match the theme */
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
border-radius: 0.25rem;
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Make CSV input textarea wider and improve formatting */
|
||||
#csvInput {
|
||||
width: 100% !important;
|
||||
min-height: 250px;
|
||||
font-family: 'Courier New', monospace;
|
||||
white-space: pre;
|
||||
tab-size: 4;
|
||||
-moz-tab-size: 4;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
resize: vertical;
|
||||
overflow-x: auto;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
letter-spacing: -0.2px;
|
||||
padding: 12px;
|
||||
margin: 0;
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
/* Fix for textarea width issues */
|
||||
textarea#csvInput {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.2s, opacity 0.2s;
|
||||
margin-right: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Accessibility features */
|
||||
.btn:focus, .form-control:focus {
|
||||
outline: 2px solid var(--focus-outline-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Table styles for data display */
|
||||
.table-responsive {
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
margin-bottom: 2rem;
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.tool-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: left;
|
||||
border: 1px solid var(--border-color);
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
.tool-table th,
|
||||
.tool-table td {
|
||||
padding: 0.75rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
border-right: 1px solid var(--border-color);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.tool-table td {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.tool-table th {
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.tool-table th:hover {
|
||||
background-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
.tool-table th.sort-asc,
|
||||
.tool-table th.sort-desc {
|
||||
background-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
.tool-table tr:nth-child(even) {
|
||||
background-color: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.tool-table tr:hover {
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
|
||||
/* File input styling */
|
||||
.file-input-container {
|
||||
position: relative;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.file-input {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
}
|
||||
|
||||
.file-input-label {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: var(--button-bg);
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s, border-color 0.3s;
|
||||
}
|
||||
|
||||
.file-input-label:hover {
|
||||
background-color: var(--button-hover-bg);
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.file-name {
|
||||
margin-left: 1rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Progress indicators */
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
background-color: var(--progress-bg);
|
||||
border-radius: 4px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 10px;
|
||||
background-color: var(--accent-color);
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* Alert/notification styles */
|
||||
.alert {
|
||||
padding: 1rem 1rem 1rem 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border-radius: 0.25rem;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.alert::before {
|
||||
margin-right: 0.75rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: rgba(59, 130, 246, 0.1);
|
||||
border-left: 4px solid rgb(59, 130, 246);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.alert-info::before {
|
||||
content: "ℹ️";
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: rgba(34, 197, 94, 0.15);
|
||||
border-left: 4px solid rgb(34, 197, 94);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.alert-success::before {
|
||||
content: "✅";
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: rgba(239, 68, 68, 0.15);
|
||||
border-left: 4px solid rgb(239, 68, 68);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.alert-error::before {
|
||||
content: "⚠️";
|
||||
}
|
||||
|
||||
/* CSV Example Section */
|
||||
.csv-examples {
|
||||
margin: 2rem 0;
|
||||
padding: 1.5rem;
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.csv-examples h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.csv-examples p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.csv-examples button {
|
||||
margin-right: 0.75rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* Checkbox styling */
|
||||
input[type="checkbox"] {
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
vertical-align: middle;
|
||||
margin-left: 0.5rem;
|
||||
accent-color: var(--accent-color);
|
||||
}
|
||||
|
||||
/* Section headers */
|
||||
h2, h3 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 2.5rem 0;
|
||||
border: 0;
|
||||
height: 1px;
|
||||
background-color: var(--border-color);
|
||||
}
|
||||
|
||||
/* Accessibility styles */
|
||||
.accessibility-notice {
|
||||
font-size: 0.9rem;
|
||||
margin-top: 30px;
|
||||
padding: 15px;
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Improved responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.tool-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tool-controls,
|
||||
.tool-output {
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tool-table th,
|
||||
.tool-table td {
|
||||
padding: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
#csvInput {
|
||||
min-height: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Data stats styling */
|
||||
.data-stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem;
|
||||
background-color: var(--bg-tertiary);
|
||||
border-radius: 0.25rem;
|
||||
border-left: 4px solid var(--accent-color);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: var(--bg-primary);
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-weight: 600;
|
||||
margin-right: 0.5rem;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - Tool Example">
|
||||
<title>Tool Example - Colin Knapp</title>
|
||||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css">
|
||||
<link rel="stylesheet" href="tool-styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="../utils.js" integrity="sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544="></script>
|
||||
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
|
||||
<script src="tool-example.js" defer></script>
|
||||
</head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<h1>Tool Example</h1>
|
||||
<p>A simple example tool to demonstrate the includes system.</p>
|
||||
|
||||
<div class="tool-container">
|
||||
<div class="tool-controls">
|
||||
<h3>Tool Controls</h3>
|
||||
<div class="form-group">
|
||||
<label for="exampleInput">Example Input:</label>
|
||||
<input type="text" id="exampleInput" class="form-control" placeholder="Enter some text...">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button id="exampleButton" class="btn">Process</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tool-output" id="output">
|
||||
<p class="alert alert-info">Output will appear here.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>About This Tool</h2>
|
||||
<p>This is an example tool page that demonstrates how to use the includes system.</p>
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
|
@ -1,22 +0,0 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const downloadButton = document.getElementById('downloadPDF');
|
||||
if (downloadButton) {
|
||||
downloadButton.addEventListener('click', function() {
|
||||
// Store current theme
|
||||
const currentTheme = document.body.getAttribute('data-theme');
|
||||
|
||||
// Force light theme for PDF
|
||||
document.body.setAttribute('data-theme', 'light');
|
||||
|
||||
// Wait for theme change to apply
|
||||
setTimeout(function() {
|
||||
window.print();
|
||||
|
||||
// Restore original theme
|
||||
setTimeout(function() {
|
||||
document.body.setAttribute('data-theme', currentTheme);
|
||||
}, 100);
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,84 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - Portfolio Stories and Case Studies">
|
||||
<title>Colin Knapp - Stories & Case Studies</title>
|
||||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=">
|
||||
</head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<div class="container-fluid" role="main">
|
||||
<h1>Project Stories & Case Studies</h1>
|
||||
<p>Detailed stories and elaborations of projects from my portfolio.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="stories-grid">
|
||||
<div class="story-card">
|
||||
<h2>Open Source Community Success</h2>
|
||||
<p class="story-excerpt">How I revitalized an abandoned open source project, built a thriving community of 32,000+ members, and established sustainable funding.</p>
|
||||
<p class="story-meta">Category: Open Source | Date: 2019-Present</p>
|
||||
<a href="open-source-success.html" class="story-link">Read Full Story</a>
|
||||
</div>
|
||||
|
||||
<div class="story-card">
|
||||
<h2>ViperWire Cybersecurity</h2>
|
||||
<p class="story-excerpt">How I built an AI-powered cybersecurity consultancy from the ground up, focusing on cutting-edge protection for digital assets.</p>
|
||||
<p class="story-meta">Category: Cybersecurity | Date: 2023</p>
|
||||
<a href="viperwire.html" class="story-link">Read Full Story</a>
|
||||
</div>
|
||||
|
||||
<div class="story-card">
|
||||
<h2>FastAsyncWorldEdit & PlotSquared</h2>
|
||||
<p class="story-excerpt">The technical challenges overcome in scaling Minecraft world editing from crashing at 50,000 edits to seamlessly handling billions.</p>
|
||||
<p class="story-meta">Category: Open Source | Date: 2014-Present</p>
|
||||
<a href="fawe-plotsquared.html" class="story-link">Read Full Story</a>
|
||||
</div>
|
||||
|
||||
<div class="story-card">
|
||||
<h2>Healthcare Platform Infrastructure</h2>
|
||||
<p class="story-excerpt">An in-depth look at the infrastructure design and security implementation for the Improving MI Practices healthcare platform.</p>
|
||||
<p class="story-meta">Category: Infrastructure | Date: 2019-Present</p>
|
||||
<a href="healthcare-platform.html" class="story-link">Read Full Story</a>
|
||||
</div>
|
||||
|
||||
<div class="story-card">
|
||||
<h2>WordPress Security Automation</h2>
|
||||
<p class="story-excerpt">How I developed a Docker-based solution that eliminated persistent malware attacks on a high-profile website.</p>
|
||||
<p class="story-meta">Category: Security | Date: 2023</p>
|
||||
<a href="wordpress-security.html" class="story-link">Read Full Story</a>
|
||||
</div>
|
||||
|
||||
<div class="story-card">
|
||||
<h2>Airport DNS Infrastructure</h2>
|
||||
<p class="story-excerpt">Building a geographically redundant DNS cluster for Flint Bishop International Airport that achieves A+ reliability standards.</p>
|
||||
<p class="story-meta">Category: Infrastructure | Date: 2019-Present</p>
|
||||
<a href="airport-dns.html" class="story-link">Read Full Story</a>
|
||||
</div>
|
||||
|
||||
<div class="story-card">
|
||||
<h2>NitricConcepts Leadership</h2>
|
||||
<p class="story-excerpt">Managing a distributed team of 45 contractors and implementing DevSecOps practices across multiple timezones.</p>
|
||||
<p class="story-meta">Category: Leadership | Date: 2018-2021</p>
|
||||
<a href="nitric-leadership.html" class="story-link">Read Full Story</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<p class="accessibility-notice"><strong>Accessibility:</strong> This website is designed and developed to meet WCAG 2.1 Level AAA standards, ensuring the highest level of accessibility for all users. Features include high contrast ratios, keyboard navigation, screen reader compatibility, and responsive design. The site supports both light and dark modes with automatic system preference detection.</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,161 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - Open Source Community Success Case Study">
|
||||
<title>Open Source Community Success - Colin Knapp Case Study</title>
|
||||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=">
|
||||
</head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<div class="container-fluid" role="main">
|
||||
<div class="story-header">
|
||||
<h1>Building a Thriving Open Source Community</h1>
|
||||
<p class="story-meta">Category: Open Source | Date: 2019-Present</p>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div class="story-content">
|
||||
<p>In 2019, I had the opportunity to take over a promising open source project that was at risk of becoming abandoned. The original developer had created a solid foundation but was unable to continue maintaining it. Recognizing the project's potential and the community's need, I stepped in to not only maintain but significantly expand both the codebase and the community around it.</p>
|
||||
|
||||
<div class="story-image-container">
|
||||
<img src="../images/docker-hub-stats.jpg" alt="Docker Hub statistics showing over 10 million pulls" class="story-image">
|
||||
<p class="image-caption">The project's Docker Hub statistics showing over 10 million pulls</p>
|
||||
</div>
|
||||
|
||||
<div class="story-image-container">
|
||||
<img src="../images/discord-community.jpg" alt="Discord community statistics showing over 32,000 members with 4,297 online" class="story-image">
|
||||
<p class="image-caption">Our thriving Discord community with over 32,000 members</p>
|
||||
</div>
|
||||
|
||||
<h2>The Challenge</h2>
|
||||
<p>When I took over the project, it faced several critical challenges:</p>
|
||||
<ul>
|
||||
<li>A codebase that was functional but needed significant modernization</li>
|
||||
<li>Limited infrastructure for continuous integration, testing, and deployment</li>
|
||||
<li>A small but passionate community with no structured way to communicate or collaborate</li>
|
||||
<li>No sustainable funding model to support ongoing development and maintenance</li>
|
||||
<li>Technical debt that was hindering new feature development and stability</li>
|
||||
</ul>
|
||||
|
||||
<blockquote>
|
||||
"The true measure of an open source project's success isn't just code quality or feature completeness—it's the health and engagement of the community that forms around it."
|
||||
</blockquote>
|
||||
|
||||
<h2>The Approach</h2>
|
||||
<p>I developed a comprehensive strategy that addressed both technical excellence and community building:</p>
|
||||
|
||||
<h3>1. Technical Infrastructure</h3>
|
||||
<p>My first priority was establishing robust infrastructure to support sustainable development:</p>
|
||||
<ul>
|
||||
<li>Implemented a comprehensive CI/CD pipeline using GitHub Actions</li>
|
||||
<li>Created Docker containers for easy deployment, which have now been pulled over 10 million times</li>
|
||||
<li>Established automated testing with coverage requirements for all new code</li>
|
||||
<li>Developed clear documentation for both users and contributors</li>
|
||||
<li>Set up monitoring and observability tools to track usage and identify issues</li>
|
||||
</ul>
|
||||
|
||||
<h3>2. Community Building</h3>
|
||||
<p>In parallel with technical improvements, I focused on creating a welcoming, active community:</p>
|
||||
<ul>
|
||||
<li>Established a Discord server that has grown to over 32,000 members, with typically 4,000+ active at any time</li>
|
||||
<li>Created structured channels for support, feature requests, showcase, and general discussion</li>
|
||||
<li>Implemented community guidelines and moderation systems to maintain a positive environment</li>
|
||||
<li>Organized regular community calls and update announcements</li>
|
||||
<li>Recognized and celebrated community contributions</li>
|
||||
</ul>
|
||||
|
||||
<h3>3. Sustainable Funding</h3>
|
||||
<p>To ensure long-term viability, I established a sustainable funding model:</p>
|
||||
<ul>
|
||||
<li>Set up an OpenCollective account for transparent community funding</li>
|
||||
<li>Developed clear funding goals tied to specific development milestones</li>
|
||||
<li>Created a sponsorship program with appropriate recognition for corporate supporters</li>
|
||||
<li>Established a governance model for fund allocation</li>
|
||||
<li>Built several years of runway to ensure project stability</li>
|
||||
</ul>
|
||||
|
||||
<h2>The Team</h2>
|
||||
<p>As the project grew, I recruited and mentored a team of over 10 developers who now contribute regularly:</p>
|
||||
<ul>
|
||||
<li>Established clear contribution guidelines and review processes</li>
|
||||
<li>Created an onboarding process for new contributors</li>
|
||||
<li>Implemented a mentorship system pairing experienced developers with newcomers</li>
|
||||
<li>Developed a governance structure that distributes decision-making authority</li>
|
||||
<li>Set up regular team meetings and coordination channels</li>
|
||||
</ul>
|
||||
|
||||
<h2>Results & Impact</h2>
|
||||
<p>The project has achieved remarkable success under this new structure:</p>
|
||||
<ul>
|
||||
<li>Over 10 million Docker image pulls, demonstrating widespread adoption</li>
|
||||
<li>A thriving Discord community with 32,000+ members and 4,000+ regularly active users</li>
|
||||
<li>Sustainable funding through OpenCollective with several years of runway</li>
|
||||
<li>A stable team of 10+ regular contributors</li>
|
||||
<li>Significant expansion of features and capabilities</li>
|
||||
<li>Improved code quality, test coverage, and documentation</li>
|
||||
<li>Regular release cycles with clear roadmaps</li>
|
||||
</ul>
|
||||
|
||||
<h2>Lessons Learned</h2>
|
||||
<p>This experience provided valuable insights into open source project management:</p>
|
||||
|
||||
<h3>Technical Lessons</h3>
|
||||
<p>Infrastructure investments pay dividends. The early focus on CI/CD, containerization, and automated testing dramatically improved both code quality and contributor experience. By making it easy to deploy, test, and contribute, we lowered barriers to entry and increased the pace of innovation.</p>
|
||||
|
||||
<p>Additionally, the decision to containerize the application early proved prescient, as it significantly simplified deployment across diverse environments and contributed to the project's widespread adoption.</p>
|
||||
|
||||
<h3>Community Lessons</h3>
|
||||
<p>Community building requires intentional design. By creating structured spaces for different types of interactions (support, development, showcase, etc.), we enabled community members to engage in ways that matched their interests and expertise. The regular rhythm of updates and community calls helped maintain momentum and excitement.</p>
|
||||
|
||||
<p>Transparency in decision-making and fund management built trust with the community, which proved essential for sustainable growth and support.</p>
|
||||
|
||||
<h2>Future Directions</h2>
|
||||
<p>Looking ahead, the project continues to evolve with several key initiatives:</p>
|
||||
<ul>
|
||||
<li>Expanding the core team through formalized mentorship programs</li>
|
||||
<li>Developing educational resources to lower the barrier to entry for new users</li>
|
||||
<li>Exploring integration opportunities with complementary projects</li>
|
||||
<li>Implementing a more structured feature planning process based on community input</li>
|
||||
<li>Expanding the project's scope based on evolving user needs</li>
|
||||
</ul>
|
||||
|
||||
<p>The success of this project demonstrates how technical excellence combined with intentional community building can transform an at-risk open source project into a thriving ecosystem that benefits thousands of users while providing sustainable opportunities for contributors.</p>
|
||||
</div>
|
||||
|
||||
<div class="story-footer">
|
||||
<div class="story-nav">
|
||||
<a href="nitric-leadership.html" class="story-nav-link prev">NitricConcepts Leadership</a>
|
||||
<span class="story-nav-link next" style="visibility: hidden;">Next Story</span>
|
||||
</div>
|
||||
|
||||
<div class="related-stories">
|
||||
<h3>Related Stories</h3>
|
||||
<div class="related-stories-list">
|
||||
<div class="story-card">
|
||||
<h2>FastAsyncWorldEdit & PlotSquared</h2>
|
||||
<p class="story-excerpt">The technical challenges overcome in scaling Minecraft world editing from crashing at 50,000 edits to seamlessly handling billions.</p>
|
||||
<a href="fawe-plotsquared.html" class="story-link">Read Story</a>
|
||||
</div>
|
||||
<div class="story-card">
|
||||
<h2>NitricConcepts Leadership</h2>
|
||||
<p class="story-excerpt">Managing a distributed team of 45 contractors and implementing DevSecOps practices across multiple timezones.</p>
|
||||
<a href="nitric-leadership.html" class="story-link">Read Story</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,204 @@
|
|||
/* Additional styles for stories and case studies */
|
||||
|
||||
.stories-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.story-card {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 1.5rem;
|
||||
background-color: var(--card-bg);
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.story-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.story-card h2 {
|
||||
margin-top: 0;
|
||||
font-size: 1.5rem;
|
||||
color: var(--heading-color);
|
||||
}
|
||||
|
||||
.story-excerpt {
|
||||
flex-grow: 1;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.story-meta {
|
||||
font-size: 0.85rem;
|
||||
color: var(--meta-text-color);
|
||||
margin-bottom: 1rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.story-link {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.3s;
|
||||
text-align: center;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.story-link:hover {
|
||||
background-color: var(--accent-color-darker);
|
||||
}
|
||||
|
||||
/* Individual story page styles */
|
||||
.story-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.story-header h1 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.story-header .story-meta {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Story image container and caption styles */
|
||||
.story-image-container {
|
||||
margin: 2rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.story-image {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
margin: 0 0 0.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.image-caption {
|
||||
font-size: 0.9rem;
|
||||
color: var(--meta-text-color);
|
||||
margin: 0.5rem 0 1.5rem;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.story-content {
|
||||
line-height: 1.7;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.story-content h2 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.story-content p {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.story-content blockquote {
|
||||
border-left: 4px solid var(--accent-color);
|
||||
padding-left: 1rem;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
font-style: italic;
|
||||
color: var(--blockquote-color);
|
||||
}
|
||||
|
||||
.story-footer {
|
||||
margin-top: 3rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.related-stories {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.related-stories h3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.related-stories-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.story-nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.story-nav-link {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
color: var(--text-color);
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.story-nav-link:hover {
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.story-nav-link.prev::before {
|
||||
content: "← ";
|
||||
}
|
||||
|
||||
.story-nav-link.next::after {
|
||||
content: " →";
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.stories-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.related-stories-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.story-nav {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.story-nav-link {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* Accessibility enhancements */
|
||||
.story-link:focus {
|
||||
outline: 2px solid var(--focus-outline-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Utility classes */
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - Story Example">
|
||||
<title>Story Example - Colin Knapp</title>
|
||||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
|
||||
</head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="story-header">
|
||||
<h1>Story Title Example</h1>
|
||||
<p class="story-meta">Category: Example | Date: 2023-Present</p>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div class="story-content">
|
||||
<p>This is an example story content paragraph.</p>
|
||||
|
||||
<h2>Story Section</h2>
|
||||
<p>This is a section of the story.</p>
|
||||
|
||||
<!-- Additional content would go here -->
|
||||
</div>
|
||||
|
||||
<div class="story-footer">
|
||||
<div class="story-nav">
|
||||
<a href="#" class="story-nav-link prev">Previous Story</a>
|
||||
<a href="#" class="story-nav-link next">Next Story</a>
|
||||
</div>
|
||||
|
||||
<div class="related-stories">
|
||||
<h3>Related Stories</h3>
|
||||
<div class="related-stories-list">
|
||||
<div class="story-card">
|
||||
<h2>Related Story Title</h2>
|
||||
<p class="story-excerpt">Brief description of the related story content.</p>
|
||||
<a href="#" class="story-link">Read Story</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,80 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - [Story Title] Case Study">
|
||||
<title>[Story Title] - Colin Knapp Case Study</title>
|
||||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=" crossorigin="anonymous">
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=">
|
||||
</head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<div class="container-fluid" role="main">
|
||||
<div class="story-header">
|
||||
<h1>[Story Title]</h1>
|
||||
<p class="story-meta">Category: [Category] | Date: [Date Range]</p>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div class="story-content">
|
||||
<!-- Replace with actual content -->
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam vehicula, nisl vel ultricies aliquet, nisi urna posuere nibh, vel dapibus sapien libero ac nisi. Phasellus non volutpat orci, eu eleifend diam. Integer scelerisque euismod sem, vel euismod orci viverra a.</p>
|
||||
|
||||
<h2>The Challenge</h2>
|
||||
<p>Cras consectetur dolor vel arcu luctus, a hendrerit tortor ultricies. Fusce eu eros vel mauris fermentum volutpat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer rhoncus tortor vitae turpis tincidunt, sed congue lacus tempor.</p>
|
||||
|
||||
<blockquote>
|
||||
"This project represented a significant challenge that required innovative thinking and cutting-edge technology solutions."
|
||||
</blockquote>
|
||||
|
||||
<h2>The Approach</h2>
|
||||
<p>Praesent feugiat tortor non tortor maximus, at congue nibh tincidunt. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Morbi vulputate velit non enim euismod, sit amet maximus mauris consectetur. Donec placerat arcu non faucibus condimentum.</p>
|
||||
|
||||
<!-- Sample image placement -->
|
||||
<!-- <img src="images/story-image.jpg" alt="Description of the image" class="story-image"> -->
|
||||
|
||||
<h2>Technical Implementation</h2>
|
||||
<p>Aenean non auctor mauris, id dapibus turpis. Fusce vestibulum mi sit amet sem commodo, id aliquam magna sollicitudin. Donec vel libero cursus, venenatis justo sit amet, faucibus nisi. Vestibulum nec suscipit mi. Ut fringilla scelerisque eros, id vestibulum lectus vehicula id.</p>
|
||||
|
||||
<h2>Results & Impact</h2>
|
||||
<p>Suspendisse potenti. Curabitur pharetra neque quis dolor pretium, nec feugiat metus varius. Nulla facilisi. Nam ut justo sed leo viverra iaculis. Aliquam eget risus vitae quam dapibus dignissim at vel lectus. Proin convallis velit non dictum faucibus.</p>
|
||||
|
||||
<h2>Lessons Learned</h2>
|
||||
<p>Proin malesuada facilisis felis, quis placerat erat dignissim nec. Morbi hendrerit elit vitae orci interdum mattis. Curabitur imperdiet velit ut libero egestas, at accumsan arcu vehicula. Nullam ac orci et velit efficitur condimentum.</p>
|
||||
</div>
|
||||
|
||||
<div class="story-footer">
|
||||
<div class="story-nav">
|
||||
<a href="#" class="story-nav-link prev">Previous Story</a>
|
||||
<a href="#" class="story-nav-link next">Next Story</a>
|
||||
</div>
|
||||
|
||||
<div class="related-stories">
|
||||
<h3>Related Stories</h3>
|
||||
<div class="related-stories-list">
|
||||
<div class="story-card">
|
||||
<h2>Related Story Title</h2>
|
||||
<p class="story-excerpt">Brief description of the related story content.</p>
|
||||
<a href="#" class="story-link">Read Story</a>
|
||||
</div>
|
||||
<div class="story-card">
|
||||
<h2>Another Related Story</h2>
|
||||
<p class="story-excerpt">Brief description of another related story.</p>
|
||||
<a href="#" class="story-link">Read Story</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,126 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - ViperWire Cybersecurity Case Study">
|
||||
<title>ViperWire Cybersecurity - Colin Knapp Case Study</title>
|
||||
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||
<link rel="stylesheet" href="../styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=" crossorigin="anonymous">
|
||||
<script src="../includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="stories.css" integrity="sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=">
|
||||
</head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<div class="container-fluid" role="main">
|
||||
<div class="story-header">
|
||||
<h1>Building ViperWire: An AI-Powered Cybersecurity Consultancy</h1>
|
||||
<p class="story-meta">Category: Cybersecurity | Date: 2023-Present</p>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div class="story-content">
|
||||
<p>In early 2023, I identified a critical gap in the cybersecurity market: small to medium-sized businesses were increasingly becoming targets for sophisticated cyber attacks, but lacked access to enterprise-grade security solutions that could adapt to rapidly evolving threats. This observation led to the creation of ViperWire, an AI-powered cybersecurity consultancy designed to democratize access to advanced security measures.</p>
|
||||
|
||||
<h2>The Challenge</h2>
|
||||
<p>The cybersecurity landscape in 2023 presented several unique challenges:</p>
|
||||
<ul>
|
||||
<li>The increasing sophistication of attacks targeting SMBs with limited security budgets</li>
|
||||
<li>A shortage of cybersecurity professionals capable of addressing modern threats</li>
|
||||
<li>The rapid evolution of attack vectors requiring constant vigilance and adaptation</li>
|
||||
<li>The need for solutions that could scale from small businesses to larger enterprises</li>
|
||||
</ul>
|
||||
|
||||
<blockquote>
|
||||
"The typical SMB faces the same threat actors as Fortune 500 companies, but with a fraction of the resources to defend themselves. This asymmetry creates a perfect storm where businesses are increasingly vulnerable while security solutions remain inaccessible."
|
||||
</blockquote>
|
||||
|
||||
<h2>The Approach</h2>
|
||||
<p>I built ViperWire around three core principles that would differentiate it in the market:</p>
|
||||
|
||||
<h3>1. AI-Augmented Security Analysis</h3>
|
||||
<p>Rather than attempting to replace human expertise with AI, I designed systems where AI tools augment human analysts, dramatically increasing their efficiency and effectiveness. This approach began with custom-built monitoring tools that use machine learning to identify behavioral anomalies and prioritize potential threats, allowing human experts to focus on the most critical issues.</p>
|
||||
|
||||
<h3>2. Accessible Enterprise-Grade Protection</h3>
|
||||
<p>By leveraging containerization, infrastructure-as-code, and modular security components, I created scalable security systems that could be rapidly deployed across organizations of varying sizes. This technical architecture allowed ViperWire to deliver enterprise-caliber protection at price points accessible to smaller organizations.</p>
|
||||
|
||||
<h3>3. Continuous Adaptation</h3>
|
||||
<p>I implemented a continuous security improvement cycle that incorporated threat intelligence feeds, regular penetration testing, and automated vulnerability scanning. This approach ensured that security postures evolved in tandem with emerging threats rather than reacting after incidents occurred.</p>
|
||||
|
||||
<h2>Technical Implementation</h2>
|
||||
<p>The technical architecture of ViperWire comprises several innovative components:</p>
|
||||
|
||||
<h3>Threat Detection Infrastructure</h3>
|
||||
<p>I built a distributed monitoring system using a combination of open-source tools (Wazuh, Suricata, OSSEC) enhanced with custom machine learning models to detect anomalous network and system behaviors. The architecture utilizes Kubernetes for orchestration and Prometheus/Grafana for metrics visualization, with custom alerting thresholds tuned to each client's environment.</p>
|
||||
|
||||
<h3>Response Automation</h3>
|
||||
<p>To counter the speed of modern attacks, I developed an automated response framework using Python and Ansible that could isolate compromised systems, revoke credentials, and implement temporary access controls within seconds of a confirmed threat detection. This system reduced the mean time to respond from hours to minutes, significantly limiting potential damage.</p>
|
||||
|
||||
<h3>Security Assessment Pipeline</h3>
|
||||
<p>For proactive security, I created an assessment pipeline incorporating static analysis, dynamic testing, and configuration auditing. This suite leverages Docker containers for consistent, reproducible security tests across different environments and includes custom scanners for emerging vulnerabilities not yet covered by commercial tools.</p>
|
||||
|
||||
<h2>Results & Impact</h2>
|
||||
<p>In its first year, ViperWire has achieved several notable successes:</p>
|
||||
<ul>
|
||||
<li>Successfully prevented ransomware attacks at two clients who had been targeted, saving an estimated $500,000 in potential losses</li>
|
||||
<li>Reduced security alert noise by 87% through improved detection algorithms, allowing for more focused attention on genuine threats</li>
|
||||
<li>Decreased mean time to detection of security incidents from 24+ hours to under 15 minutes</li>
|
||||
<li>Enabled five small businesses to achieve compliance with industry security standards that were previously beyond their reach</li>
|
||||
</ul>
|
||||
|
||||
<h2>Lessons Learned</h2>
|
||||
<p>Building ViperWire has provided valuable insights into both technical and business aspects of cybersecurity:</p>
|
||||
|
||||
<h3>Technical Lessons</h3>
|
||||
<p>The most effective security solutions combine multiple detection methodologies rather than relying on any single approach. Our hybrid model of behavioral analysis, signature detection, and anomaly identification proved far more effective than any individual method alone.</p>
|
||||
|
||||
<p>Additionally, I discovered that properly tuned automation dramatically reduces false positives—the bane of many security operations—while still capturing genuine threats. The key was implementing progressive verification steps that validate alerts before triggering high-impact responses.</p>
|
||||
|
||||
<h3>Business Lessons</h3>
|
||||
<p>Perhaps most importantly, I learned that transparency builds trust in security services. By providing clients with clear visibility into threat detection processes and plainly explaining technical concepts, ViperWire was able to build stronger relationships and encourage better security practices within client organizations.</p>
|
||||
|
||||
<h2>Future Directions</h2>
|
||||
<p>Looking ahead, ViperWire is expanding into several promising areas:</p>
|
||||
<ul>
|
||||
<li>Developing specialized security solutions for IoT environments in manufacturing settings</li>
|
||||
<li>Creating educational resources to help clients build internal security capabilities</li>
|
||||
<li>Expanding AI capabilities to provide predictive threat intelligence specific to each client's industry</li>
|
||||
</ul>
|
||||
|
||||
<p>The founding principle of ViperWire—that sophisticated security should be accessible to organizations of all sizes—continues to guide its evolution and growth.</p>
|
||||
</div>
|
||||
|
||||
<div class="story-footer">
|
||||
<div class="story-nav">
|
||||
<span class="story-nav-link prev hidden">Previous Story</span>
|
||||
<a href="fawe-plotsquared.html" class="story-nav-link next">FastAsyncWorldEdit & PlotSquared</a>
|
||||
</div>
|
||||
|
||||
<div class="related-stories">
|
||||
<h3>Related Stories</h3>
|
||||
<div class="related-stories-list">
|
||||
<div class="story-card">
|
||||
<h2>WordPress Security Automation</h2>
|
||||
<p class="story-excerpt">How I developed a Docker-based solution that eliminated persistent malware attacks on a high-profile website.</p>
|
||||
<a href="wordpress-security.html" class="story-link">Read Story</a>
|
||||
</div>
|
||||
<div class="story-card">
|
||||
<h2>Healthcare Platform Infrastructure</h2>
|
||||
<p class="story-excerpt">An in-depth look at the infrastructure design and security implementation for the Improving MI Practices healthcare platform.</p>
|
||||
<a href="healthcare-platform.html" class="story-link">Read Story</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
|
||||
<!-- Theme script moved to end of body -->
|
||||
<script src="../theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -8,6 +8,16 @@
|
|||
--theme-border: #ddd;
|
||||
--theme-hover: #e0e0e0;
|
||||
--date-color: #555555;
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f5f5f5;
|
||||
--bg-tertiary: #eaeaea;
|
||||
--bg-hover: #f0f0f0;
|
||||
--text-primary: #333333;
|
||||
--button-bg: #f5f5f5;
|
||||
--button-hover-bg: #e0e0e0;
|
||||
--focus-outline-color: #0056b3;
|
||||
--progress-bg: #e0e0e0;
|
||||
--accent-hover: #003d82;
|
||||
}
|
||||
|
||||
/* Dark theme variables when system prefers dark mode (auto setting) */
|
||||
|
@ -22,6 +32,16 @@
|
|||
--theme-border: #404040;
|
||||
--theme-hover: #3d3d3d;
|
||||
--date-color: #a0a0a0;
|
||||
--bg-primary: #1a1a1a;
|
||||
--bg-secondary: #2d2d2d;
|
||||
--bg-tertiary: #3d3d3d;
|
||||
--bg-hover: #333333;
|
||||
--text-primary: #e0e0e0;
|
||||
--button-bg: #2d2d2d;
|
||||
--button-hover-bg: #3d3d3d;
|
||||
--focus-outline-color: #5fa9ff;
|
||||
--progress-bg: #404040;
|
||||
--accent-hover: #8ac2ff;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +56,16 @@ html[data-theme='light'] {
|
|||
--theme-border: #ddd;
|
||||
--theme-hover: #e0e0e0;
|
||||
--date-color: #555555;
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f5f5f5;
|
||||
--bg-tertiary: #eaeaea;
|
||||
--bg-hover: #f0f0f0;
|
||||
--text-primary: #333333;
|
||||
--button-bg: #f5f5f5;
|
||||
--button-hover-bg: #e0e0e0;
|
||||
--focus-outline-color: #0056b3;
|
||||
--progress-bg: #e0e0e0;
|
||||
--accent-hover: #003d82;
|
||||
}
|
||||
|
||||
/* Dark theme variables when manually selected */
|
||||
|
@ -49,6 +79,16 @@ html[data-theme='dark'] {
|
|||
--theme-border: #404040;
|
||||
--theme-hover: #3d3d3d;
|
||||
--date-color: #a0a0a0;
|
||||
--bg-primary: #1a1a1a;
|
||||
--bg-secondary: #2d2d2d;
|
||||
--bg-tertiary: #3d3d3d;
|
||||
--bg-hover: #333333;
|
||||
--text-primary: #e0e0e0;
|
||||
--button-bg: #2d2d2d;
|
||||
--button-hover-bg: #3d3d3d;
|
||||
--focus-outline-color: #5fa9ff;
|
||||
--progress-bg: #404040;
|
||||
--accent-hover: #8ac2ff;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -62,6 +102,17 @@ body {
|
|||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Container that can expand to full width */
|
||||
.container-fluid {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
color: var(--text-color);
|
||||
margin-top: 1.5em;
|
||||
|
@ -163,33 +214,6 @@ hr {
|
|||
background-color: rgba(128, 128, 128, 0.2);
|
||||
}
|
||||
|
||||
/* Print styles */
|
||||
@media print {
|
||||
.theme-switch {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
background: white !important;
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
a {
|
||||
color: black !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-color: black !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
|
@ -206,4 +230,112 @@ hr {
|
|||
h3 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Navigation styles */
|
||||
.main-nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.main-nav ul {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
gap: 1rem;
|
||||
border-radius: 4px;
|
||||
background-color: var(--theme-bg);
|
||||
padding: 0.5rem 1rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.main-nav li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main-nav a {
|
||||
display: block;
|
||||
padding: 0.5rem 1rem;
|
||||
text-decoration: none;
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.main-nav a:hover {
|
||||
background-color: var(--theme-hover);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.main-nav a.active {
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Dropdown styles */
|
||||
.main-nav .dropdown {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.main-nav .dropdown > a::after {
|
||||
content: "▼";
|
||||
font-size: 0.7em;
|
||||
margin-left: 0.5em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.main-nav .dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background-color: var(--theme-bg);
|
||||
min-width: 160px;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem 0;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.main-nav .dropdown:hover .dropdown-content,
|
||||
.main-nav .dropdown:focus-within .dropdown-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.main-nav .dropdown-content a {
|
||||
padding: 0.5rem 1rem;
|
||||
display: block;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.main-nav .dropdown-content a.active {
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Responsive navigation */
|
||||
@media (max-width: 600px) {
|
||||
.main-nav ul {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.main-nav a {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.main-nav .dropdown-content {
|
||||
position: static;
|
||||
box-shadow: none;
|
||||
margin-top: 0;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Colin Knapp - Template with Includes">
|
||||
<title>Template with Includes - Colin Knapp</title>
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="styles.css" integrity="sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=">
|
||||
<script src="theme.js" integrity="sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4="></script>
|
||||
<script src="includes.js" integrity="sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4="></script>
|
||||
</head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'sha256-oRCvBUmDTuPb8XOF1vLYwhIrcj2kzMbEwX5QzUPAPQI=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-PzIpA2rd65QhtrVKWtx1/13mwFPbIl8DKS3arIpG8y4=' 'sha256-+dDNTo7WAOmn2YC875+vn9oH4UkMwlVOGlARp2uq3A4=' 'sha256-1ZUvhca3M/N6hch4NrdPufDPLTnANOpJ4hfsZgRykgg=' 'sha256-Ue6wom48SQbpmwW9QIk7pyVDR5Bg36SetP67V2pDkxc=' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; style-src 'self' 'sha256-5oTxos9Qxwhor3qIwHSM12YyIZi5E+tHuFdYER0hXoI=' 'sha256-807UZmWvd6eLc8xVckZkNX6CRP9WV8MzHURc5BgtRWo=' 'sha256-ALJm8rFr/KMj0rKwlJLLJ3iq4FoBvZrBZaaGZS1qGOY=' 'sha256-O42V9brCZFYWsV+Yu5u141A1u5p5LcnOs5K1dVeaIVs=' 'sha256-2EA12+9d+s6rrc0rkdIjfmjbh6p2o0ZSXs4wbZuk/tA='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';">
|
||||
<body>
|
||||
<!-- Header Include -->
|
||||
<div id="header-include"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<h1>Page Title</h1>
|
||||
<p>This is the main content of the page.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Section Title</h2>
|
||||
<p>This is a section of content.</p>
|
||||
|
||||
<!-- Footer Include -->
|
||||
<div id="footer-include"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,44 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# test-csv-tool.sh - Test the CSV tool functionality
|
||||
# =====================================================================
|
||||
# This script checks if the CSV tool page loads without CSP errors
|
||||
# =====================================================================
|
||||
|
||||
echo "=== Testing CSV Tool ==="
|
||||
|
||||
# Create a test CSV file
|
||||
echo "Name,Age,City
|
||||
John,30,New York
|
||||
Jane,25,San Francisco
|
||||
Bob,40,Chicago" > test.csv
|
||||
|
||||
# Check if the page loads properly
|
||||
echo "Checking if the CSV tool page loads properly..."
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/one-pager-tools/csv-tool.html)
|
||||
|
||||
if [ "$RESPONSE" -eq 200 ]; then
|
||||
echo "✅ CSV tool page loads successfully (HTTP $RESPONSE)"
|
||||
else
|
||||
echo "❌ CSV tool page failed to load (HTTP $RESPONSE)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for CSP errors in the response headers
|
||||
echo "Checking for CSP errors in response headers..."
|
||||
CSP_HEADER=$(curl -s -I http://localhost:8080/one-pager-tools/csv-tool.html | grep -i "Content-Security-Policy")
|
||||
|
||||
if [ -n "$CSP_HEADER" ]; then
|
||||
echo "✅ CSP header found in response"
|
||||
else
|
||||
echo "❌ CSP header not found in response"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
rm -f test.csv
|
||||
|
||||
echo "=== CSV Tool Test Completed Successfully ==="
|
||||
echo "The CSV tool appears to be working correctly."
|
||||
echo "You can manually test it by visiting: http://localhost:8080/one-pager-tools/csv-tool.html"
|
||||
echo "and pasting CSV data into the textarea."
|
|
@ -2,6 +2,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
const themeToggle = document.getElementById('themeToggle');
|
||||
const html = document.documentElement;
|
||||
|
||||
// Check if themeToggle exists before proceeding
|
||||
if (!themeToggle) {
|
||||
console.log('Theme toggle button not found on this page');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for saved theme preference, default to auto
|
||||
const savedTheme = localStorage.getItem('theme') || 'auto';
|
||||
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# update-csp-hashes.sh - Update Content Security Policy hashes
|
||||
# =====================================================================
|
||||
# This script updates the CSP hashes for:
|
||||
# 1. All JavaScript and CSS files
|
||||
# 2. All inline style attributes in HTML files
|
||||
# 3. Adds CSP meta tags to HTML files
|
||||
# After running this script, restart the server using:
|
||||
# ./caddy.sh
|
||||
# =====================================================================
|
||||
|
||||
set -e
|
||||
|
||||
echo "Updating CSP hashes for all JavaScript, CSS files, and inline styles..."
|
||||
|
||||
# Directory containing the files
|
||||
BASE_DIR="$(pwd)"
|
||||
CADDYFILE="$BASE_DIR/Caddyfile"
|
||||
TEMP_INLINE_HASHES_FILE=$(mktemp)
|
||||
|
||||
# Arrays to store hashes
|
||||
SCRIPT_HASHES=()
|
||||
STYLE_HASHES=()
|
||||
|
||||
# Calculate hash for a file
|
||||
calculate_hash() {
|
||||
local file=$1
|
||||
shasum -a 256 "$file" | awk '{print $1}' | xxd -r -p | base64
|
||||
}
|
||||
|
||||
# Calculate hash for inline style
|
||||
calculate_inline_hash() {
|
||||
local style_content=$1
|
||||
echo -n "$style_content" | shasum -a 256 | awk '{print $1}' | xxd -r -p | base64
|
||||
}
|
||||
|
||||
# Process JavaScript files
|
||||
echo "Processing JavaScript files..."
|
||||
for js_file in $(find "$BASE_DIR" -name "*.js" -type f); do
|
||||
echo "Processing $js_file"
|
||||
file_name=$(basename "$js_file")
|
||||
hash=$(calculate_hash "$js_file")
|
||||
SCRIPT_HASHES+=("'sha256-$hash'")
|
||||
|
||||
# Update HTML files that reference this JS file
|
||||
for html_file in $(find "$BASE_DIR" -name "*.html" -type f); do
|
||||
if grep -q "$file_name" "$html_file"; then
|
||||
echo "Updating $file_name in $html_file"
|
||||
|
||||
# Create a temporary file for the replacement
|
||||
tmp_file=$(mktemp)
|
||||
|
||||
# For files with existing integrity attribute
|
||||
if grep -q "$file_name.*integrity" "$html_file"; then
|
||||
# Use awk for safer text processing
|
||||
awk -v fname="$file_name" -v newhash="$hash" '
|
||||
{
|
||||
if ($0 ~ fname && $0 ~ /integrity/) {
|
||||
gsub(/integrity="sha256-[^"]*"/, "integrity=\"sha256-" newhash "\"");
|
||||
}
|
||||
print;
|
||||
}' "$html_file" > "$tmp_file"
|
||||
else
|
||||
# Add integrity attribute if it doesn't exist
|
||||
awk -v fname="$file_name" -v newhash="$hash" '
|
||||
{
|
||||
if ($0 ~ fname && $0 ~ /src/ && !($0 ~ /integrity/)) {
|
||||
gsub(/src="[^"]*"/, "&" " integrity=\"sha256-" newhash "\"");
|
||||
}
|
||||
print;
|
||||
}' "$html_file" > "$tmp_file"
|
||||
fi
|
||||
|
||||
# Replace original file with modified content
|
||||
mv "$tmp_file" "$html_file"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# Process CSS files
|
||||
echo "Processing CSS files..."
|
||||
for css_file in $(find "$BASE_DIR" -name "*.css" -type f); do
|
||||
echo "Processing $css_file"
|
||||
file_name=$(basename "$css_file")
|
||||
hash=$(calculate_hash "$css_file")
|
||||
STYLE_HASHES+=("'sha256-$hash'")
|
||||
|
||||
# Update HTML files that reference this CSS file
|
||||
for html_file in $(find "$BASE_DIR" -name "*.html" -type f); do
|
||||
if grep -q "$file_name" "$html_file"; then
|
||||
echo "Updating $file_name in $html_file"
|
||||
|
||||
# Create a temporary file for the replacement
|
||||
tmp_file=$(mktemp)
|
||||
|
||||
# For files with existing integrity attribute
|
||||
if grep -q "$file_name.*integrity" "$html_file"; then
|
||||
# Use awk for safer text processing
|
||||
awk -v fname="$file_name" -v newhash="$hash" '
|
||||
{
|
||||
if ($0 ~ fname && $0 ~ /integrity/) {
|
||||
gsub(/integrity="sha256-[^"]*"/, "integrity=\"sha256-" newhash "\"");
|
||||
}
|
||||
print;
|
||||
}' "$html_file" > "$tmp_file"
|
||||
else
|
||||
# Add integrity attribute if it doesn't exist
|
||||
awk -v fname="$file_name" -v newhash="$hash" '
|
||||
{
|
||||
if ($0 ~ fname && $0 ~ /href/ && !($0 ~ /integrity/)) {
|
||||
gsub(/href="[^"]*"/, "&" " integrity=\"sha256-" newhash "\"");
|
||||
}
|
||||
print;
|
||||
}' "$html_file" > "$tmp_file"
|
||||
fi
|
||||
|
||||
# Replace original file with modified content
|
||||
mv "$tmp_file" "$html_file"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# Find and process inline styles - using a more thorough approach
|
||||
echo "Processing HTML files for inline styles..."
|
||||
find "$BASE_DIR" -name "*.html" -type f | while read -r html_file; do
|
||||
echo "Processing $html_file for inline styles..."
|
||||
|
||||
# Use a more comprehensive grep pattern to catch all inline styles
|
||||
# This includes both style="..." and style = "..." patterns
|
||||
grep -o 'style\s*=\s*"[^"]*"' "$html_file" | sed 's/style\s*=\s*"\(.*\)"/\1/' | while read -r style_content; do
|
||||
if [ -n "$style_content" ]; then
|
||||
hash=$(calculate_inline_hash "$style_content")
|
||||
echo "Found inline style: '$style_content'"
|
||||
echo "Calculated hash: sha256-$hash"
|
||||
echo "'sha256-$hash'" >> "$TEMP_INLINE_HASHES_FILE"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# Sort and remove duplicates from inline style hashes
|
||||
if [ -f "$TEMP_INLINE_HASHES_FILE" ]; then
|
||||
sort -u "$TEMP_INLINE_HASHES_FILE" > "${TEMP_INLINE_HASHES_FILE}.sorted"
|
||||
mv "${TEMP_INLINE_HASHES_FILE}.sorted" "$TEMP_INLINE_HASHES_FILE"
|
||||
|
||||
# Add inline style hashes to the STYLE_HASHES array
|
||||
while read -r hash; do
|
||||
STYLE_HASHES+=("$hash")
|
||||
done < "$TEMP_INLINE_HASHES_FILE"
|
||||
|
||||
# Clean up
|
||||
rm -f "$TEMP_INLINE_HASHES_FILE"
|
||||
fi
|
||||
|
||||
# Combine all hashes for CSP
|
||||
echo "Updating Caddyfile CSP headers..."
|
||||
SCRIPT_HASHES_STR=$(printf " %s" "${SCRIPT_HASHES[@]}")
|
||||
STYLE_HASHES_STR=$(printf " %s" "${STYLE_HASHES[@]}")
|
||||
|
||||
# Create the CSP string
|
||||
CSP_STRING="default-src 'none'; script-src 'self'$SCRIPT_HASHES_STR; style-src 'self'$STYLE_HASHES_STR; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
|
||||
|
||||
# Create a temporary file for the Caddyfile update
|
||||
tmp_file=$(mktemp)
|
||||
|
||||
# Update CSP in Caddyfile using awk for more reliable text processing
|
||||
awk -v csp_string="$CSP_STRING" '
|
||||
{
|
||||
if ($0 ~ /Content-Security-Policy/) {
|
||||
gsub(/Content-Security-Policy "[^"]*"/, "Content-Security-Policy \"" csp_string "\"");
|
||||
}
|
||||
print;
|
||||
}' "$CADDYFILE" > "$tmp_file"
|
||||
|
||||
# Replace original Caddyfile with modified content
|
||||
mv "$tmp_file" "$CADDYFILE"
|
||||
|
||||
# Also update Caddyfile.local if it exists
|
||||
if [ -f "$BASE_DIR/Caddyfile.local" ]; then
|
||||
echo "Updating Caddyfile.local CSP headers..."
|
||||
tmp_file=$(mktemp)
|
||||
|
||||
awk -v csp_string="$CSP_STRING" '
|
||||
{
|
||||
if ($0 ~ /Content-Security-Policy/) {
|
||||
gsub(/Content-Security-Policy "[^"]*"/, "Content-Security-Policy \"" csp_string "\"");
|
||||
}
|
||||
print;
|
||||
}' "$BASE_DIR/Caddyfile.local" > "$tmp_file"
|
||||
|
||||
# Replace original Caddyfile.local with modified content
|
||||
mv "$tmp_file" "$BASE_DIR/Caddyfile.local"
|
||||
fi
|
||||
|
||||
# Add CSP meta tags to HTML files
|
||||
echo "Adding CSP meta tags to HTML files..."
|
||||
for html_file in $(find "$BASE_DIR" -name "*.html" -type f); do
|
||||
echo "Adding CSP meta tag to $html_file"
|
||||
|
||||
# Create a temporary file for the replacement
|
||||
tmp_file=$(mktemp)
|
||||
|
||||
# Check if the file already has a CSP meta tag
|
||||
if grep -q '<meta http-equiv="Content-Security-Policy"' "$html_file"; then
|
||||
# Update existing CSP meta tag
|
||||
awk -v csp_string="$CSP_STRING" '
|
||||
{
|
||||
if ($0 ~ /<meta http-equiv="Content-Security-Policy"/) {
|
||||
gsub(/<meta http-equiv="Content-Security-Policy" content="[^"]*"/, "<meta http-equiv=\"Content-Security-Policy\" content=\"" csp_string "\"");
|
||||
}
|
||||
print;
|
||||
}' "$html_file" > "$tmp_file"
|
||||
else
|
||||
# Add CSP meta tag after the last meta tag
|
||||
awk -v csp_string="$CSP_STRING" '
|
||||
{
|
||||
print;
|
||||
if ($0 ~ /<\/head>/ && !added_csp) {
|
||||
print " <meta http-equiv=\"Content-Security-Policy\" content=\"" csp_string "\">";
|
||||
added_csp = 1;
|
||||
}
|
||||
}' "$html_file" > "$tmp_file"
|
||||
fi
|
||||
|
||||
# Replace original file with modified content
|
||||
mv "$tmp_file" "$html_file"
|
||||
done
|
||||
|
||||
echo "CSP hashes updated successfully!"
|
||||
echo "To apply changes, restart the server using: ./caddy.sh"
|
Loading…
Reference in New Issue