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__/
|
||||
.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/
|
||||
.ruff_cache/
|
||||
.pdm-python
|
||||
.coverage
|
||||
.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]
|
||||
name = "judas_server"
|
||||
version = "0.1.0"
|
||||
description = "Default template for PDM package"
|
||||
authors = [
|
||||
{name = "Artur Borecki", email = "me@pufereq.pl"},
|
||||
]
|
||||
dependencies = []
|
||||
requires-python = ">=3.13"
|
||||
description = "The backbone of the remote PC fleet management system."
|
||||
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]
|
||||
distribution = false
|
||||
[tool.ruff]
|
||||
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