Compare commits
17 Commits
324c4046f7
...
build/migr
| Author | SHA1 | Date | |
|---|---|---|---|
|
de463b8c71
|
|||
|
30d94348d7
|
|||
|
c6e9df1f07
|
|||
|
b4b0e91450
|
|||
|
8968d7ec08
|
|||
|
a5c1490aca
|
|||
|
40d0edd507
|
|||
|
04d4e97f62
|
|||
|
368769e1a7
|
|||
|
6ba91e5ba6
|
|||
|
b125a3008f
|
|||
|
e4620c81b2
|
|||
|
1b39dce698
|
|||
|
9edae70618
|
|||
|
9acc788b34
|
|||
|
1ab8bb866d
|
|||
|
7de428c475
|
121
.github/workflows/release.yaml
vendored
Normal file
121
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
name: Bump version
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
short_description:
|
||||||
|
type: string
|
||||||
|
description: 'Short description of the release'
|
||||||
|
required: false
|
||||||
|
auto_bump:
|
||||||
|
type: boolean
|
||||||
|
description: 'Auto bump version?'
|
||||||
|
required: true
|
||||||
|
bump_type:
|
||||||
|
type: choice
|
||||||
|
description: 'Bump type'
|
||||||
|
required: true
|
||||||
|
options:
|
||||||
|
- 'major'
|
||||||
|
- 'minor'
|
||||||
|
- 'patch'
|
||||||
|
- 'prerelease'
|
||||||
|
as_pre_release:
|
||||||
|
type: boolean
|
||||||
|
description: 'As pre-release?'
|
||||||
|
required: true
|
||||||
|
prerelease_type:
|
||||||
|
type: choice
|
||||||
|
description: 'Pre-release label'
|
||||||
|
required: true
|
||||||
|
options:
|
||||||
|
- 'dev'
|
||||||
|
- 'alpha'
|
||||||
|
- 'beta'
|
||||||
|
- 'rc'
|
||||||
|
- 'post'
|
||||||
|
jobs:
|
||||||
|
bump_version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Bump version and create release
|
||||||
|
steps:
|
||||||
|
- name: 🔀 checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
submodules: 'recursive'
|
||||||
|
|
||||||
|
- name: 📦 install uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
|
||||||
|
- name: 🐍 python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version-file: "pyproject.toml"
|
||||||
|
|
||||||
|
- name: 📦 sync depedencies
|
||||||
|
run: |
|
||||||
|
uv sync
|
||||||
|
uv sync --group bump
|
||||||
|
|
||||||
|
- name: configure git
|
||||||
|
run: |
|
||||||
|
git config --global user.name "github-actions[bot]"
|
||||||
|
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
|
- name: 📦 bump version (auto)
|
||||||
|
if: ${{ github.event.inputs.auto_bump == 'true' }}
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event.inputs.as_pre_release }}" == "true" ]; then
|
||||||
|
uv run semantic-release version --no-changelog --no-commit --no-push --as-prerelease --prerelease-token ${{ github.event.inputs.prerelease_type }}
|
||||||
|
else
|
||||||
|
uv run semantic-release version --no-changelog --no-commit --no-push
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: 📦 bump version (manual)
|
||||||
|
if: ${{ github.event.inputs.auto_bump == 'false' }}
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event.inputs.as_pre_release }}" == "true" ]; then
|
||||||
|
uv run semantic-release version --no-changelog --no-commit --no-push --${{ github.event.inputs.bump_type }} --as-prerelease --prerelease-token ${{ github.event.inputs.prerelease_type }}
|
||||||
|
else
|
||||||
|
uv run semantic-release version --no-changelog --no-commit --no-push --${{ github.event.inputs.bump_type }}
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: 📦 get new version
|
||||||
|
id: get_version
|
||||||
|
run: echo "BUMPED_VERSION=$(cat pyproject.toml | grep -e "^version = " | cut -d '"' -f 2)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: 📝 create changelog
|
||||||
|
run: |
|
||||||
|
uv run git-cliff -s header -l > release_body.md
|
||||||
|
echo "Changes:" && cat release_body.md
|
||||||
|
uv run git-cliff > CHANGELOG.md
|
||||||
|
|
||||||
|
- name: 📤 push changes
|
||||||
|
run: |
|
||||||
|
git add CHANGELOG.md pyproject.toml src/*/__init__.py
|
||||||
|
git commit -m "chore(release): ${{ steps.get_version.outputs.BUMPED_VERSION }}"
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
- name: create release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
name: ${{ github.event.repository.name }} ${{ steps.get_version.outputs.BUMPED_VERSION }} - ${{ github.event.inputs.short_description }}
|
||||||
|
tag_name: ${{ steps.get_version.outputs.BUMPED_VERSION }}
|
||||||
|
body_path: release_body.md
|
||||||
|
prerelease: ${{ github.event.inputs.as_pre_release }}
|
||||||
|
discussion_category_name: 'Announcements'
|
||||||
|
|
||||||
|
- name: 🧹 cleanup
|
||||||
|
run: |
|
||||||
|
rm release_body.md
|
||||||
|
|
||||||
|
- name: 📤 merge into develop
|
||||||
|
run: |
|
||||||
|
git checkout develop
|
||||||
|
git merge main
|
||||||
|
git push origin develop
|
||||||
|
|
||||||
|
- name: ✅ done
|
||||||
|
run: echo "done"
|
||||||
49
.gitignore
vendored
49
.gitignore
vendored
@@ -1,6 +1,47 @@
|
|||||||
.py[cod]
|
# VSCode stuff
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
.mypy_cache/
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# PDM
|
||||||
|
.pdm*
|
||||||
|
!pdm.lock
|
||||||
|
|
||||||
|
# uv
|
||||||
|
.python-version
|
||||||
|
!uv.lock
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
/*cache/
|
||||||
|
|
||||||
|
# Tests
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.ruff_cache/
|
.coverage
|
||||||
.pdm-python
|
.htmlcov/
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Sphinx
|
||||||
|
docs/_build/
|
||||||
|
docs/ref/modules/
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"python.analysis.autoImportCompletions": true
|
||||||
|
}
|
||||||
134
CODE_OF_CONDUCT.md
Normal file
134
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||||
|
identity and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the overall
|
||||||
|
community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
|
any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email address,
|
||||||
|
without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
[INSERT CONTACT METHOD].
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series of
|
||||||
|
actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or permanent
|
||||||
|
ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||||
|
community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.1, available at
|
||||||
|
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by
|
||||||
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||||
|
[https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
|
|
||||||
83
cliff.toml
Normal file
83
cliff.toml
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# git-cliff ~ default configuration file
|
||||||
|
# https://git-cliff.org/docs/configuration
|
||||||
|
#
|
||||||
|
# Lines starting with "#" are comments.
|
||||||
|
# Configuration options are organized into tables and keys.
|
||||||
|
# See documentation for more information on available options.
|
||||||
|
|
||||||
|
[changelog]
|
||||||
|
# changelog header
|
||||||
|
header = """
|
||||||
|
# Changelog\n
|
||||||
|
All notable changes to this project will be documented in this file.\n
|
||||||
|
"""
|
||||||
|
# template for the changelog body
|
||||||
|
# https://keats.github.io/tera/docs/#introduction
|
||||||
|
body = """
|
||||||
|
{% if version %}\
|
||||||
|
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||||
|
{% else %}\
|
||||||
|
## [unreleased]
|
||||||
|
{% endif %}\
|
||||||
|
{% for group, commits in commits | group_by(attribute="group") %}
|
||||||
|
### {{ group | upper_first }}
|
||||||
|
{% for commit in commits %}
|
||||||
|
- [`{{ commit.id|truncate(length=7, end="") }}`](<REPO>/commit/{{ commit.id }}) {% if commit.scope %}**{{ commit.scope }}**: {% endif %}{% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message}}\
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}\n
|
||||||
|
"""
|
||||||
|
# remove the leading and trailing whitespace from the template
|
||||||
|
trim = true
|
||||||
|
# changelog footer
|
||||||
|
footer = """
|
||||||
|
<!-- generated by git-cliff -->
|
||||||
|
"""
|
||||||
|
# postprocessors
|
||||||
|
postprocessors = [
|
||||||
|
{ pattern = '<REPO>', replace = "https://github.com/pufereq/template-repo" }, # replace repository URL
|
||||||
|
]
|
||||||
|
[git]
|
||||||
|
# parse the commits based on https://www.conventionalcommits.org
|
||||||
|
conventional_commits = true
|
||||||
|
# filter out the commits that are not conventional
|
||||||
|
filter_unconventional = true
|
||||||
|
# process each line of a commit as an individual commit
|
||||||
|
split_commits = false
|
||||||
|
# regex for preprocessing the commit messages
|
||||||
|
commit_preprocessors = [
|
||||||
|
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"}, # replace issue numbers
|
||||||
|
]
|
||||||
|
# regex for parsing and grouping commits
|
||||||
|
commit_parsers = [
|
||||||
|
{ message = "^feat", group = "Features" },
|
||||||
|
{ message = "^fix", group = "Bug Fixes" },
|
||||||
|
{ message = "^doc", group = "Documentation" },
|
||||||
|
{ message = "^perf", group = "Performance" },
|
||||||
|
{ message = "^refactor", group = "Refactor" },
|
||||||
|
{ message = "^style", group = "Styling" },
|
||||||
|
{ message = "^test", group = "Testing" },
|
||||||
|
{ message = "^chore\\(release\\)", skip = true },
|
||||||
|
{ message = "^chore\\(deps\\)", skip = true },
|
||||||
|
{ message = "^chore\\(pr\\)", skip = true },
|
||||||
|
{ message = "^chore\\(pull\\)", skip = true },
|
||||||
|
{ message = "^chore|ci", group = "Miscellaneous Tasks" },
|
||||||
|
{ body = ".*security", group = "Security" },
|
||||||
|
{ message = "^revert", group = "Revert" },
|
||||||
|
]
|
||||||
|
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
||||||
|
protect_breaking_commits = false
|
||||||
|
# filter out the commits that are not matched by commit parsers
|
||||||
|
filter_commits = false
|
||||||
|
# regex for matching git tags
|
||||||
|
tag_pattern = "[0-9].*"
|
||||||
|
|
||||||
|
# regex for skipping tags
|
||||||
|
skip_tags = "v0.1.0-beta.1"
|
||||||
|
# regex for ignoring tags
|
||||||
|
ignore_tags = ""
|
||||||
|
# sort the tags topologically
|
||||||
|
topo_order = false
|
||||||
|
# sort the commits inside sections by oldest/newest order
|
||||||
|
sort_commits = "newest"
|
||||||
|
# limit the number of commits included in the changelog.
|
||||||
|
# limit_commits = 42
|
||||||
@@ -1,15 +1,80 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["uv_build>=0.7.15,<0.8.0"]
|
||||||
|
build-backend = "uv_build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "judas_server"
|
name = "judas_server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Default template for PDM package"
|
description = "The backbone of the remote PC fleet management system."
|
||||||
authors = [
|
|
||||||
{name = "Artur Borecki", email = "me@pufereq.pl"},
|
|
||||||
]
|
|
||||||
dependencies = []
|
|
||||||
requires-python = ">=3.13"
|
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = {text = "GPL-3.0"}
|
authors = []
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = ["flask>=3.1.1", "flask-login>=0.6.3"]
|
||||||
|
license = { text = "GPL-3.0+" }
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
bump = ["git-cliff>=2.9.1", "python-semantic-release>=10.2.0"]
|
||||||
|
test = [
|
||||||
|
"pytest>=4.2.1",
|
||||||
|
"pytest-cov>=6.2.1",
|
||||||
|
"pytest-github-actions-annotate-failures>=0.3.0",
|
||||||
|
"pytest-mock>=3.14.1",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.pdm]
|
[tool.ruff]
|
||||||
distribution = false
|
line-length = 79
|
||||||
|
exclude = ["tests/*"]
|
||||||
|
|
||||||
|
[tool.ruff.lint.pycodestyle]
|
||||||
|
max-line-length = 79
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"__init__.py" = ["E402", "F401"]
|
||||||
|
"**/{tests,docs,tools}/*" = ["E402"]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
docstring-code-format = true
|
||||||
|
docstring-code-line-length = 72
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
addopts = [
|
||||||
|
"--cov-report=term-missing",
|
||||||
|
"--cov-report=html",
|
||||||
|
"--cov-fail-under=100",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
omit = ["src/judas_server/__init__.py", "examples/*", "docs/*", "tests/*"]
|
||||||
|
|
||||||
|
[tool.semantic_release]
|
||||||
|
version_variables = ["src/judas_server/__init__.py:__version__"]
|
||||||
|
version_toml = ["pyproject.toml:project.version"]
|
||||||
|
build_command_env = []
|
||||||
|
commit_message = "chore(release): {version}\n\nAutomatically generated by python-semantic-release"
|
||||||
|
commit_parser = "conventional"
|
||||||
|
logging_use_named_masks = false
|
||||||
|
major_on_zero = true
|
||||||
|
allow_zero_version = true
|
||||||
|
no_git_verify = false
|
||||||
|
tag_format = "{version}"
|
||||||
|
|
||||||
|
[tool.semantic_release.commit_author]
|
||||||
|
env = "GIT_COMMIT_AUTHOR"
|
||||||
|
default = "semantic-release <semantic-release>"
|
||||||
|
|
||||||
|
[tool.semantic_release.commit_parser_options]
|
||||||
|
allowed_tags = [
|
||||||
|
"build",
|
||||||
|
"chore",
|
||||||
|
"ci",
|
||||||
|
"docs",
|
||||||
|
"feat",
|
||||||
|
"fix",
|
||||||
|
"perf",
|
||||||
|
"style",
|
||||||
|
"refactor",
|
||||||
|
"test",
|
||||||
|
]
|
||||||
|
minor_tags = ["feat"]
|
||||||
|
patch_tags = ["fix", "perf"]
|
||||||
|
default_bump_level = 0
|
||||||
|
|||||||
20
src/judas_server/web/static/css/style.css
Normal file
20
src/judas_server/web/static/css/style.css
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #2e3440;
|
||||||
|
color: #eceff4;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 2rem;
|
||||||
|
background-color: #eceff4;
|
||||||
|
color: #2e3440;
|
||||||
|
}
|
||||||
40
src/judas_server/web/templates/details.html
Normal file
40
src/judas_server/web/templates/details.html
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>judas panel - details</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="wrapper">
|
||||||
|
<header>
|
||||||
|
<h1><a href="{{ url_for('panel') }}">judas panel</a></h1>
|
||||||
|
<p>Welcome, {{ username }}! <a href="{{ url_for('logout') }}">Logout</a></p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<h2>Details for PC ID: {{ pc.id }}</h2>
|
||||||
|
<h3><a href="{{ url_for('stream', pc_id=pc.id) }}">View Stream</a></h3>
|
||||||
|
<table border="1">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Attribute</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Status</td>
|
||||||
|
<td>{{ pc.status }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Last Seen</td>
|
||||||
|
<td>{{ pc.last_seen }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><a href="{{ url_for('panel') }}">Back to Panel</a></p>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
21
src/judas_server/web/templates/index.html
Normal file
21
src/judas_server/web/templates/index.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>judas</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="wrapper">
|
||||||
|
<header>
|
||||||
|
<h1>judas</h1>
|
||||||
|
{% if logged %}
|
||||||
|
<p>Welcome, {{ username }}! <a href="{{ url_for('logout') }}">Logout</a></p>
|
||||||
|
{% else %}
|
||||||
|
<p><a href="{{ url_for('login') }}">Login</a></p>
|
||||||
|
{% endif %}
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
41
src/judas_server/web/templates/panel.html
Normal file
41
src/judas_server/web/templates/panel.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>judas panel</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="wrapper">
|
||||||
|
<header>
|
||||||
|
<h1><a href="{{ url_for('panel') }}">judas panel</a></h1>
|
||||||
|
<p>Welcome, {{ username }}! <a href="{{ url_for('logout') }}">Logout</a></p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<table border="1">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>PC ID</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Last Seen</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for pc in pcs.values() %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{ url_for('details', pc_id=pc.id) }}">{{ pc.id }}</a></td>
|
||||||
|
<td>{{ pc.status if pc.status else 'Unknown' }}</td>
|
||||||
|
<td>{{ pc.last_seen if pc.last_seen else 'Never' }}</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">No PCs found.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
src/judas_server/web/templates/stream.html
Normal file
16
src/judas_server/web/templates/stream.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>judas panel - stream</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="wrapper">
|
||||||
|
<header>
|
||||||
|
<h1><a href="{{ url_for('panel') }}">judas panel</a></h1>
|
||||||
|
<p>Welcome, {{ username }}! <a href="{{ url_for('logout') }}">Logout</a></p>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
152
src/judas_server/web/web_server.py
Normal file
152
src/judas_server/web/web_server.py
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
import flask
|
||||||
|
import flask_login
|
||||||
|
|
||||||
|
PASSWORD: Final[str] = "123"
|
||||||
|
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.secret_key = "dildo"
|
||||||
|
|
||||||
|
login_manager = flask_login.LoginManager()
|
||||||
|
login_manager.init_app(app)
|
||||||
|
|
||||||
|
|
||||||
|
PC_DETAILS = {
|
||||||
|
"PC1": {
|
||||||
|
"id": "PC1",
|
||||||
|
"status": "Online",
|
||||||
|
"last_seen": "2023-10-01 12:00:00",
|
||||||
|
},
|
||||||
|
"PC2": {
|
||||||
|
"id": "PC2",
|
||||||
|
"status": "Offline",
|
||||||
|
"last_seen": "2023-10-01 11:00:00",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class User(flask_login.UserMixin):
|
||||||
|
"""Represents a user for authentication purposes."""
|
||||||
|
|
||||||
|
def __init__(self, id: str):
|
||||||
|
super().__init__()
|
||||||
|
self.id = id
|
||||||
|
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(user_id: str) -> User | None:
|
||||||
|
"""Loads a user by user_id.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: The ID of the user.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The User object if found, else None.
|
||||||
|
"""
|
||||||
|
if user_id == "admin":
|
||||||
|
return User("admin")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index() -> str:
|
||||||
|
"""Renders the index page with a link to the login page."""
|
||||||
|
if flask_login.current_user.is_authenticated:
|
||||||
|
return flask.render_template(
|
||||||
|
"index.html", logged=True, username=flask_login.current_user.id
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return flask.render_template(
|
||||||
|
"index.html", logged=False, login_url=flask.url_for("login")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/login", methods=["GET", "POST"])
|
||||||
|
def login() -> str:
|
||||||
|
"""Handles user login via password form."""
|
||||||
|
if flask.request.method == "POST":
|
||||||
|
password = flask.request.form.get("password", "")
|
||||||
|
if password == PASSWORD:
|
||||||
|
user = User("admin")
|
||||||
|
flask_login.login_user(user)
|
||||||
|
return flask.redirect(flask.url_for("panel"))
|
||||||
|
else:
|
||||||
|
return "Invalid password", 401
|
||||||
|
return """
|
||||||
|
<form method="post">
|
||||||
|
Password: <input type="password" name="password">
|
||||||
|
<input type="submit" value="Login">
|
||||||
|
</form>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/logout")
|
||||||
|
@flask_login.login_required
|
||||||
|
def logout() -> str:
|
||||||
|
"""Logs out the current user."""
|
||||||
|
flask_login.logout_user()
|
||||||
|
return flask.redirect(flask.url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/panel")
|
||||||
|
@flask_login.login_required
|
||||||
|
def panel() -> str:
|
||||||
|
"""Renders the main panel page with PC details.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Rendered HTML template with PC details.
|
||||||
|
"""
|
||||||
|
return flask.render_template(
|
||||||
|
"panel.html",
|
||||||
|
username=flask_login.current_user.id,
|
||||||
|
pcs=PC_DETAILS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/details/<pc_id>")
|
||||||
|
@flask_login.login_required
|
||||||
|
def details(pc_id: str) -> str:
|
||||||
|
"""Renders the details page for a specific PC.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pc_id: The ID of the PC to display details for.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Rendered HTML template with PC details.
|
||||||
|
"""
|
||||||
|
return flask.render_template(
|
||||||
|
"details.html",
|
||||||
|
username=flask_login.current_user.id,
|
||||||
|
pc=PC_DETAILS[pc_id],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/stream/<pc_id>")
|
||||||
|
@flask_login.login_required
|
||||||
|
def stream(pc_id: str) -> str:
|
||||||
|
"""Renders the stream page for a specific PC.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pc_id: The ID of the PC to stream from.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Rendered HTML template for streaming.
|
||||||
|
"""
|
||||||
|
return flask.render_template(
|
||||||
|
logged=True,
|
||||||
|
username=flask_login.current_user.id,
|
||||||
|
pc=PC_DETAILS[pc_id],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/stream_feed/<pc_id>")
|
||||||
|
@flask_login.login_required
|
||||||
|
def stream_feed(pc_id: str) -> flask.Response:
|
||||||
|
return flask.Response(mimetype="multipart/x-mixed-replace; boundary=frame")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True)
|
||||||
Reference in New Issue
Block a user