Browse Source

fix pending titles and favicons, improve add page, custom admin

Nick Sweeting 5 years ago
parent
commit
3aeca0e450

+ 75 - 11
archivebox/core/admin.py

@@ -1,19 +1,31 @@
+__package__ = 'archivebox.core'
+
+from io import StringIO
+from contextlib import redirect_stdout
+
 from django.contrib import admin
+from django.urls import path
 from django.utils.html import format_html
+from django.shortcuts import render
+from django.contrib.auth import get_user_model
 
-from util import htmldecode, urldecode
 from core.models import Snapshot
-from archivebox.logging_util import printable_filesize
+from core.forms import AddLinkForm
+
+from ..util import htmldecode, urldecode, ansi_to_html
+from ..logging_util import printable_filesize
+from ..main import add
+from ..config import OUTPUT_DIR
 
 # TODO: https://stackoverflow.com/questions/40760880/add-custom-button-to-django-admin-panel
 
 
 class SnapshotAdmin(admin.ModelAdmin):
-    list_display = ('title_str', 'url_str', 'tags', 'files', 'size', 'added', 'updated')
+    list_display = ('added', 'title_str', 'url_str', 'tags', 'files', 'size', 'updated')
     sort_fields = ('title_str', 'url_str', 'tags', 'added', 'updated')
-    readonly_fields = ('id', 'num_outputs', 'is_archived', 'url_hash', 'added', 'updated')
+    readonly_fields = ('id', 'url', 'timestamp', 'num_outputs', 'is_archived', 'url_hash', 'added', 'updated')
     search_fields = ('url', 'timestamp', 'title', 'tags')
-    fields = ('url', 'timestamp', 'title', 'tags', *readonly_fields)
+    fields = ('title', 'tags', *readonly_fields)
     list_filter = ('added', 'updated', 'tags')
     ordering = ['-added']
 
@@ -27,15 +39,16 @@ class SnapshotAdmin(admin.ModelAdmin):
         canon = obj.as_link().canonical_outputs()
         return format_html(
             '<a href="/{}">'
-            '<img src="/{}/{}" style="height: 20px; width: 20px;" onerror="this.remove()">'
-            ' &nbsp; &nbsp; '
+                '<img src="/{}/{}" class="favicon" onerror="this.remove()">'
             '</a>'
             '<a href="/{}/{}">'
-            '<b>{}</b></a>',
+                '<b class="status-{}">{}</b>'
+            '</a>',
             obj.archive_path,
             obj.archive_path, canon['favicon_path'],
             obj.archive_path, canon['wget_path'] or '',
-            urldecode(htmldecode(obj.latest_title or obj.title or '-'))[:128],
+            'fetched' if obj.latest_title or obj.title else 'pending',
+            urldecode(htmldecode(obj.latest_title or obj.title or ''))[:128] or 'Pending...',
         )
 
     def files(self, obj):
@@ -68,17 +81,68 @@ class SnapshotAdmin(admin.ModelAdmin):
 
     def url_str(self, obj):
         return format_html(
-            '<a href="{}"><code>{}</code></a>',
+            '<a href="{}">{}</a>',
             obj.url,
             obj.url.split('://www.', 1)[-1].split('://', 1)[-1][:64],
         )
 
     id_str.short_description = 'ID'
     title_str.short_description = 'Title'
-    url_str.short_description = 'URL'
+    url_str.short_description = 'Original URL'
 
     id_str.admin_order_field = 'id'
     title_str.admin_order_field = 'title'
     url_str.admin_order_field = 'url'
 
+
+
+class ArchiveBoxAdmin(admin.AdminSite):
+    site_header = 'ArchiveBox'
+    index_title = 'Links'
+    site_title = 'Index'
+
+    def get_urls(self):
+        return [
+            path('core/snapshot/add/', self.add_view, name='add'),
+        ] + super().get_urls()
+
+    def add_view(self, request):
+        request.current_app = self.name
+        context = {
+            **self.each_context(request),
+            'title': 'Add URLs',
+        }
+
+        if request.method == 'GET':
+            context['form'] = AddLinkForm()
+
+        elif request.method == 'POST':
+            form = AddLinkForm(request.POST)
+            if form.is_valid():
+                url = form.cleaned_data["url"]
+                print(f'[+] Adding URL: {url}')
+                depth = 0 if form.cleaned_data["depth"] == "0" else 1
+                input_kwargs = {
+                    "urls": url,
+                    "depth": depth,
+                    "update_all": False,
+                    "out_dir": OUTPUT_DIR,
+                }
+                add_stdout = StringIO()
+                with redirect_stdout(add_stdout):
+                   add(**input_kwargs)
+                print(add_stdout.getvalue())
+
+                context.update({
+                    "stdout": ansi_to_html(add_stdout.getvalue().strip()),
+                    "form": AddLinkForm()
+                })
+            else:
+                context["form"] = form
+
+        return render(template_name='add_links.html', request=request, context=context)
+
+
+admin.site = ArchiveBoxAdmin()
+admin.site.register(get_user_model())
 admin.site.register(Snapshot, SnapshotAdmin)

+ 8 - 4
archivebox/core/forms.py

@@ -1,10 +1,14 @@
+__package__ = 'archivebox.core'
+
 from django import forms
 
+from ..util import URL_REGEX
+
 CHOICES = (
-    ('0', 'depth=0 (archive just this url)'),
-    ('1', 'depth=1 (archive this url and all sites one link away)'),
+    ('0', 'depth = 0 (archive just these URLs)'),
+    ('1', 'depth = 1 (archive these URLs and all URLs one hop away)'),
 )
 
 class AddLinkForm(forms.Form):
-    url = forms.URLField()
-    depth = forms.ChoiceField(choices=CHOICES, widget=forms.RadioSelect, initial='0')
+    url = forms.RegexField(label="URLs (one per line)", regex=URL_REGEX, min_length='6', strip=True, widget=forms.Textarea, required=True)
+    depth = forms.ChoiceField(label="Archive depth", choices=CHOICES, widget=forms.RadioSelect, initial='0')

+ 28 - 0
archivebox/core/migrations/0005_auto_20200728_0326.py

@@ -0,0 +1,28 @@
+# Generated by Django 3.0.7 on 2020-07-28 03:26
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0004_auto_20200713_1552'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='snapshot',
+            name='tags',
+            field=models.CharField(blank=True, db_index=True, max_length=256, null=True),
+        ),
+        migrations.AlterField(
+            model_name='snapshot',
+            name='title',
+            field=models.CharField(blank=True, db_index=True, max_length=128, null=True),
+        ),
+        migrations.AlterField(
+            model_name='snapshot',
+            name='updated',
+            field=models.DateTimeField(blank=True, db_index=True, null=True),
+        ),
+    ]

+ 3 - 3
archivebox/core/models.py

@@ -15,11 +15,11 @@ class Snapshot(models.Model):
     url = models.URLField(unique=True)
     timestamp = models.CharField(max_length=32, unique=True, db_index=True)
 
-    title = models.CharField(max_length=128, null=True, default=None, db_index=True)
-    tags = models.CharField(max_length=256, null=True, default=None, db_index=True)
+    title = models.CharField(max_length=128, null=True, blank=True, db_index=True)
+    tags = models.CharField(max_length=256, null=True, blank=True, db_index=True)
 
     added = models.DateTimeField(auto_now_add=True, db_index=True)
-    updated = models.DateTimeField(null=True, default=None, db_index=True)
+    updated = models.DateTimeField(null=True, blank=True, db_index=True)
     # bookmarked = models.DateTimeField()
 
     keys = ('url', 'timestamp', 'title', 'tags', 'updated')

+ 4 - 5
archivebox/core/settings.py

@@ -5,16 +5,16 @@ import sys
 from django.utils.crypto import get_random_string
 
 
-from ..config import (
-    OUTPUT_DIR,
+from ..config import (                                                          # noqa: F401
+    DEBUG,
     SECRET_KEY,
     ALLOWED_HOSTS,
     PYTHON_DIR,
     ACTIVE_THEME,
     SQL_INDEX_FILENAME,
+    OUTPUT_DIR,
 )
 
-
 ALLOWED_HOSTS = ALLOWED_HOSTS.split(',')
 IS_SHELL = 'shell' in sys.argv[:3] or 'shell_plus' in sys.argv[:3]
 
@@ -25,8 +25,8 @@ INSTALLED_APPS = [
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
-    'django.contrib.admin',
     'django.contrib.staticfiles',
+    'django.contrib.admin',
 
     'core',
 
@@ -121,5 +121,4 @@ STATIC_URL = '/static/'
 STATICFILES_DIRS = [
     os.path.join(PYTHON_DIR, 'themes', ACTIVE_THEME, 'static'),
     os.path.join(PYTHON_DIR, 'themes', 'default', 'static'),
-    os.path.join(PYTHON_DIR, 'themes', 'static'),
 ]

+ 3 - 9
archivebox/core/urls.py

@@ -3,15 +3,12 @@ from django.contrib import admin
 from django.urls import path, include
 from django.views import static
 from django.conf import settings
-from django.contrib.staticfiles.views import serve as serve_static
 from django.views.generic.base import RedirectView
 
-from core.views import MainIndex, AddLinks, LinkDetails
+from core.views import MainIndex, LinkDetails
 
-admin.site.site_header = 'ArchiveBox'
-admin.site.index_title = 'Links' 
-admin.site.site_title = 'Index'
 
+# print('DEBUG', settings.DEBUG)
 
 urlpatterns = [
     path('robots.txt', static.serve, {'document_root': settings.OUTPUT_DIR, 'path': 'robots.txt'}),
@@ -19,14 +16,11 @@ urlpatterns = [
 
     path('archive/', RedirectView.as_view(url='/')),
     path('archive/<path:path>', LinkDetails.as_view(), name='LinkAssets'),
-    path('add/', AddLinks.as_view(), name='AddLinks'),
-    
-    path('static/<path>', serve_static),
+    path('add/', RedirectView.as_view(url='/admin/core/snapshot/add/')),
     
     path('accounts/login/', RedirectView.as_view(url='/admin/login/')),
     path('accounts/logout/', RedirectView.as_view(url='/admin/logout/')),
 
-    path('admin/core/snapshot/add/', RedirectView.as_view(url='/add/')),
 
     path('accounts/', include('django.contrib.auth.urls')),
     path('admin/', admin.site.urls),

+ 1 - 49
archivebox/core/views.py

@@ -7,9 +7,6 @@ from django.views import View, static
 
 from core.models import Snapshot
 
-from contextlib import redirect_stdout
-from io import StringIO
-
 from ..index import load_main_index, load_main_index_meta
 from ..config import (
     OUTPUT_DIR,
@@ -18,10 +15,7 @@ from ..config import (
     PUBLIC_INDEX,
     PUBLIC_SNAPSHOTS,
 )
-from ..util import base_url, ansi_to_html
-from .. main import add
-
-from .forms import AddLinkForm
+from ..util import base_url
 
 
 class MainIndex(View):
@@ -45,48 +39,6 @@ class MainIndex(View):
         return render(template_name=self.template, request=request, context=context)
 
 
-class AddLinks(View):
-    template = 'add_links.html'
-
-    def get(self, request):
-        if not request.user.is_authenticated and not PUBLIC_INDEX:
-            return redirect(f'/admin/login/?next={request.path}')
-
-        context = {
-            "form": AddLinkForm()
-        }
-
-        return render(template_name=self.template, request=request, context=context)
-
-    def post(self, request):
-        if not request.user.is_authenticated and not PUBLIC_INDEX:
-            return redirect(f'/admin/login/?next={request.path}')
-        form = AddLinkForm(request.POST)
-        if form.is_valid():
-            url = form.cleaned_data["url"]
-            print(f'[+] Adding URL: {url}')
-            depth = 0 if form.cleaned_data["depth"] == "0" else 0
-            input_kwargs = {
-                "urls": url,
-                "depth": depth,
-                "update_all": False,
-                "out_dir": OUTPUT_DIR,
-            }
-            add_stdout = StringIO()
-            with redirect_stdout(add_stdout):
-               add(**input_kwargs)
-            print(add_stdout.getvalue())
-
-            context = {
-                "stdout": ansi_to_html(add_stdout.getvalue()),
-                "form": AddLinkForm()
-            }
-        else:
-            context = {"form": form}
-
-        return render(template_name=self.template, request=request, context=context)
-
-
 class LinkDetails(View):
     def get(self, request, path):
         # missing trailing slash -> redirect to index

+ 2 - 2
archivebox/index/html.py

@@ -136,8 +136,8 @@ def link_details_template(link: Link) -> str:
         'url_str': htmlencode(urldecode(link.base_url)),
         'archive_url': urlencode(
             wget_output_path(link)
-            or (link.domain if link.is_archived else 'about:blank')
-        ),
+            or (link.domain if link.is_archived else '')
+        ) or 'about:blank',
         'extension': link.extension or 'html',
         'tags': link.tags or 'untagged',
         'status': 'archived' if link.is_archived else 'not yet archived',

+ 9 - 2
archivebox/main.py

@@ -83,6 +83,7 @@ from .config import (
     EXTERNAL_LOCATIONS,
     DATA_LOCATIONS,
     DEPENDENCIES,
+    DEBUG,
     load_all_config,
     CONFIG,
     USER_CONFIG,
@@ -987,13 +988,19 @@ def server(runserver_args: Optional[List[str]]=None,
     """Run the ArchiveBox HTTP server"""
 
     runserver_args = runserver_args or []
-    check_data_folder(out_dir=out_dir)
+    
+    from . import config
+    config.SHOW_PROGRESS = False
 
     if debug:
-        os.environ['DEBUG'] = 'True'
+        # if --debug is passed, patch config.DEBUG to be True for this run
+        config.DEBUG = True
     else:
+        # force staticfiles to be served when DEBUG=False
+        # TODO: do this using nginx or another server instead of django?
         runserver_args.append('--insecure')
 
+    check_data_folder(out_dir=out_dir)
     setup_django(out_dir)
     from django.core.management import call_command
     from django.contrib.auth.models import User

+ 5 - 3
archivebox/themes/admin/base.html

@@ -2,7 +2,7 @@
 {% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
 <html lang="{{ LANGUAGE_CODE|default:"en-us" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
 <head>
-<title>{% block title %}{% endblock %}</title>
+<title>{% block title %}{% endblock %} | ArchiveBox</title>
 <link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}">
 {% block extrastyle %}{% endblock %}
 {% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}">{% endif %}
@@ -13,6 +13,7 @@
     {% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% static "admin/css/responsive_rtl.css" %}">{% endif %}
 {% endblock %}
 {% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE">{% endblock %}
+<link rel="stylesheet" type="text/css" href="{% static "admin.css" %}">
 </head>
 {% load i18n %}
 
@@ -26,13 +27,14 @@
     <!-- Header -->
     <div id="header">
         <div id="branding">
-        {% block branding %}{% endblock %}
+            {% block branding %}{% endblock %}
         </div>
         {% block usertools %}
         {% if has_permission %}
         <div id="user-tools">
             <a href="/add/">Add Links</a> /
             <a href="/">Main Index</a> /
+            <a href="/admin/">Admin</a> /
             <a href="https://github.com/pirate/ArchiveBox/wiki">Docs</a>
              &nbsp; &nbsp;
             {% block welcome-msg %}
@@ -76,7 +78,7 @@
     <!-- Content -->
     <div id="content" class="{% block coltype %}colM{% endblock %}">
         {% block pretitle %}{% endblock %}
-        {% block content_title %}{% if title %}<h1>{{ title }}</h1>{% endif %}{% endblock %}
+        {% block content_title %}{# {% if title %}<h1>{{ title }}</h1>{% endif %} #}{% endblock %}
         {% block content %}
         {% block object-tools %}{% endblock %}
         {{ content }}

+ 109 - 214
archivebox/themes/default/add_links.html

@@ -1,218 +1,113 @@
-{% load static %}
+{% extends "admin/index.html" %}
+{% load i18n %}
 
-<!DOCTYPE html>
-<html lang="en">
-    <head>
-        <title>Archived Sites</title>
-        <meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1">
-        <style>
-            html, body {
-                width: 100%;
-                height: 100%;
-                font-size: 18px;
-                font-weight: 200;
-                text-align: center;
-                margin: 0px;
-                padding: 0px;
-                font-family: "Gill Sans", Helvetica, sans-serif;
-            }
-            .header-top small {
-                font-weight: 200;
-                color: #efefef;
-            }
-            
-            .header-top {
-                width: 100%;
-                height: auto;
-                min-height: 40px;
-                margin: 0px;
-                text-align: center;
-                color: white;
-                font-size: calc(11px + 0.84vw);
-                font-weight: 200;
-                padding: 4px 4px;
-                border-bottom: 3px solid #aa1e55;
-                background-color: #aa1e55;
-            }
-            input[type=search] {
-                width: 22vw;
-                border-radius: 4px;
-                border: 1px solid #aeaeae;
-                padding: 3px 5px;
-            }
-            .nav > div {
-                min-height: 30px;
-            }
-            .header-top a {
-                text-decoration: none;
-                color: rgba(0,0,0,0.6);
-            }
-            .header-top a:hover {
-                text-decoration: none;
-                color: rgba(0,0,0,0.9);
-            }
-            .header-top .col-lg-4 {
-                text-align: center;
-                padding-top: 4px;
-                padding-bottom: 4px;
-            }
-            .header-archivebox img {
-                display: inline-block;
-                margin-right: 3px;
-                height: 30px;
-                margin-left: 12px;
-                margin-top: -4px;
-                margin-bottom: 2px;
-            }
-            .header-archivebox img:hover {
-                opacity: 0.5;
-            }
+{% block breadcrumbs %}
+    <div class="breadcrumbs">
+        <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+        {% if title %} &rsaquo; {{ title }}{% endif %}
+    </div>
+{% endblock %}
 
-            #table-bookmarks_length, #table-bookmarks_filter {
-                padding-top: 12px;
-                opacity: 0.8;
-                padding-left: 24px;
-                padding-right: 22px;
-                margin-bottom: -16px;
-            }
-            table {
-                padding: 6px;
-                width: 100%;
-            }
-            table thead th {
-                font-weight: 400;
-            }
-            table tr {
-                height: 35px;
-            }
-            tbody tr:nth-child(odd) {
-               background-color: #ffebeb !important;
-            }
-            table tr td {
-                white-space: nowrap;
-                overflow: hidden;
-                /*padding-bottom: 0.4em;*/
-                /*padding-top: 0.4em;*/
-                padding-left: 2px;
-                text-align: center;
-            }
-            table tr td a {
-                text-decoration: none;
-            }
-            table tr td img, table tr td object {
-                display: inline-block;
-                margin: auto;
-                height: 24px;
-                width: 24px;
-                padding: 0px;
-                padding-right: 5px;
-                vertical-align: middle;
-                margin-left: 4px;
-            }
-            #table-bookmarks {
-                width: 100%; 
-                overflow-y: scroll;
-                table-layout: fixed;
-            }
-            .dataTables_wrapper {
-                background-color: #fafafa;
-            }
-            table tr a span[data-archived~=False] {
-                opacity: 0.4;
-            }
-            .files-spinner {
-                height: 15px;
-                width: auto;
-                opacity: 0.5;
-                vertical-align: -2px;
-            }
-            .in-progress {
-                display: none;
-            }
-            body[data-status~=finished] .files-spinner {
-                display: none;
-            }
-            /*body[data-status~=running] .in-progress {
-                display: inline-block;
-            }*/
-            tr td a.favicon img {
-                padding-left: 6px;
-                padding-right: 12px;
-                vertical-align: -4px;
-            }
-            tr td a.title {
-                font-size: 1.4em;
-                text-decoration:none;
-                color:black;
-            }
-            tr td a.title small {
-                background-color: #efefef;
-                border-radius: 4px;
-                float:right
-            }
-            input[type=search]::-webkit-search-cancel-button {
-                -webkit-appearance: searchfield-cancel-button;
-            }
-            .title-col {
-                text-align: left;
-            }
-            .title-col a {
-                color: black;
-            }
-            .ul-form {
-                list-style: none;
-            }
-            .ul-form li {
-                list-style: none;
-            }
-        </style>
-        <link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
-        <link rel="stylesheet" href="{% static 'jquery.dataTables.min.css' %}"/>
-        <script src="{% static 'jquery.min.js' %}"></script>
-        <script src="{% static 'jquery.dataTables.min.js' %}"></script>
-        <script>
-            document.addEventListener('error', function(e) {
-              e.target.style.opacity = 0;
-            }, true)
-            jQuery(document).ready(function() {
-                jQuery('#table-bookmarks').DataTable({
-                    stateSave: true, // save state (filtered input, number of entries shown, etc) in localStorage
-                    dom: '<lf<t>ip>', // how to show the table and its helpers (filter, etc) in the DOM
-                    order: [[0, 'desc']],
-                    iDisplayLength: 100,
-                });
-            });
-        </script>
-    </head>
-    <body data-status="finished">
-        <header>
-            <div class="header-top container-fluid">
-                <div class="row nav">
-                    <div class="col-sm-2">
-                        <a href="/" class="header-archivebox" title="Last updated: {{updated}}">
-                            <img src="{% static 'archive.png' %}" alt="Logo"/>
-                            ArchiveBox: Add
-                        </a>
-                    </div>
-                    <div class="col-sm-10" style="text-align: right">
-                        <a href="/">Main Index</a> &nbsp; | &nbsp; 
-                        <a href="/admin/">Admin</a> &nbsp; | &nbsp; 
-                        <a href="https://github.com/pirate/ArchiveBox/wiki">Docs</a>
-                    </div>
-                </div>
-            </div>
-        </header>
-        <center>
-            {{ stdout | safe }}
-            <br/><br/>
-            <form action="?" method="POST" class="ul-form">{% csrf_token %}
-                Add new links...<br/>
-                {{ form.as_ul }}
-                <button role="submit">Add</button>
+{% block content %}
+    <style>
+        .dashboard #content {
+            width: 100%;
+            margin-right: 0px;
+            margin-left: 0px;
+        }
+        #submit {
+            border: 1px solid rgba(0,0,0,0.2);
+            padding: 10px;
+            border-radius: 4px;
+            background-color: #f5dd5d;
+            color: #333;
+            font-size: 18px;
+            font-weight: 800;
+        }
+        #add-form button[role=submit]:hover {
+            background-color: #e5cd4d;
+        }
+        #add-form label {
+            display: block;
+            font-size: 16px;
+        }
+        #add-form textarea {
+            width: 100%;
+            min-height: 300px;
+        }
+        #delay-warning div {
+            border: 1px solid red;
+            border-radius: 4px;
+            margin: 10px;
+            padding: 10px;
+            font-size: 15px;
+            background-color: #F5DD5D;
+        }
+        #stdout {
+            background-color: #ded;
+            padding: 10px 10px;
+            border-radius: 4px;
+            white-space: normal;
+        }
+        .loader {
+            border: 16px solid #f3f3f3; /* Light grey */
+            border-top: 16px solid #3498db; /* Blue */
+            border-radius: 50%;
+            width: 120px;
+            height: 120px;
+            animation: spin 2s linear infinite;
+        }
+
+        @keyframes spin {
+            0% { transform: rotate(0deg); }
+            100% { transform: rotate(360deg); }
+        }
+    </style>
+    <div style="max-width: 550px; margin: auto; float: none">
+        <br/><br/>
+        {% if stdout %}
+            <h1>Add new URLs to your archive: results</h1>
+            <pre id="stdout">
+                {{ stdout | safe }}
+                <br/><br/>
+            </pre>
+            <br/>
+            <center>
+                <a href="/add" id="submit">&nbsp; Add more URLs ➕</a>
+            </center>
+        {% else %}
+            <form id="add-form" action="?" method="POST" class="p-form">{% csrf_token %}
+                <h1>Add new URLs to your archive</h1>
+                <br/>
+                {{ form.as_p }}
+                <center>
+                    <button role="submit" id="submit">&nbsp; Add URLs and archive ➕</button>
+                </center>
             </form>
-        </center>
+            <br/><br/><br/>
+            <center id="delay-warning" style="display: none">
+                <b><i>This page will be unresponsive until the process is completely finished.</i></b>
+                <br/><br/>
+                <div>
+                    Warning: it may take several minutes to finish adding!<br/>
+                    <br/>
+                    Progress will be displayed in the <code>archivebox server</code> stdout,<br/>
+                    and on this page once the archiving process completes.<br/>
+                    <br/>
+                    <small>(it's safe to leave this page, adding will continue in the background)</small>
+                </div>
+            </center>
+            <script>
+                document.getElementById('add-form').addEventListener('submit', function(event) {
+                    setTimeout(function() {
+                        document.getElementById('add-form').innerHTML = '<center><h3>Adding URLs to index and running archive methods...<h3><br/><div class="loader"></div><br/>(see terminal for progress)</center>'
+                        document.getElementById('delay-warning').style.display = 'block'
+                    }, 200)
+                    return true
+                })
+            </script>
+        {% endif %}
+    </div>
+{% endblock %}
 
-        <a href="{% url 'admin:core_snapshot_changelist' %}">Go back to Main Index</a>
-        
-    </body>
-</html>
+{% block sidebar %}{% endblock %}

+ 126 - 0
archivebox/themes/default/static/admin.css

@@ -0,0 +1,126 @@
+#header {
+    background: #aa1e55;
+    padding: 6px 14px;
+}
+#content {
+    padding: 8px 8px;
+}
+#user-tools {
+    font-size: 13px;
+
+}
+
+div.breadcrumbs {
+    background: #772948;
+    color: #f5dd5d;
+    padding: 6px 15px;
+}
+
+.module h2, .module caption, .inline-group h2 {
+    background: #772948;
+}
+
+#content .object-tools {
+    margin-top: -35px;
+    margin-right: -10px;
+    float: right;
+}
+
+#content .object-tools a:link, #content .object-tools a:visited {
+    border-radius: 0px;
+    background-color: #f5dd5d;
+    color: #333;
+    font-size: 12px;
+    font-weight: 800;
+}
+
+#content .object-tools a.addlink {
+    background-blend-mode: difference;
+}
+
+#content #changelist #toolbar {
+    padding: 0px;
+    background: none;
+    margin-bottom: 10px;
+    border-top: 0px; 
+    border-bottom: 0px;
+}
+
+#content #changelist #toolbar form input[type="submit"] {
+    border-color: #aa1e55;
+}
+
+#content #changelist-filter li.selected a {
+    color: #aa1e55;
+}
+
+
+#content #changelist .actions {
+    position: fixed;
+    bottom: 0px;
+    z-index: 800;
+}
+
+#content #changelist .actions .button {
+    border-color: #aa1e55;
+}
+#content #changelist-filter h2 {
+    border-radius: 4px 4px 0px 0px;
+}
+
+@media (min-width: 767px) {
+    #content #changelist-filter {
+        top: 35px;
+        width: 110px;
+    }
+
+    .change-list .filtered .results,
+    .change-list .filtered .paginator, 
+    .filtered #toolbar, 
+    .filtered div.xfull {
+        margin-right: 115px;
+    }
+}
+
+#content a img.favicon {
+    height: 20px;
+    width: 20px;
+    vertical-align: -5px;
+    padding-right: 6px;
+}
+
+#content td, #content th {
+    vertical-align: middle;
+}
+
+#content #changelist table input {
+    vertical-align: -2px;
+}
+
+
+#content th.field-added, #content td.field-updated {
+    word-break: break-word;
+    min-width: 85px;
+    white-space: normal;
+}
+
+#content th.field-title_str {
+    min-width: 300px;
+}
+
+#content td.field-files {
+    white-space: nowrap;
+}
+#content td.field-size {
+    white-space: nowrap;
+}
+
+#content td.field-url_str {
+    word-break: break-all;
+    min-width: 200px;
+}
+
+#content tr b.status-pending {
+    font-weight: 200;
+    opacity: 0.6;
+}

+ 0 - 0
archivebox/themes/static/archive.png → archivebox/themes/default/static/archive.png


+ 0 - 0
archivebox/themes/static/bootstrap.min.css → archivebox/themes/default/static/bootstrap.min.css


+ 0 - 0
archivebox/themes/static/external.png → archivebox/themes/default/static/external.png


+ 0 - 0
archivebox/themes/static/jquery.dataTables.min.css → archivebox/themes/default/static/jquery.dataTables.min.css


+ 0 - 0
archivebox/themes/static/jquery.dataTables.min.js → archivebox/themes/default/static/jquery.dataTables.min.js


+ 0 - 0
archivebox/themes/static/jquery.min.js → archivebox/themes/default/static/jquery.min.js


+ 0 - 0
archivebox/themes/static/sort_asc.png → archivebox/themes/default/static/sort_asc.png


+ 0 - 0
archivebox/themes/static/sort_both.png → archivebox/themes/default/static/sort_both.png


+ 0 - 0
archivebox/themes/static/sort_desc.png → archivebox/themes/default/static/sort_desc.png


+ 0 - 0
archivebox/themes/static/spinner.gif → archivebox/themes/default/static/spinner.gif


+ 8 - 8
archivebox/themes/legacy/link_details.html

@@ -361,25 +361,25 @@
                     </div>
                     <div class="col-lg-2">
                         <div class="card">
-                          <iframe class="card-img-top" src="$url" sandbox="allow-same-origin allow-scripts allow-forms" scrolling="no"></iframe>
+                          <iframe class="card-img-top" src="$archive_org_path" sandbox="allow-same-origin allow-scripts allow-forms" scrolling="no"></iframe>
                           <div class="card-body">
-                            <a href="$url" style="float:right" title="Open in new tab..." target="_blank" rel="noopener">
+                            <a href="$archive_org_path" style="float:right" title="Open in new tab..." target="_blank" rel="noopener">
                                 <img src="../../static/external.png" class="external"/>
                             </a>
-                            <a href="$url" target="preview"><h4 class="card-title">Original</h4></a>
-                            <p class="card-text">$domain</p>
+                            <a href="$archive_org_path" target="preview"><h4 class="card-title">Archive.Org</h4></a>
+                            <p class="card-text">web.archive.org/web/...</p>
                           </div>
                         </div>
                     </div>
                     <div class="col-lg-2">
                         <div class="card">
-                          <iframe class="card-img-top" src="$archive_org_path" sandbox="allow-same-origin allow-scripts allow-forms" scrolling="no"></iframe>
+                          <iframe class="card-img-top" src="$url" sandbox="allow-same-origin allow-scripts allow-forms" scrolling="no"></iframe>
                           <div class="card-body">
-                            <a href="$archive_org_path" style="float:right" title="Open in new tab..." target="_blank" rel="noopener">
+                            <a href="$url" style="float:right" title="Open in new tab..." target="_blank" rel="noopener">
                                 <img src="../../static/external.png" class="external"/>
                             </a>
-                            <a href="$archive_org_path" target="preview"><h4 class="card-title">Archive.Org</h4></a>
-                            <p class="card-text">web.archive.org/web/...</p>
+                            <a href="$url" target="preview"><h4 class="card-title">Original</h4></a>
+                            <p class="card-text">$domain</p>
                           </div>
                         </div>
                     </div>