1
0
Fork 0
mirror of synced 2024-06-22 16:10:54 +12:00

Merge pull request #460 from cdvv7788/view_refactor

View refactor
This commit is contained in:
Cristian Vargas 2020-09-17 09:09:10 -05:00 committed by GitHub
commit 2767155e59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 534 additions and 104 deletions

View file

@ -69,6 +69,7 @@ CONFIG_DEFAULTS: Dict[str, ConfigDefaultDict] = {
'DEBUG': {'type': bool, 'default': False},
'PUBLIC_INDEX': {'type': bool, 'default': True},
'PUBLIC_SNAPSHOTS': {'type': bool, 'default': True},
'PUBLIC_ADD_VIEW': {'type': bool, 'default': False},
'FOOTER_INFO': {'type': str, 'default': 'Content is hosted for personal archiving purposes only. Contact server owner for any takedown requests.'},
'ACTIVE_THEME': {'type': str, 'default': 'default'},
},

View file

@ -2,7 +2,6 @@ __package__ = 'archivebox.core'
from io import StringIO
from contextlib import redirect_stdout
from pathlib import Path
from django.contrib import admin
from django.urls import path
@ -13,6 +12,7 @@ from django.contrib.auth import get_user_model
from core.models import Snapshot
from core.forms import AddLinkForm
from core.utils import get_icons
from util import htmldecode, urldecode, ansi_to_html
from logging_util import printable_filesize
@ -93,34 +93,7 @@ class SnapshotAdmin(admin.ModelAdmin):
) + mark_safe(f'<span class="tags">{tags}</span>')
def files(self, obj):
link = obj.as_link()
canon = link.canonical_outputs()
out_dir = Path(link.link_dir)
link_tuple = lambda link, method: (link.archive_path, canon[method] or '', canon[method] and (out_dir / (canon[method] or 'notdone')).exists())
return format_html(
'<span class="files-icons" style="font-size: 1.2em; opacity: 0.8">'
'<a href="/{}/{}/" class="exists-{}" title="Wget clone">🌐 </a> '
'<a href="/{}/{}" class="exists-{}" title="PDF">📄</a> '
'<a href="/{}/{}" class="exists-{}" title="Screenshot">🖥 </a> '
'<a href="/{}/{}" class="exists-{}" title="HTML dump">🅷 </a> '
'<a href="/{}/{}/" class="exists-{}" title="WARC">🆆 </a> '
'<a href="/{}/{}" class="exists-{}" title="SingleFile">&#128476; </a>'
'<a href="/{}/{}/" class="exists-{}" title="Media files">📼 </a> '
'<a href="/{}/{}/" class="exists-{}" title="Git repos">📦 </a> '
'<a href="{}" class="exists-{}" title="Archive.org snapshot">🏛 </a> '
'</span>',
*link_tuple(link, 'wget_path'),
*link_tuple(link, 'pdf_path'),
*link_tuple(link, 'screenshot_path'),
*link_tuple(link, 'dom_path'),
*link_tuple(link, 'warc_path')[:2], any((out_dir / canon['warc_path']).glob('*.warc.gz')),
*link_tuple(link, 'singlefile_path'),
*link_tuple(link, 'media_path')[:2], any((out_dir / canon['media_path']).glob('*')),
*link_tuple(link, 'git_path')[:2], any((out_dir / canon['git_path']).glob('*')),
canon['archive_org_path'], (out_dir / 'archive.org.txt').exists(),
)
return get_icons(obj)
def size(self, obj):
return format_html(

View file

@ -5,7 +5,7 @@ from django.views import static
from django.conf import settings
from django.views.generic.base import RedirectView
from core.views import MainIndex, OldIndex, LinkDetails
from core.views import MainIndex, LinkDetails, PublicArchiveView, AddView
# print('DEBUG', settings.DEBUG)
@ -18,7 +18,9 @@ urlpatterns = [
path('archive/', RedirectView.as_view(url='/')),
path('archive/<path:path>', LinkDetails.as_view(), name='LinkAssets'),
path('add/', RedirectView.as_view(url='/admin/core/snapshot/add/')),
path('admin/core/snapshot/add/', RedirectView.as_view(url='/add/')),
path('add/', AddView.as_view()),
path('accounts/login/', RedirectView.as_view(url='/admin/login/')),
path('accounts/logout/', RedirectView.as_view(url='/admin/logout/')),
@ -27,8 +29,8 @@ urlpatterns = [
path('accounts/', include('django.contrib.auth.urls')),
path('admin/', admin.site.urls),
path('old.html', OldIndex.as_view(), name='OldHome'),
path('index.html', RedirectView.as_view(url='/')),
path('index.json', static.serve, {'document_root': settings.OUTPUT_DIR, 'path': 'index.json'}),
path('', MainIndex.as_view(), name='Home'),
path('public/', PublicArchiveView.as_view(), name='public-index'),
]

36
archivebox/core/utils.py Normal file
View file

@ -0,0 +1,36 @@
from pathlib import Path
from django.utils.html import format_html
from core.models import Snapshot
def get_icons(snapshot: Snapshot) -> str:
link = snapshot.as_link()
canon = link.canonical_outputs()
out_dir = Path(link.link_dir)
link_tuple = lambda link, method: (link.archive_path, canon[method] or '', canon[method] and (out_dir / (canon[method] or 'notdone')).exists())
return format_html(
'<span class="files-icons" style="font-size: 1.2em; opacity: 0.8">'
'<a href="/{}/{}/" class="exists-{}" title="Wget clone">🌐 </a> '
'<a href="/{}/{}" class="exists-{}" title="PDF">📄</a> '
'<a href="/{}/{}" class="exists-{}" title="Screenshot">🖥 </a> '
'<a href="/{}/{}" class="exists-{}" title="HTML dump">🅷 </a> '
'<a href="/{}/{}/" class="exists-{}" title="WARC">🆆 </a> '
'<a href="/{}/{}" class="exists-{}" title="SingleFile">&#128476; </a>'
'<a href="/{}/{}/" class="exists-{}" title="Media files">📼 </a> '
'<a href="/{}/{}/" class="exists-{}" title="Git repos">📦 </a> '
'<a href="{}" class="exists-{}" title="Archive.org snapshot">🏛 </a> '
'</span>',
*link_tuple(link, 'wget_path'),
*link_tuple(link, 'pdf_path'),
*link_tuple(link, 'screenshot_path'),
*link_tuple(link, 'dom_path'),
*link_tuple(link, 'warc_path')[:2], any((out_dir / canon['warc_path']).glob('*.warc.gz')),
*link_tuple(link, 'singlefile_path'),
*link_tuple(link, 'media_path')[:2], any((out_dir / canon['media_path']).glob('*')),
*link_tuple(link, 'git_path')[:2], any((out_dir / canon['git_path']).glob('*')),
canon['archive_org_path'], (out_dir / 'archive.org.txt').exists(),
)

View file

@ -1,21 +1,28 @@
__package__ = 'archivebox.core'
from io import StringIO
from contextlib import redirect_stdout
from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.views import View, static
from django.views.generic.list import ListView
from django.views.generic import FormView
from django.contrib.auth.mixins import UserPassesTestMixin
from core.models import Snapshot
from core.utils import get_icons
from core.forms import AddLinkForm
from ..index import load_main_index, load_main_index_meta
from ..config import (
OUTPUT_DIR,
VERSION,
FOOTER_INFO,
PUBLIC_INDEX,
PUBLIC_SNAPSHOTS,
PUBLIC_ADD_VIEW
)
from ..util import base_url
from main import add
from ..util import base_url, ansi_to_html
class MainIndex(View):
@ -26,32 +33,10 @@ class MainIndex(View):
return redirect('/admin/core/snapshot/')
if PUBLIC_INDEX:
return redirect('OldHome')
return redirect('public-index')
return redirect(f'/admin/login/?next={request.path}')
class OldIndex(View):
template = 'main_index.html'
def get(self, request):
if PUBLIC_INDEX or request.user.is_authenticated:
all_links = load_main_index(out_dir=OUTPUT_DIR)
meta_info = load_main_index_meta(out_dir=OUTPUT_DIR)
context = {
'updated': meta_info['updated'],
'num_links': meta_info['num_links'],
'links': all_links,
'VERSION': VERSION,
'FOOTER_INFO': FOOTER_INFO,
}
return render(template_name=self.template, request=request, context=context)
return redirect(f'/admin/login/?next={request.path}')
class LinkDetails(View):
def get(self, request, path):
@ -102,3 +87,60 @@ class LinkDetails(View):
content_type="text/plain",
status=404,
)
class PublicArchiveView(ListView):
template = 'snapshot_list.html'
model = Snapshot
paginate_by = 100
def get_queryset(self, **kwargs):
qs = super().get_queryset(**kwargs)
query = self.request.GET.get('q')
if query:
qs = Snapshot.objects.filter(title__icontains=query)
for snapshot in qs:
snapshot.icons = get_icons(snapshot)
return qs
def get(self, *args, **kwargs):
if PUBLIC_INDEX or self.request.user.is_authenticated:
response = super().get(*args, **kwargs)
return response
else:
return redirect(f'/admin/login/?next={self.request.path}')
class AddView(UserPassesTestMixin, FormView):
template_name = "add_links.html"
form_class = AddLinkForm
def test_func(self):
return PUBLIC_ADD_VIEW or self.request.user.is_authenticated
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context["title"] = "Add URLs"
return context
def form_valid(self, form):
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 = self.get_context_data()
context.update({
"stdout": ansi_to_html(add_stdout.getvalue().strip()),
"form": AddLinkForm()
})
return render(template_name=self.template_name, request=self.request, context=context)

View file

@ -89,7 +89,6 @@
<a href="{% url 'admin:Add' %}">Add </a> /
<a href="{% url 'Home' %}">Snapshots</a> /
<a href="/admin/auth/user/">Users</a> /
<a href="{% url 'OldHome' %}">Old UI</a> /
<a href="{% url 'Docs' %}">Docs</a>
&nbsp; &nbsp;
{% block welcome-msg %}

View file

@ -1,4 +1,6 @@
{% extends "admin/index.html" %}
{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% block breadcrumbs %}
@ -8,48 +10,11 @@
</div>
{% endblock %}
{% 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;
}
</style>
{% block extra_head %}
<link rel="stylesheet" href="{% static 'add.css' %}" />
{% endblock %}
{% block body %}
<div style="max-width: 550px; margin: auto; float: none">
<br/><br/>
{% if stdout %}
@ -63,7 +28,7 @@
<a href="/add" id="submit">&nbsp; Add more URLs </a>
</center>
{% else %}
<form id="add-form" action="?" method="POST" class="p-form">{% csrf_token %}
<form id="add-form" method="POST" class="p-form">{% csrf_token %}
<h1>Add new URLs to your archive</h1>
<br/>
{{ form.as_p }}

View file

@ -0,0 +1,286 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Archived Sites</title>
<meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1">
<style>
:root {
--bg-main: #efefef;
--accent-1: #aa1e55;
--accent-2: #ffebeb;
--accent-3: #efefef;
--text-1: #1c1c1c;
--text-2: #eaeaea;
--text-main: #1a1a1a;
--font-main: "Gill Sans", Helvetica, sans-serif;
}
/* Dark Mode (WIP) */
/*
@media (prefers-color-scheme: dark) {
:root {
--accent-2: hsl(160, 100%, 96%);
--text-1: #eaeaea;
--text-2: #1a1a1a;
--bg-main: #101010;
}
#table-bookmarks_wrapper,
#table-bookmarks_wrapper img,
tbody td:nth-child(3),
tbody td:nth-child(3) span,
footer {
filter: invert(100%);
}
}*/
html,
body {
width: 100%;
height: 100%;
font-size: 18px;
font-weight: 200;
text-align: center;
margin: 0px;
padding: 0px;
font-family: var(--font-main);
}
.header-top small {
font-weight: 200;
color: var(--accent-3);
}
.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 var(--accent-1);
background-color: var(--accent-1);
}
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;
}
#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: var(--accent-2) !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: var(--accent-3);
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;
}
</style>
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'jquery.dataTables.min.css' %}" />
{% block extra_head %}
{% endblock %}
<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({
searching: false,
paging: false,
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>
<base href="{% url 'Home' %}">
</head>
<body>
<header>
<div class="header-top container-fluid">
<div class="row nav">
<div class="col-sm-2">
<a href="{% url 'public-index' %}" class="header-archivebox" title="Last updated: {{updated}}">
<img src="{% static 'archive.png' %}" alt="Logo" />
ArchiveBox: Index
</a>
</div>
<div class="col-sm-10" style="text-align: right">
<a href="/add/">Add Links</a> &nbsp; | &nbsp;
<a href="/admin/core/snapshot/">Admin</a> &nbsp; | &nbsp;
<a href="https://github.com/pirate/ArchiveBox/wiki">Docs</a>
</div>
</div>
</div>
</header>
{% block body %}
{% endblock %}
<br>
<footer>
<br />
<center>
<small>
Archive created using <a href="https://github.com/pirate/ArchiveBox" title="Github">ArchiveBox</a> &nbsp; |
&nbsp;
Download index as <a href="index.json" title="JSON summary of archived links.">JSON</a>
<br /><br />
{{FOOTER_INFO}}
</small>
</center>
<br />
</footer>
</body>
</html>

View file

@ -0,0 +1,64 @@
{% extends "base.html" %}
{% load static %}
{% block body %}
<br>
<form action="{% url 'public-index' %}" method="get">
<input name="q" type="text" placeholder="Search...">
<button type="submit">Search</button>
<button onclick="location.href='{% url 'public-index' %}'" type="button">
Reload Index</button>
</form>
<table id="table-bookmarks">
<thead>
<tr>
<th style="width: 100px;">Bookmarked</th>
<th style="width: 26vw;">Saved Link ({{num_links}})</th>
<th style="width: 50px">Files</th>
<th style="width: 16vw;whitespace:nowrap;overflow-x:hidden;">Original URL</th>
</tr>
</thead>
<tbody>
{% for link in object_list %}
<tr>
<td title="{{link.timestamp}}">{{link.added}}</td>
<td class="title-col">
{% if link.is_archived %}
<a href="archive/{{link.timestamp}}/index.html"><img src="archive/{{link.timestamp}}/favicon.ico" class="link-favicon" decoding="async"></a>
{% else %}
<a href="archive/{{link.timestamp}}/index.html"><img src="{% static 'spinner.gif' %}" class="link-favicon" decoding="async"></a>
{% endif %}
<a href="archive/{{link.timestamp}}/index.html" title="{{link.title}}">
<span data-title-for="{{link.url}}" data-archived="{{link.is_archived}}">{{link.title|default:'Loading...'}}</span>
<small style="float:right">{{link.tags|default:''}}</small>
</a>
</td>
<td>
<a href="archive/{{link.timestamp}}/index.html">📄
<span data-number-for="{{link.url}}" title="Fetching any missing files...">{{link.icons}} <img src="{% static 'spinner.gif' %}" class="files-spinner" decoding="async"/></span>
</a>
</td>
<td style="text-align:left"><a href="{{link.url}}">{{link.url}}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<center>
<span class="step-links">
{% if page_obj.has_previous %}
<a href="{% url 'public-index' %}?page=1">&laquo; first</a>
<a href="{% url 'public-index' %}?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="{% url 'public-index' %}?page={{ page_obj.next_page_number }}">next </a>
<a href="{% url 'public-index' %}?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
{% endif %}
</span>
<br>
</center>
{% endblock %}

View file

@ -0,0 +1,62 @@
.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;
}
ul#id_depth {
list-style-type: none;
padding: 0;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.loader {
border: 16px solid #f3f3f3; /* Light grey */
border-top: 16px solid #3498db; /* Blue */
border-radius: 50%;
width: 30px;
height: 30px;
box-sizing: border-box;
animation: spin 2s linear infinite;
}