Show banner and hints to upgrade when ArchiveBox is out of date (#1274)
This commit is contained in:
commit
b03ece41e9
4 changed files with 126 additions and 6 deletions
|
@ -30,6 +30,7 @@ import inspect
|
||||||
import getpass
|
import getpass
|
||||||
import platform
|
import platform
|
||||||
import shutil
|
import shutil
|
||||||
|
import requests
|
||||||
import django
|
import django
|
||||||
from sqlite3 import dbapi2 as sqlite3
|
from sqlite3 import dbapi2 as sqlite3
|
||||||
|
|
||||||
|
@ -376,7 +377,7 @@ ALLOWED_IN_OUTPUT_DIR = {
|
||||||
'static_index.json',
|
'static_index.json',
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_version(config):
|
def get_version(config) -> str:
|
||||||
try:
|
try:
|
||||||
return importlib.metadata.version(__package__ or 'archivebox')
|
return importlib.metadata.version(__package__ or 'archivebox')
|
||||||
except importlib.metadata.PackageNotFoundError:
|
except importlib.metadata.PackageNotFoundError:
|
||||||
|
@ -415,6 +416,55 @@ def get_build_time(config) -> str:
|
||||||
src_last_modified_unix_timestamp = (config['PACKAGE_DIR'] / 'config.py').stat().st_mtime
|
src_last_modified_unix_timestamp = (config['PACKAGE_DIR'] / 'config.py').stat().st_mtime
|
||||||
return datetime.fromtimestamp(src_last_modified_unix_timestamp).strftime('%Y-%m-%d %H:%M:%S %s')
|
return datetime.fromtimestamp(src_last_modified_unix_timestamp).strftime('%Y-%m-%d %H:%M:%S %s')
|
||||||
|
|
||||||
|
def get_versions_available_on_github(config):
|
||||||
|
"""
|
||||||
|
returns a dictionary containing the ArchiveBox GitHub release info for
|
||||||
|
the recommended upgrade version and the currently installed version
|
||||||
|
"""
|
||||||
|
|
||||||
|
# we only want to perform the (relatively expensive) check for new versions
|
||||||
|
# when its most relevant, e.g. when the user runs a long-running command
|
||||||
|
subcommand_run_by_user = sys.argv[3]
|
||||||
|
long_running_commands = ('add', 'schedule', 'update', 'status', 'server')
|
||||||
|
if subcommand_run_by_user not in long_running_commands:
|
||||||
|
return None
|
||||||
|
|
||||||
|
github_releases_api = "https://api.github.com/repos/ArchiveBox/ArchiveBox/releases"
|
||||||
|
response = requests.get(github_releases_api)
|
||||||
|
if response.status_code != 200:
|
||||||
|
stderr(f'[!] Warning: GitHub API call to check for new ArchiveBox version failed! (status={response.status_code})', color='lightyellow', config=config)
|
||||||
|
return None
|
||||||
|
all_releases = response.json()
|
||||||
|
|
||||||
|
installed_version = parse_version_string(config['VERSION'])
|
||||||
|
|
||||||
|
# find current version or nearest older version (to link to)
|
||||||
|
current_version = None
|
||||||
|
for idx, release in enumerate(all_releases):
|
||||||
|
release_version = parse_version_string(release["tag_name"])
|
||||||
|
if release_version <= installed_version:
|
||||||
|
current_version = release
|
||||||
|
break
|
||||||
|
|
||||||
|
current_version = current_version or releases[-1]
|
||||||
|
|
||||||
|
# recommended version is whatever comes after current_version in the release list
|
||||||
|
# (perhaps too conservative to only recommend upgrading one version at a time, but it's safest)
|
||||||
|
try:
|
||||||
|
recommended_version = all_releases[idx+1]
|
||||||
|
except IndexError:
|
||||||
|
recommended_version = None
|
||||||
|
|
||||||
|
return {"recommended_version": recommended_version, "current_version": current_version}
|
||||||
|
|
||||||
|
def can_upgrade(config):
|
||||||
|
if config['VERSIONS_AVAILABLE'] and config['VERSIONS_AVAILABLE']['recommended_version']:
|
||||||
|
recommended_version = parse_version_string(config['VERSIONS_AVAILABLE']['recommended_version']['tag_name'])
|
||||||
|
current_version = parse_version_string(config['VERSIONS_AVAILABLE']['current_version']['tag_name'])
|
||||||
|
return recommended_version > current_version
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
############################## Derived Config ##################################
|
############################## Derived Config ##################################
|
||||||
|
|
||||||
|
|
||||||
|
@ -441,10 +491,14 @@ DYNAMIC_CONFIG_SCHEMA: ConfigDefaultDict = {
|
||||||
'DIR_OUTPUT_PERMISSIONS': {'default': lambda c: c['OUTPUT_PERMISSIONS'].replace('6', '7').replace('4', '5')},
|
'DIR_OUTPUT_PERMISSIONS': {'default': lambda c: c['OUTPUT_PERMISSIONS'].replace('6', '7').replace('4', '5')},
|
||||||
|
|
||||||
'ARCHIVEBOX_BINARY': {'default': lambda c: sys.argv[0] or bin_path('archivebox')},
|
'ARCHIVEBOX_BINARY': {'default': lambda c: sys.argv[0] or bin_path('archivebox')},
|
||||||
|
|
||||||
'VERSION': {'default': lambda c: get_version(c).split('+', 1)[0]},
|
'VERSION': {'default': lambda c: get_version(c).split('+', 1)[0]},
|
||||||
'COMMIT_HASH': {'default': lambda c: get_commit_hash(c)},
|
'COMMIT_HASH': {'default': lambda c: get_commit_hash(c)},
|
||||||
'BUILD_TIME': {'default': lambda c: get_build_time(c)},
|
'BUILD_TIME': {'default': lambda c: get_build_time(c)},
|
||||||
|
|
||||||
|
'VERSIONS_AVAILABLE': {'default': lambda c: get_versions_available_on_github(c)},
|
||||||
|
'CAN_UPGRADE': {'default': lambda c: can_upgrade(c)},
|
||||||
|
|
||||||
'PYTHON_BINARY': {'default': lambda c: sys.executable},
|
'PYTHON_BINARY': {'default': lambda c: sys.executable},
|
||||||
'PYTHON_ENCODING': {'default': lambda c: sys.stdout.encoding.upper()},
|
'PYTHON_ENCODING': {'default': lambda c: sys.stdout.encoding.upper()},
|
||||||
'PYTHON_VERSION': {'default': lambda c: '{}.{}.{}'.format(*sys.version_info[:3])},
|
'PYTHON_VERSION': {'default': lambda c: '{}.{}.{}'.format(*sys.version_info[:3])},
|
||||||
|
@ -454,7 +508,7 @@ DYNAMIC_CONFIG_SCHEMA: ConfigDefaultDict = {
|
||||||
|
|
||||||
'SQLITE_BINARY': {'default': lambda c: inspect.getfile(sqlite3)},
|
'SQLITE_BINARY': {'default': lambda c: inspect.getfile(sqlite3)},
|
||||||
'SQLITE_VERSION': {'default': lambda c: sqlite3.version},
|
'SQLITE_VERSION': {'default': lambda c: sqlite3.version},
|
||||||
#'SQLITE_JOURNAL_MODE': {'default': lambda c: 'wal'}, # set at runtime below, interesting but unused for now
|
#'SQLITE_JOURNAL_MODE': {'default': lambda c: 'wal'}, # set at runtime below, interesting if changed later but unused for now because its always expected to be wal
|
||||||
#'SQLITE_OPTIONS': {'default': lambda c: ['JSON1']}, # set at runtime below
|
#'SQLITE_OPTIONS': {'default': lambda c: ['JSON1']}, # set at runtime below
|
||||||
|
|
||||||
'USE_CURL': {'default': lambda c: c['USE_CURL'] and (c['SAVE_FAVICON'] or c['SAVE_TITLE'] or c['SAVE_ARCHIVE_DOT_ORG'])},
|
'USE_CURL': {'default': lambda c: c['USE_CURL'] and (c['SAVE_FAVICON'] or c['SAVE_TITLE'] or c['SAVE_ARCHIVE_DOT_ORG'])},
|
||||||
|
@ -711,9 +765,11 @@ def load_config(defaults: ConfigDefaultDict,
|
||||||
|
|
||||||
return extended_config
|
return extended_config
|
||||||
|
|
||||||
# def write_config(config: ConfigDict):
|
|
||||||
|
|
||||||
# with open(os.path.join(config['OUTPUT_DIR'], CONFIG_FILENAME), 'w+') as f:
|
def parse_version_string(version: str) -> Tuple[int, int int]:
|
||||||
|
"""parses a version tag string formatted like 'vx.x.x' into (major, minor, patch) ints"""
|
||||||
|
base = v.split('+')[0].split('v')[-1] # remove 'v' prefix and '+editable' suffix
|
||||||
|
return tuple(int(part) for part in base.split('.'))[:3]
|
||||||
|
|
||||||
|
|
||||||
# Logging Helpers
|
# Logging Helpers
|
||||||
|
|
|
@ -8,9 +8,12 @@ from django.views.generic.base import RedirectView
|
||||||
|
|
||||||
from core.views import HomepageView, SnapshotView, PublicIndexView, AddView, HealthCheckView
|
from core.views import HomepageView, SnapshotView, PublicIndexView, AddView, HealthCheckView
|
||||||
|
|
||||||
|
from config import VERSION, VERSIONS_AVAILABLE, CAN_UPGRADE
|
||||||
|
|
||||||
# print('DEBUG', settings.DEBUG)
|
# print('DEBUG', settings.DEBUG)
|
||||||
|
|
||||||
|
GLOBAL_CONTEXT = {'VERSION': VERSION, 'VERSIONS_AVAILABLE': VERSIONS_AVAILABLE, 'CAN_UPGRADE': CAN_UPGRADE}
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('public/', PublicIndexView.as_view(), name='public-index'),
|
path('public/', PublicIndexView.as_view(), name='public-index'),
|
||||||
|
|
||||||
|
@ -30,7 +33,7 @@ urlpatterns = [
|
||||||
|
|
||||||
|
|
||||||
path('accounts/', include('django.contrib.auth.urls')),
|
path('accounts/', include('django.contrib.auth.urls')),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls, {'extra_context': GLOBAL_CONTEXT}),
|
||||||
|
|
||||||
path('health/', HealthCheckView.as_view(), name='healthcheck'),
|
path('health/', HealthCheckView.as_view(), name='healthcheck'),
|
||||||
path('error/', lambda _: 1/0),
|
path('error/', lambda _: 1/0),
|
||||||
|
|
|
@ -99,6 +99,8 @@ from .config import (
|
||||||
check_data_folder,
|
check_data_folder,
|
||||||
write_config_file,
|
write_config_file,
|
||||||
VERSION,
|
VERSION,
|
||||||
|
VERSIONS_AVAILABLE,
|
||||||
|
CAN_UPGRADE,
|
||||||
COMMIT_HASH,
|
COMMIT_HASH,
|
||||||
BUILD_TIME,
|
BUILD_TIME,
|
||||||
CODE_LOCATIONS,
|
CODE_LOCATIONS,
|
||||||
|
@ -692,6 +694,8 @@ def add(urls: Union[str, List[str]],
|
||||||
snapshot.save()
|
snapshot.save()
|
||||||
# print(f' √ Tagged {len(imported_links)} Snapshots with {len(tags)} tags {tags_str}')
|
# print(f' √ Tagged {len(imported_links)} Snapshots with {len(tags)} tags {tags_str}')
|
||||||
|
|
||||||
|
if CAN_UPGRADE:
|
||||||
|
hint(f"There's a new version of ArchiveBox available! Your current version is {VERSION}. You can upgrade to {VERSIONS_AVAILABLE['recommended_version']['tag_name']} ({VERSIONS_AVAILABLE['recommended_version']['html_url']}). For more on how to upgrade: https://github.com/ArchiveBox/ArchiveBox/wiki/Upgrading-or-Merging-Archives\n")
|
||||||
|
|
||||||
return all_links
|
return all_links
|
||||||
|
|
||||||
|
@ -1281,6 +1285,9 @@ def schedule(add: bool=False,
|
||||||
print('\n{green}[√] Stopped.{reset}'.format(**ANSI))
|
print('\n{green}[√] Stopped.{reset}'.format(**ANSI))
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
if CAN_UPGRADE:
|
||||||
|
hint(f"There's a new version of ArchiveBox available! Your current version is {VERSION}. You can upgrade to {VERSIONS_AVAILABLE['recommended_version']['tag_name']} ({VERSIONS_AVAILABLE['recommended_version']['html_url']}). For more on how to upgrade: https://github.com/ArchiveBox/ArchiveBox/wiki/Upgrading-or-Merging-Archives\n")
|
||||||
|
|
||||||
|
|
||||||
@enforce_types
|
@enforce_types
|
||||||
def server(runserver_args: Optional[List[str]]=None,
|
def server(runserver_args: Optional[List[str]]=None,
|
||||||
|
|
|
@ -12,7 +12,26 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}">
|
<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}">
|
||||||
{% block extrastyle %}{% endblock %}
|
{% block extrastyle %}
|
||||||
|
<style>
|
||||||
|
#upgrade-banner {
|
||||||
|
position: fixed;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 20px;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
color: #333333;
|
||||||
|
border: 2px solid #772948;
|
||||||
|
padding: 10px 20px;
|
||||||
|
z-index: 1000;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#dismiss-btn {
|
||||||
|
background: #aa1e55;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% if LANGUAGE_BIDI %}
|
{% if LANGUAGE_BIDI %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}">
|
<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}">
|
||||||
|
@ -123,6 +142,41 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
{% if user.is_authenticated and user.is_superuser and CAN_UPGRADE %}
|
||||||
|
if (!localStorage.getItem("bannerDismissed")) {
|
||||||
|
const upgradeVersionTag = "{{VERSIONS_AVAILABLE.recommended_version.tag_name}}"
|
||||||
|
const upgradeVersionURL = "{{VERSIONS_AVAILABLE.recommended_version.html_url}}"
|
||||||
|
const currentVersionTag = "{{VERSION}}"
|
||||||
|
const currentVersionURL = "{{VERSIONS_AVAILABLE.recommended_version.html_url}}"
|
||||||
|
|
||||||
|
createBanner(currentVersionTag, currentVersionURL, upgradeVersionTag, upgradeVersionURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBanner(currentVersionTag, currentVersionURL, upgradeVersionTag, upgradeVersionURL) {
|
||||||
|
const banner = document.createElement('div')
|
||||||
|
banner.setAttribute('id', 'upgrade-banner');
|
||||||
|
banner.innerHTML = `
|
||||||
|
<p>There's a new version of ArchiveBox available!</p>
|
||||||
|
Your version: <a href=${currentVersionURL}>${currentVersionTag}</a> | New version: <a href=${upgradeVersionURL}>${upgradeVersionTag}</a>
|
||||||
|
<p>
|
||||||
|
<a href=https://github.com/ArchiveBox/ArchiveBox/wiki/Upgrading-or-Merging-Archives>Upgrade Instructions</a> | <a href=https://github.com/ArchiveBox/ArchiveBox/releases>Changelog</a> | <a href=https://github.com/ArchiveBox/ArchiveBox/wiki/Roadmap>Roadmap</a>
|
||||||
|
</p>
|
||||||
|
<button id="dismiss-btn">Dismiss</button>
|
||||||
|
`
|
||||||
|
document.body.appendChild(banner);
|
||||||
|
const dismissButton = document.querySelector("#dismiss-btn")
|
||||||
|
if (dismissButton) {
|
||||||
|
dismissButton.addEventListener("click", dismissBanner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismissBanner() {
|
||||||
|
const banner = document.getElementById("upgrade-banner")
|
||||||
|
banner.style.display = "none"
|
||||||
|
localStorage.setItem("bannerDismissed", "true")
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
$ = django.jQuery;
|
$ = django.jQuery;
|
||||||
$.fn.reverse = [].reverse;
|
$.fn.reverse = [].reverse;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue