Merges extensions into one page (#1656)

* Merged extensions into one page

* Bundle files updated

* Fixed install bug

* feat: client side version compatibility check

* fix: hide `Activated/Deactivated` toggle for non-admins

* feat: translate labels to `EN`

* feat: add other language translations

* chore: update bundle for i18n

* feat: check extension version server-side

* feat: show warning message

* refactor: nicer mapping

Co-authored-by: dni  <office@dnilabs.com>

* chore: code format

* chore: extra log

* feat: check_latest_version of ext

* feat: show tooltip for new version

* chore: `make bundle`

* chore: `mypy`

* chore: code clean-up

* feat: show version in badge (spacing is fine)

* chore: make bundle

* feat: check `min_lnbits_version` and `warning` in `config.json`

* chore: code formatting

* chore: downgrade log level

* fix: extract `ExtensionsInstallSettings` as readonly

* fix: do not show installed and deactivated extensions

* chore: format

* fix: `Enable` button tooltip

* fix: set installed release after installation

* fix: hide deactivated extensions from regular users

* bundle fundle

* bundle fundle

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
Co-authored-by: dni  <office@dnilabs.com>
This commit is contained in:
Arc
2023-05-11 01:14:07 +01:00
committed by GitHub
co-authored by dni ⚡ Vlad Stan
parent 3fe33dfb81
commit 6f2771e334
14 changed files with 786 additions and 610 deletions
+77 -21
View File
@@ -12,6 +12,7 @@ from urllib import request
import httpx
from fastapi import HTTPException
from loguru import logger
from packaging import version
from pydantic import BaseModel
from lnbits.settings import settings
@@ -26,11 +27,17 @@ class ExplicitRelease(BaseModel):
dependencies: List[str] = []
icon: Optional[str]
short_description: Optional[str]
html_url: Optional[str]
details: Optional[str]
min_lnbits_version: Optional[str]
html_url: Optional[str] # todo: release_url
warning: Optional[str]
info_notification: Optional[str]
critical_notification: Optional[str]
def is_version_compatible(self):
if not self.min_lnbits_version:
return True
return version.parse(self.min_lnbits_version) <= version.parse(settings.version)
class GitHubRelease(BaseModel):
id: str
@@ -61,6 +68,13 @@ class ExtensionConfig(BaseModel):
name: str
short_description: str
tile: str = ""
warning: Optional[str] = ""
min_lnbits_version: Optional[str]
def is_version_compatible(self):
if not self.min_lnbits_version:
return True
return version.parse(self.min_lnbits_version) <= version.parse(settings.version)
def download_url(url, save_path):
@@ -117,6 +131,17 @@ async def fetch_github_releases(org: str, repo: str) -> List[GitHubRepoRelease]:
return [GitHubRepoRelease.parse_obj(r) for r in releases]
async def fetch_github_release_config(
org: str, repo: str, tag_name: str
) -> Optional[ExtensionConfig]:
config_url = (
f"https://raw.githubusercontent.com/{org}/{repo}/{tag_name}/config.json"
)
error_msg = "Cannot fetch GitHub extension config"
config = await gihub_api_get(config_url, error_msg)
return ExtensionConfig.parse_obj(config)
async def gihub_api_get(url: str, error_msg: Optional[str]) -> Any:
async with httpx.AsyncClient() as client:
headers = (
@@ -224,9 +249,11 @@ class ExtensionRelease(BaseModel):
source_repo: str
is_github_release: bool = False
hash: Optional[str] = None
min_lnbits_version: Optional[str] = None
is_version_compatible: Optional[bool] = True
html_url: Optional[str] = None
description: Optional[str] = None
details_html: Optional[str] = None
warning: Optional[str] = None
icon: Optional[str] = None
@classmethod
@@ -244,6 +271,24 @@ class ExtensionRelease(BaseModel):
html_url=r.html_url,
)
@classmethod
def from_explicit_release(
cls, source_repo: str, e: "ExplicitRelease"
) -> "ExtensionRelease":
return ExtensionRelease(
name=e.name,
version=e.version,
archive=e.archive,
hash=e.hash,
source_repo=source_repo,
description=e.short_description,
min_lnbits_version=e.min_lnbits_version,
is_version_compatible=e.is_version_compatible(),
warning=e.warning,
html_url=e.html_url,
icon=e.icon,
)
@classmethod
async def all_releases(cls, org: str, repo: str) -> List["ExtensionRelease"]:
try:
@@ -394,6 +439,15 @@ class InstallableExtension(BaseModel):
shutil.rmtree(self.ext_upgrade_dir, True)
def check_latest_version(self, release: Optional[ExtensionRelease]):
if not release:
return
if not self.latest_release:
self.latest_release = release
return
if version.parse(self.latest_release.version) < version.parse(release.version):
self.latest_release = release
@classmethod
def from_row(cls, data: dict) -> "InstallableExtension":
meta = json.loads(data["meta"])
@@ -451,18 +505,30 @@ class InstallableExtension(BaseModel):
manifest = await fetch_manifest(url)
for r in manifest.repos:
if r.id in extension_id_list:
continue
ext = await InstallableExtension.from_github_release(r)
if ext:
ext.featured = ext.id in manifest.featured
extension_list += [ext]
extension_id_list += [ext.id]
if not ext:
continue
existing_ext = next(
(ee for ee in extension_list if ee.id == r.id), None
)
if existing_ext:
existing_ext.check_latest_version(ext.latest_release)
continue
ext.featured = ext.id in manifest.featured
extension_list += [ext]
extension_id_list += [ext.id]
for e in manifest.extensions:
if e.id in extension_id_list:
release = ExtensionRelease.from_explicit_release(url, e)
existing_ext = next(
(ee for ee in extension_list if ee.id == e.id), None
)
if existing_ext:
existing_ext.check_latest_version(release)
continue
ext = InstallableExtension.from_explicit_release(e)
ext.check_latest_version(release)
ext.featured = ext.id in manifest.featured
extension_list += [ext]
extension_id_list += [e.id]
@@ -488,17 +554,7 @@ class InstallableExtension(BaseModel):
for e in manifest.extensions:
if e.id == ext_id:
extension_releases += [
ExtensionRelease(
name=e.name,
version=e.version,
archive=e.archive,
hash=e.hash,
source_repo=url,
description=e.short_description,
details_html=e.details,
html_url=e.html_url,
icon=e.icon,
)
ExtensionRelease.from_explicit_release(url, e)
]
except Exception as e: