Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

6.0.4 #53

Merged
merged 78 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
cb9824f
fixed some docstrings
martastain Dec 25, 2023
748bd5f
Merge pull request #45 from nebulabroadcast/main
martastain Jan 3, 2024
cdb1bbe
version bump to 6.0.4
martastain Jan 3, 2024
46e420a
typing fixes
martastain Jan 3, 2024
815cc52
reduce server start-up verbosity
martastain Jan 4, 2024
8fad457
support passing api key as query parameter
martastain Jan 9, 2024
88309df
error rate monitor
martastain Jan 9, 2024
419e1b4
streamlined error handling during endpoint load
martastain Jan 10, 2024
3c856e9
refactor: split browser formatting helpers
martastain Jan 10, 2024
ca31492
basic multiselection
martastain Jan 10, 2024
9e76ccd
multiselect support for send-to
martastain Jan 10, 2024
1bed444
multiselect support for reset
martastain Jan 10, 2024
fd97581
added ErrorBanner component
martastain Jan 10, 2024
768b118
fix: send-to options
martastain Jan 10, 2024
c2c9f1b
chore: formatting fixes
martastain Jan 12, 2024
cd58ed2
fix: type hints in Messaging background task
martastain Jan 12, 2024
5e86b01
Merge pull request #46 from nebulabroadcast/enhancement/browser_multi…
martastain Jan 12, 2024
d8578d2
chore: fixed jobs model examples
martastain Jan 12, 2024
6dd7c2d
allow users without a password
martastain Jan 12, 2024
367cbba
fix: missing empty password condition
martastain Jan 12, 2024
44057d8
Browser context menu
martastain Jan 13, 2024
cd0078e
content actions confirmation dialog
martastain Jan 13, 2024
062a4eb
added icons and separators
martastain Jan 13, 2024
c77be70
clean-up
martastain Jan 13, 2024
568b039
Merge pull request #47 from nebulabroadcast/enhancement/browser_conte…
martastain Jan 13, 2024
b2e6ac4
fix error-rate clean-up
martastain Jan 14, 2024
275bcb5
do not rely on 'message' field in playout responses
martastain Jan 15, 2024
a5193a8
added CI for dev image
martastain Jan 22, 2024
56f5810
Merge branch 'develop' of https://github.com/nebulabroadcast/nebula i…
martastain Jan 22, 2024
1f01cd8
handle object_change event
martastain Jan 22, 2024
b1b0fcb
added id DESC as secondary sorting parameter
martastain Jan 22, 2024
45ab0fb
fix: key props in dropdown/context menu
martastain Jan 22, 2024
3291c72
added hostname column to services page
martastain Jan 22, 2024
642d096
added hostname to service model
martastain Jan 22, 2024
c47f283
Merge branch 'enhancement/handle_object_changed_message' of https://g…
martastain Jan 22, 2024
a21c83b
debounce browser load
martastain Jan 23, 2024
f731ccd
Merge pull request #48 from nebulabroadcast/enhancement/handle_object…
martastain Jan 23, 2024
204207e
typing fixes
martastain Jan 24, 2024
9b020f6
experimental support for granian
martastain Jan 24, 2024
3bd88fd
support for python logging module
martastain Jan 24, 2024
9d3dcb8
doc: object type enum description
martastain Jan 24, 2024
1d4241e
explicit default on settings field to make pyright shut up
martastain Jan 24, 2024
9af6312
do not use streaming response for proxies
martastain Jan 24, 2024
74d2231
more type fixes
martastain Jan 24, 2024
d39c7e5
Merge branch 'develop' into chore/api_docstrings
martastain Jan 25, 2024
08a42d0
Merge pull request #50 from nebulabroadcast/chore/api_docstrings
martastain Jan 25, 2024
6f2bfae
Merge branch 'develop' into refactor/general_clean_up
martastain Feb 3, 2024
8b03909
Merge pull request #49 from nebulabroadcast/refactor/general_clean_up
martastain Feb 3, 2024
cb1069e
Update README.md
martastain Feb 29, 2024
ce06846
migrated cli, api and solver plugins to plugin_library
martastain Mar 1, 2024
ee80aa8
updated python version
martastain Mar 1, 2024
90f84a3
package loading
martastain Mar 1, 2024
4ea38ac
chore: added db class typehints
martastain Mar 6, 2024
04c05f2
chore: more typing fixes
martastain Mar 6, 2024
b38f311
refactored frontend plugins loading
martastain Mar 6, 2024
7fc30d6
fix: logging message while loading packages
martastain Mar 6, 2024
fe9ff18
clean-up
martastain Mar 6, 2024
0aa8fca
typing fixes
martastain Mar 7, 2024
e16c33b
Merge branch 'chore/type_fixes_and_docs' into enhancement/plugin_library
martastain Mar 7, 2024
36b034f
linting
martastain Mar 7, 2024
f90dfea
Merge pull request #51 from nebulabroadcast/enhancement/plugin_library
martastain Mar 7, 2024
2fa565c
updated dependencies, removed black
martastain Mar 7, 2024
451fb48
Merge branch 'develop' into chore/type_fixes_and_docs
martastain Mar 7, 2024
72fd792
ruff format
martastain Mar 7, 2024
1a6bb84
migrate to pydantic2
martastain Mar 7, 2024
95fd901
updated frontend dependencies
martastain Mar 7, 2024
c910a7b
use native html dialog
martastain Mar 7, 2024
8f7d9ec
useConfirm when switching assets
martastain Mar 7, 2024
ad1f45f
json wrapper for redis
martastain Mar 7, 2024
cb69372
more strict linting (wip)
martastain Mar 7, 2024
ec08b21
clean up (wip)
martastain Mar 7, 2024
f535f05
more linting (no errors)
martastain Mar 9, 2024
bf9e439
added sleep while waiting for ws client
martastain Mar 9, 2024
c6bdf17
do not include dev dependencies in docker image
martastain Mar 9, 2024
4241157
Merge pull request #52 from nebulabroadcast/chore/type_fixes_and_docs
martastain Mar 9, 2024
6b44d47
fix: regression after pydantic and fastapi update
martastain Mar 9, 2024
789f639
endpoints clean-up
martastain Mar 12, 2024
675b049
chore: updated make check
martastain Mar 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Publish a new version

on:
push:
branches: [ "develop" ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Get the version
id: get_version
uses: SebRollen/toml-action@v1.0.1
with:
file: 'backend/pyproject.toml'
field: 'tool.poetry.version'

- name: Build docker image
uses: docker/build-push-action@v4
with:
context: .
cache-from: type=gha
cache-to: type=gha,mode=max
push: true
tags: |
nebulabroadcast/nebula-server:dev
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ COPY ./frontend/public /frontend/public
WORKDIR /frontend
RUN yarn install && yarn build

FROM python:3.11-bullseye
FROM python:3.12-bullseye
ENV PYTHONBUFFERED=1

RUN \
Expand All @@ -28,7 +28,7 @@ RUN \
pip install -U pip && \
pip install poetry && \
poetry config virtualenvs.create false && \
poetry install --no-interaction --no-ansi
poetry install --no-interaction --no-ansi --only main

COPY ./backend /backend
COPY --from=build /frontend/dist/ /frontend
Expand Down
9 changes: 4 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
IMAGE_NAME=nebulabroadcast/nebula-server:latest
IMAGE_NAME=nebulabroadcast/nebula-server:dev
VERSION=$(shell cd backend && poetry run python -c 'import nebula' --version)

check: check_version
cd frontend && yarn format

cd backend && \
poetry run black . && \
poetry run ruff --fix . && \
poetry run ruff format . && \
poetry run ruff check --fix . && \
poetry run mypy .

check_version:
echo $(VERSION)
sed -i "s/^version = \".*\"/version = \"$(VERSION)\"/" backend/pyproject.toml
cd backend && poetry version $(VERSION)

build:
docker build -t $(IMAGE_NAME) .
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ NEBULA
![GitHub release (latest by date)](https://img.shields.io/github/v/release/nebulabroadcast/nebula?style=for-the-badge)
![Maintenance](https://img.shields.io/maintenance/yes/2024?style=for-the-badge)
![Last commit](https://img.shields.io/github/last-commit/nebulabroadcast/nebula?style=for-the-badge)
![Python version](https://img.shields.io/badge/python-3.10-blue?style=for-the-badge)
![Python version](https://img.shields.io/badge/python-3.11-blue?style=for-the-badge)

Nebula is an open source broadcast automation and media asset management system for television, radio and VOD platforms.
Since 2012 Nebula has proven stable and reliable software in 24/7 broadcast environment
Expand Down
47 changes: 32 additions & 15 deletions backend/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ class LoginRequestModel(RequestModel):
username: str = Field(
...,
title="Username",
example="admin",
regex=r"^[a-zA-Z0-9_\-\.]{2,}$",
examples=["admin"],
pattern=r"^[a-zA-Z0-9_\-\.]{2,}$",
)
password: str = Field(
...,
title="Password",
description="Password in plain text",
example="Password.123",
examples=["Password.123"],
)


Expand All @@ -41,8 +41,8 @@ class LoginResponseModel(ResponseModel):


class PasswordRequestModel(RequestModel):
login: str | None = Field(None, title="Login", example="admin")
password: str = Field(..., title="Password", example="Password.123")
login: str | None = Field(None, title="Login", examples=["admin"])
password: str = Field(..., title="Password", examples=["Password.123"])


#
Expand All @@ -64,27 +64,38 @@ async def check_failed_login(ip_address: str) -> None:
raise nebula.LoginFailedException("Too many failed login attempts")


async def set_failed_login(ip_address: str):
async def set_failed_login(ip_address: str) -> None:
ns = "login-failed-ip"
failed_attempts = await nebula.redis.incr(ns, ip_address)
failed_attempts_str = await nebula.redis.incr(ns, ip_address)
failed_attempts = int(failed_attempts_str) if failed_attempts_str else 0

await nebula.redis.expire(
ns, ip_address, 600
) # this is just for the clean-up, it cannot be used to reset the counter

if failed_attempts > nebula.config.max_failed_login_attempts:
ban_time = nebula.config.failed_login_ban_time or 0
await nebula.redis.set(
"banned-ip-until",
ip_address,
time.time() + nebula.config.failed_login_ban_time,
str(time.time() + ban_time),
)


async def clear_failed_login(ip_address: str):
async def clear_failed_login(ip_address: str) -> None:
await nebula.redis.delete("login-failed-ip", ip_address)


class LoginRequest(APIRequest):
"""Login using a username and password"""
"""Login using a username and password

This request will return an access token that can be used in the
Authorization header for the subsequent requests.
If the login fails, request will return 401 Unauthorized.

If the login fails too many (configurable) times,
the IP address will be banned for a certain amount of time (configurable).
"""

name: str = "login"
response_model = LoginResponseModel
Expand Down Expand Up @@ -113,12 +124,15 @@ async def handle(


class LogoutRequest(APIRequest):
"""Log out the current user"""
"""Log out the current user.

This request will invalidate the access token used in the Authorization header.
"""

name: str = "logout"
title: str = "Logout"

async def handle(self, authorization: str | None = Header(None)):
async def handle(self, authorization: str | None = Header(None)) -> None:
if not authorization:
raise nebula.UnauthorizedException("No authorization header provided")

Expand All @@ -134,7 +148,10 @@ async def handle(self, authorization: str | None = Header(None)):
class SetPassword(APIRequest):
"""Set a new password for the current (or a given) user.

In order to set a password for another user, the current user must be an admin.
Normal users can only change their own password.

In order to set a password for another user,
the current user must be an admin, otherwise a 403 error is returned.
"""

name: str = "password"
Expand All @@ -144,10 +161,10 @@ async def handle(
self,
request: PasswordRequestModel,
user: CurrentUser,
):
) -> Response:
if request.login:
if not user.is_admin:
raise nebula.UnauthorizedException(
raise nebula.ForbiddenException(
"Only admin can change other user's password"
)
query = "SELECT meta FROM users WHERE login = $1"
Expand Down
58 changes: 31 additions & 27 deletions backend/api/browse.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from typing import Any, Literal
from typing import Literal

from nxtools import slugify
from pydantic import Field

import nebula
from nebula.common import sql_list
from nebula.common import SerializableValue, sql_list
from nebula.enum import MetaClass
from nebula.exceptions import NebulaException
from nebula.metadata.normalize import normalize_meta
Expand Down Expand Up @@ -40,36 +40,38 @@


class ConditionModel(RequestModel):
key: str = Field(..., example="status")
value: Any = Field(None, example=1)
operator: str = Field("=", example="=")
key: str = Field(..., examples=["status"])
value: SerializableValue = Field(None, examples=[1])
operator: ConditionOperator = Field("=", examples=["="])


class BrowseRequestModel(RequestModel):
view: int | None = Field(
None,
title="View ID",
example=1,
examples=[1],
)
query: str | None = Field(
None,
title="Search query",
example="star trek",
examples=["star trek"],
)
conditions: list[ConditionModel] | None = Field(
default_factory=list,
title="Conditions",
description="List of additional conditions",
example=[
{"key": "id_folder", "value": 1, "operator": "="},
examples=[
[
{"key": "id_folder", "value": 1, "operator": "="},
]
],
)
columns: list[str] | None = Field(
None,
title="Columns",
description="Override the view columns."
"Note that several columns are always included.",
example=["title", "subtitle", "id_folder"],
examples=[["title", "subtitle", "id_folder"]],
)
ignore_view_conditions: bool = Field(False, title="Ignore view conditions")
limit: int = Field(500, title="Limit", description="Maximum number of items")
Expand All @@ -80,29 +82,31 @@ class BrowseRequestModel(RequestModel):

class BrowseResponseModel(ResponseModel):
columns: list[str] = Field(default_factory=list)
data: list[dict[str, Any]] = Field(
data: list[dict[str, SerializableValue]] = Field(
default_factory=list,
example=[
{
"id": 1,
"title": "Star Trek IV",
"subtitle": "The Voyage Home",
"id_folder": 1,
"status": 1,
"duration": 6124.3,
}
examples=[
[
{
"id": 1,
"title": "Star Trek IV",
"subtitle": "The Voyage Home",
"id_folder": 1,
"status": 1,
"duration": 6124.3,
}
]
],
)
order_by: str | None = Field(None)
order_dir: OrderDirection = Field(None)
order_dir: OrderDirection = Field(...)


#
# Request
#


def sanitize_value(value: Any) -> Any:
def sanitize_value(value: SerializableValue) -> str:
if isinstance(value, str):
value = value.replace("'", "''")
return str(value)
Expand All @@ -129,7 +133,7 @@ def build_conditions(conditions: list[ConditionModel]) -> list[str]:
return cond_list


def process_inline_conditions(request: BrowseRequestModel):
def process_inline_conditions(request: BrowseRequestModel) -> None:
if request.query:
query_elements = request.query.split(" ")
reduced_query = []
Expand Down Expand Up @@ -242,7 +246,7 @@ def build_query(

query = f"""
SELECT meta FROM assets {conds}
ORDER BY {order_by} {request.order_dir}
ORDER BY {order_by} {request.order_dir}, id DESC
LIMIT {request.limit}
OFFSET {request.offset}
"""
Expand All @@ -263,9 +267,9 @@ async def handle(
columns: list[str] = ["title", "duration"]
if request.view is not None and not request.columns:
assert isinstance(request.view, int), "View must be an integer"
if (view := nebula.settings.get_view(request.view)) is not None:
if view.columns is not None:
columns = view.columns
view = nebula.settings.get_view(request.view)
if (view is not None) and (view.columns is not None):
columns = view.columns
elif request.columns:
columns = request.columns

Expand Down
5 changes: 2 additions & 3 deletions backend/api/delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class DeleteRequestModel(RequestModel):
...,
title="Object IDs",
description="A list of object IDs to delete",
example=[1, 2, 3],
examples=[[1, 2, 3]],
)


Expand All @@ -34,7 +34,6 @@ async def handle(
initiator: RequestInitiator,
) -> Response:
"""Delete given objects."""

match request.object_type:
case ObjectType.ITEM:
# TODO: refactor events
Expand Down Expand Up @@ -69,7 +68,7 @@ async def handle(
case _:
# do not delete bins directly
raise nebula.NotImplementedException(
f"Deleting {request.obejct_type} is not implemented"
f"Deleting {request.object_type} is not implemented"
)

# Delete simple objects
Expand Down
Loading
Loading