Initial commit.

This commit is contained in:
Guilherme C. T. 2025-05-20 23:50:10 -06:00
commit 73096b465d
8 changed files with 928 additions and 0 deletions

181
.gitignore vendored Normal file
View File

@ -0,0 +1,181 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore

28
app.py Normal file
View File

@ -0,0 +1,28 @@
# app.py
from flask import Flask, render_template, url_for
app = Flask(__name__)
@app.route("/")
def index():
return render_template("index.html")
@app.route("/chmod")
def chmod():
return render_template("chmod.html")
@app.route("/cron")
def cron():
return render_template("cron.html")
@app.route("/json")
def json():
return render_template("json.html")
@app.route("/yaml")
def yaml():
return render_template("yaml.html")
if __name__ == "__main__":
app.run(debug=True)

7
requirements.txt Normal file
View File

@ -0,0 +1,7 @@
blinker==1.9.0
click==8.2.1
Flask==3.1.1
itsdangerous==2.2.0
Jinja2==3.1.6
MarkupSafe==3.0.2
Werkzeug==3.1.3

142
templates/chmod.html Normal file
View File

@ -0,0 +1,142 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>KPR chmod - Calculadora de permisos</title>
<style>
.container {
display: flex;
flex-direction: column;
width: 50%;
margin: 2rem auto;
}
.row {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
.row label {
width: 5rem;
}
.bits {
display: flex;
gap: 1rem;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<label for="octal">Octal:</label>
<input id="octal" type="number" min="0" max="777" step="1" />
</div>
<div class="row">
<label>Owner:</label>
<div class="bits">
<label><input type="checkbox" id="owner-read" /> r</label>
<label><input type="checkbox" id="owner-write" /> w</label>
<label><input type="checkbox" id="owner-execute" /> x</label>
</div>
</div>
<div class="row">
<label>Group:</label>
<div class="bits">
<label><input type="checkbox" id="group-read" /> r</label>
<label><input type="checkbox" id="group-write" /> w</label>
<label><input type="checkbox" id="group-execute" /> x</label>
</div>
</div>
<div class="row">
<label>Others:</label>
<div class="bits">
<label><input type="checkbox" id="others-read" /> r</label>
<label><input type="checkbox" id="others-write" /> w</label>
<label><input type="checkbox" id="others-execute" /> x</label>
</div>
</div>
</div>
<script>
// grab references
const octalInput = document.getElementById('octal');
const bits = {
owner: {
read: document.getElementById('owner-read'),
write: document.getElementById('owner-write'),
execute: document.getElementById('owner-execute'),
},
group: {
read: document.getElementById('group-read'),
write: document.getElementById('group-write'),
execute: document.getElementById('group-execute'),
},
others: {
read: document.getElementById('others-read'),
write: document.getElementById('others-write'),
execute: document.getElementById('others-execute'),
}
};
// helper: from digit 07 set the three checkboxes
function setBitsFromDigit(digit, checkboxes) {
checkboxes.read.checked = !!(digit & 4);
checkboxes.write.checked = !!(digit & 2);
checkboxes.execute.checked = !!(digit & 1);
}
// helper: read three checkboxes to produce 07
function getDigitFromBits(checkboxes) {
return (checkboxes.read.checked ? 4 : 0)
+ (checkboxes.write.checked ? 2 : 0)
+ (checkboxes.execute.checked ? 1 : 0);
}
// when octal input changes, update all checkboxes
function updateCheckboxesFromOctal() {
let v = octalInput.value.toString();
// pad or trim to exactly 3 digits
v = v.padStart(3, '0').slice(-3);
const ownerDigit = parseInt(v[0], 10) || 0;
const groupDigit = parseInt(v[1], 10) || 0;
const othersDigit = parseInt(v[2], 10) || 0;
setBitsFromDigit(ownerDigit, bits.owner);
setBitsFromDigit(groupDigit, bits.group);
setBitsFromDigit(othersDigit, bits.others);
}
// when any checkbox changes, recompute octal
function updateOctalFromCheckboxes() {
const owner = getDigitFromBits(bits.owner);
const group = getDigitFromBits(bits.group);
const others = getDigitFromBits(bits.others);
octalInput.value = `${owner}${group}${others}`;
}
// wire up events
octalInput.addEventListener('input', updateCheckboxesFromOctal);
for (const scope of Object.values(bits)) {
for (const cb of Object.values(scope)) {
cb.addEventListener('change', updateOctalFromCheckboxes);
}
}
// initialize on load
window.addEventListener('DOMContentLoaded', () => {
// default to 644
octalInput.value = '644';
updateCheckboxesFromOctal();
});
</script>
</body>
</html>

212
templates/cron.html Normal file
View File

@ -0,0 +1,212 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Cronjob Syntax Helper</title>
<style>
body {
font-family: sans-serif;
padding: 1rem;
}
h1 {
text-align: center;
margin-bottom: 1rem;
}
.container {
display: flex;
gap: 2rem;
width: 60%;
/* margin: auto; */
}
.left,
.right {
flex: 1;
}
.field-row {
margin-bottom: 0.75rem;
display: flex;
align-items: center;
}
.field-row label {
width: 6rem;
}
.field-row input {
flex: 1;
padding: 0.25rem;
}
.left .field-row input {
/* fix inputs to ~6rem (≈96px) wide */
flex: 0 0 6rem;
width: 6rem;
}
#cron-desc {
padding: 0.5rem;
background: #f2f2f2;
border-radius: 4px;
min-height: 2em;
}
.bottom {
width: 60%;
margin: 1.5rem auto 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.bottom select,
.bottom button {
padding: 0.4rem 0.8rem;
margin-left: 0.5rem;
}
</style>
</head>
<body>
<div class="container">
<!-- Left column: build fields -->
<div class="left">
<div class="field-row">
<label for="minute">Minute</label>
<input type="text" id="minute" placeholder="*" />
</div>
<div class="field-row">
<label for="hour">Hour</label>
<input type="text" id="hour" placeholder="*" />
</div>
<div class="field-row">
<label for="dom">Day of Mo.</label>
<input type="text" id="dom" placeholder="*" />
</div>
<div class="field-row">
<label for="month">Month</label>
<input type="text" id="month" placeholder="*" />
</div>
<div class="field-row">
<label for="dow">Day of Wk.</label>
<input type="text" id="dow" placeholder="*" />
</div>
</div>
<!-- Right column: output -->
<div class="right">
<div class="field-row">
<label for="cron-expr">Cron Expr</label>
<input type="text" id="cron-expr" readonly />
</div>
<div class="field-row">
<label>Description</label>
<div id="cron-desc"></div>
</div>
</div>
</div>
<!-- Presets + Actions -->
<div class="bottom">
<div>
Presets:
<select id="preset">
<option value="">— choose —</option>
<option value="* * * * *">Every minute</option>
<option value="0 * * * *">Every hour</option>
<option value="0 0 * * *">Daily at midnight</option>
<option value="0 0 * * 1">Weekly Mon @ midnight</option>
<option value="0 0 1 * *">Monthly 1st @ midnight</option>
</select>
</div>
<div>
<button id="copy-btn">Copy cron expr</button>
<button id="reset-btn">Reset</button>
</div>
</div>
<script>
// grab elements
const minuteIn = document.getElementById('minute');
const hourIn = document.getElementById('hour');
const domIn = document.getElementById('dom');
const monthIn = document.getElementById('month');
const dowIn = document.getElementById('dow');
const cronExpr = document.getElementById('cron-expr');
const cronDesc = document.getElementById('cron-desc');
const preset = document.getElementById('preset');
const copyBtn = document.getElementById('copy-btn');
const resetBtn = document.getElementById('reset-btn');
// build the cron string
function updateCron() {
const parts = [
minuteIn.value.trim() || '*',
hourIn.value.trim() || '*',
domIn.value.trim() || '*',
monthIn.value.trim() || '*',
dowIn.value.trim() || '*'
];
cronExpr.value = parts.join(' ');
updateDesc(parts);
}
// basic plain-English breakdown
function updateDesc([min, hr, dom, mon, dow]) {
if ([min, hr, dom, mon, dow].every(v => v === '*')) {
cronDesc.textContent = 'Every minute';
return;
}
const parts = [];
if (min !== '*') parts.push(`minute = ${min}`);
if (hr !== '*') parts.push(`hour = ${hr}`);
if (dom !== '*') parts.push(`day of month = ${dom}`);
if (mon !== '*') parts.push(`month = ${mon}`);
if (dow !== '*') parts.push(`day of week = ${dow}`);
cronDesc.textContent = parts.join(', ');
}
// wire inputs
[minuteIn, hourIn, domIn, monthIn, dowIn].forEach(el => {
el.addEventListener('input', () => {
// clear preset selection when typing freeform
preset.value = '';
updateCron();
});
});
// presets
preset.addEventListener('change', () => {
const v = preset.value;
if (!v) return;
const p = v.split(' ');
[minuteIn.value, hourIn.value, domIn.value, monthIn.value, dowIn.value] = p;
updateCron();
});
// copy button
copyBtn.addEventListener('click', () => {
navigator.clipboard.writeText(cronExpr.value)
.then(() => alert('Copied to clipboard!'));
});
// reset button
resetBtn.addEventListener('click', () => {
minuteIn.value = hourIn.value = domIn.value = monthIn.value = dowIn.value = '*';
preset.value = '';
updateCron();
});
// init
window.addEventListener('DOMContentLoaded', () => {
resetBtn.click(); // sets everything to "* * * * *"
});
</script>
</body>
</html>

85
templates/index.html Normal file
View File

@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Helpers Home</title>
<style>
body {
font-family: sans-serif;
padding: 2rem 1rem;
text-align: center;
}
h1 {
margin-bottom: 1.5rem;
}
.panel-container {
display: grid;
/* each column will be at least 360px, but can grow to fill the space */
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
gap: 1rem;
width: 95%;
max-width: 1600px;
margin: 0 auto;
}
/* extra height so tall tools fit without internal scrollbars */
.panel {
min-height: 360px;
}
/* Let wide panels span two columns on wide screens */
.panel.wide {
grid-column: span 2;
}
.panel iframe {
flex: 1;
min-height: 300px;
width: 100%;
}
.panel h2 {
margin: 0;
padding: 0.5rem;
background: #f5f5f5;
font-size: 1rem;
border-bottom: 1px solid #ccc;
}
.panel iframe {
flex: 1;
border: none;
}
</style>
</head>
<body>
<h1>KPR Utils</h1>
<div class="panel-container">
<div class="panel">
<h2>Permissions Calculator</h2>
<iframe src="{{ url_for('chmod') }}" title="chmod helper"></iframe>
</div>
<div class="panel wide">
<h2>Cron Syntax Helper</h2>
<iframe src="{{ url_for('cron') }}" title="cron helper"></iframe>
</div>
<div class="panel wide">
<h2>JSON Validator</h2>
<iframe src="{{ url_for('json') }}" title="json helper"></iframe>
</div>
<div class="panel wide">
<h2>YAML Validator</h2>
<iframe src="{{ url_for('yaml') }}" title="yaml helper"></iframe>
</div>
</div>
</body>
</html>

135
templates/json.html Normal file
View File

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>JSON Formatter &amp; Validator</title>
<style>
body {
font-family: sans-serif;
padding: 1rem;
}
h1 {
text-align: center;
margin-bottom: 1rem;
}
.container {
display: flex;
gap: 1rem;
width: 80%;
margin: auto;
}
.pane {
flex: 1;
display: flex;
flex-direction: column;
}
.pane label {
font-weight: bold;
margin-bottom: 0.25rem;
}
.pane textarea {
flex: 1;
width: 100%;
padding: 0.5rem;
font-family: monospace;
font-size: 0.9rem;
border: 1px solid #ccc;
border-radius: 4px;
resize: vertical;
}
.controls {
margin-top: 0.5rem;
display: flex;
gap: 0.5rem;
}
.controls button {
padding: 0.4rem 0.8rem;
cursor: pointer;
}
.error {
color: #c00;
margin-top: 0.5rem;
min-height: 1.2em;
}
</style>
</head>
<body>
<div class="container">
<!-- Input pane -->
<div class="pane">
<label for="input-json">Input JSON</label>
<textarea id="input-json" placeholder="Paste or type JSON here…"></textarea>
</div>
<!-- Output pane -->
<div class="pane">
<label for="output-json">Formatted JSON</label>
<textarea id="output-json" readonly placeholder="Valid JSON will appear here…"></textarea>
<div id="error" class="error"></div>
<div class="controls">
<button id="copy-btn">Copy Formatted</button>
<button id="clear-btn">Clear All</button>
</div>
</div>
</div>
<script>
const inTA = document.getElementById('input-json');
const outTA = document.getElementById('output-json');
const errDiv = document.getElementById('error');
const copyBtn = document.getElementById('copy-btn');
const clearBtn = document.getElementById('clear-btn');
function formatJSON() {
const text = inTA.value.trim();
if (!text) {
outTA.value = '';
errDiv.textContent = '';
return;
}
try {
const obj = JSON.parse(text);
outTA.value = JSON.stringify(obj, null, 2);
errDiv.textContent = '';
} catch (e) {
outTA.value = '';
errDiv.textContent = '❌ ' + e.message;
}
}
// Live update on input
inTA.addEventListener('input', formatJSON);
// Copy formatted JSON
copyBtn.addEventListener('click', () => {
if (!outTA.value) return;
navigator.clipboard.writeText(outTA.value)
.then(() => alert('Formatted JSON copied!'));
});
// Clear both panes
clearBtn.addEventListener('click', () => {
inTA.value = '';
outTA.value = '';
errDiv.textContent = '';
inTA.focus();
});
// Initialize
window.addEventListener('DOMContentLoaded', () => {
clearBtn.click();
});
</script>
</body>
</html>

138
templates/yaml.html Normal file
View File

@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>YAML Formatter &amp; Validator</title>
<style>
body {
font-family: sans-serif;
padding: 1rem;
}
h1 {
text-align: center;
margin-bottom: 1rem;
}
.container {
display: flex;
gap: 1rem;
width: 80%;
margin: auto;
}
.pane {
flex: 1;
display: flex;
flex-direction: column;
}
.pane label {
font-weight: bold;
margin-bottom: 0.25rem;
}
.pane textarea {
flex: 1;
width: 100%;
padding: 0.5rem;
font-family: monospace;
font-size: 0.9rem;
border: 1px solid #ccc;
border-radius: 4px;
resize: vertical;
}
.controls {
margin-top: 0.5rem;
display: flex;
gap: 0.5rem;
}
.controls button {
padding: 0.4rem 0.8rem;
cursor: pointer;
}
.error {
color: #c00;
margin-top: 0.5rem;
min-height: 1.2em;
}
</style>
</head>
<body>
<div class="container">
<!-- Input pane -->
<div class="pane">
<label for="input-yaml">Input YAML</label>
<textarea id="input-yaml" placeholder="Paste or type YAML here…"></textarea>
</div>
<!-- Output pane -->
<div class="pane">
<label for="output-yaml">Formatted YAML</label>
<textarea id="output-yaml" readonly placeholder="Valid YAML will appear here…"></textarea>
<div id="error-yaml" class="error"></div>
<div class="controls">
<button id="copy-yaml">Copy Formatted</button>
<button id="clear-yaml">Clear All</button>
</div>
</div>
</div>
<!-- js-yaml from CDN -->
<script src="https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"></script>
<script>
const inTA = document.getElementById('input-yaml');
const outTA = document.getElementById('output-yaml');
const errDiv = document.getElementById('error-yaml');
const copyBtn = document.getElementById('copy-yaml');
const clearBtn = document.getElementById('clear-yaml');
function formatYAML() {
const text = inTA.value.trim();
if (!text) {
outTA.value = '';
errDiv.textContent = '';
return;
}
try {
// parse then dump with 2-space indent
const obj = jsyaml.load(text);
outTA.value = jsyaml.dump(obj, { indent: 2 });
errDiv.textContent = '';
} catch (e) {
outTA.value = '';
errDiv.textContent = '❌ ' + e.message;
}
}
// Live update on input
inTA.addEventListener('input', formatYAML);
// Copy formatted YAML
copyBtn.addEventListener('click', () => {
if (!outTA.value) return;
navigator.clipboard.writeText(outTA.value)
.then(() => alert('Formatted YAML copied!'));
});
// Clear both panes
clearBtn.addEventListener('click', () => {
inTA.value = '';
outTA.value = '';
errDiv.textContent = '';
inTA.focus();
});
// Initialize
window.addEventListener('DOMContentLoaded', () => {
clearBtn.click();
});
</script>
</body>
</html>