FossilRepo

Add system dashboard with charts, wiki/ticket CRUD forms Dashboard: - Stats cards: projects, total checkins, tickets, wiki pages - System-wide activity chart (26 weeks, Chart.js) - Recent activity feed across all projects with clickable links - Sidebar quick links Wiki CRUD: - New page form (name + markdown content) - Edit page form (pre-filled with current content) - "New Page" button on wiki list, "Edit" button on page detail - Uses fossil wiki create/commit CLI commands Ticket CRUD: - New ticket form (title, type, severity, description) - "New Ticket" button on ticket list - Uses fossil ticket add CLI command CLI extensions: - wiki_commit(), wiki_create(), ticket_add() methods on FossilCLI

lmata 2026-04-06 21:03 trunk
Commit 1c110be73edc162338dfaabe1e26e6fcbbb3b08232cdb59c8b38c79552b3d46f
+56 -1
--- core/views.py
+++ core/views.py
@@ -1,7 +1,62 @@
1
+import json
2
+
13
from django.contrib.auth.decorators import login_required
24
from django.shortcuts import render
35
46
57
@login_required
68
def dashboard(request):
7
- return render(request, "dashboard.html")
9
+ from fossil.models import FossilRepository
10
+ from fossil.reader import FossilReader
11
+ from projects.models import Project
12
+
13
+ # Aggregate stats across all projects
14
+ total_projects = Project.objects.count()
15
+ total_checkins = 0
16
+ total_tickets = 0
17
+ total_wiki = 0
18
+ system_activity = [] # weekly commit counts across all repos
19
+ recent_across_all = []
20
+
21
+ repos = FossilRepository.objects.filter(deleted_at__isnull=True)
22
+ for repo in repos:
23
+ if not repo.exists_on_disk:
24
+ continue
25
+ try:
26
+ with FossilReader(repo.full_path) as reader:
27
+ meta = reader.get_metadata()
28
+ total_checkins += meta.checkin_count
29
+ total_tickets += meta.ticket_count
30
+ total_wiki += meta.wiki_page_count
31
+
32
+ activity = reader.get_commit_activity(weeks=26)
33
+ if not system_activity:
34
+ system_activity = [c["count"] for c in activity]
35
+ else:
36
+ for i, c in enumerate(activity):
37
+ if i < len(system_activity):
38
+ system_activity[i] += c["count"]
39
+
40
+ commits = reader.get_timeline(limit=3, event_type="ci")
41
+ for c in commits:
42
+ recent_across_all.append({"project": repo.project, "entry": c})
43
+ except Exception:
44
+ continue
45
+
46
+ # Sort recent across all by timestamp, take top 10
47
+ recent_across_all.sort(key=lambda x: x["entry"].timestamp, reverse=True)
48
+ recent_across_all = recent_across_all[:10]
49
+
50
+ return render(
51
+ request,
52
+ "dashboard.html",
53
+ {
54
+ "total_projects": total_projects,
55
+ "total_checkins": total_checkins,
56
+ "total_tickets": total_tickets,
57
+ "total_wiki": total_wiki,
58
+ "total_repos": repos.count(),
59
+ "system_activity_json": json.dumps(system_activity),
60
+ "recent_across_all": recent_across_all,
61
+ },
62
+ )
863
--- core/views.py
+++ core/views.py
@@ -1,7 +1,62 @@
 
 
1 from django.contrib.auth.decorators import login_required
2 from django.shortcuts import render
3
4
5 @login_required
6 def dashboard(request):
7 return render(request, "dashboard.html")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
--- core/views.py
+++ core/views.py
@@ -1,7 +1,62 @@
1 import json
2
3 from django.contrib.auth.decorators import login_required
4 from django.shortcuts import render
5
6
7 @login_required
8 def dashboard(request):
9 from fossil.models import FossilRepository
10 from fossil.reader import FossilReader
11 from projects.models import Project
12
13 # Aggregate stats across all projects
14 total_projects = Project.objects.count()
15 total_checkins = 0
16 total_tickets = 0
17 total_wiki = 0
18 system_activity = [] # weekly commit counts across all repos
19 recent_across_all = []
20
21 repos = FossilRepository.objects.filter(deleted_at__isnull=True)
22 for repo in repos:
23 if not repo.exists_on_disk:
24 continue
25 try:
26 with FossilReader(repo.full_path) as reader:
27 meta = reader.get_metadata()
28 total_checkins += meta.checkin_count
29 total_tickets += meta.ticket_count
30 total_wiki += meta.wiki_page_count
31
32 activity = reader.get_commit_activity(weeks=26)
33 if not system_activity:
34 system_activity = [c["count"] for c in activity]
35 else:
36 for i, c in enumerate(activity):
37 if i < len(system_activity):
38 system_activity[i] += c["count"]
39
40 commits = reader.get_timeline(limit=3, event_type="ci")
41 for c in commits:
42 recent_across_all.append({"project": repo.project, "entry": c})
43 except Exception:
44 continue
45
46 # Sort recent across all by timestamp, take top 10
47 recent_across_all.sort(key=lambda x: x["entry"].timestamp, reverse=True)
48 recent_across_all = recent_across_all[:10]
49
50 return render(
51 request,
52 "dashboard.html",
53 {
54 "total_projects": total_projects,
55 "total_checkins": total_checkins,
56 "total_tickets": total_tickets,
57 "total_wiki": total_wiki,
58 "total_repos": repos.count(),
59 "system_activity_json": json.dumps(system_activity),
60 "recent_across_all": recent_across_all,
61 },
62 )
63
--- fossil/cli.py
+++ fossil/cli.py
@@ -32,5 +32,28 @@
3232
try:
3333
self._run("version")
3434
return True
3535
except (FileNotFoundError, subprocess.CalledProcessError):
3636
return False
37
+
38
+ def wiki_commit(self, repo_path: Path, page_name: str, content: str, user: str = "") -> bool:
39
+ """Create or update a wiki page. Pipes content to fossil wiki commit."""
40
+ cmd = [self.binary, "wiki", "commit", page_name, "-R", str(repo_path)]
41
+ if user:
42
+ cmd.extend(["--technote-user", user])
43
+ result = subprocess.run(cmd, input=content, capture_output=True, text=True, timeout=30)
44
+ return result.returncode == 0
45
+
46
+ def wiki_create(self, repo_path: Path, page_name: str, content: str) -> bool:
47
+ """Create a new wiki page."""
48
+ cmd = [self.binary, "wiki", "create", page_name, "-R", str(repo_path)]
49
+ result = subprocess.run(cmd, input=content, capture_output=True, text=True, timeout=30)
50
+ return result.returncode == 0
51
+
52
+ def ticket_add(self, repo_path: Path, fields: dict) -> bool:
53
+ """Add a new ticket. Fields dict maps field names to values."""
54
+ cmd = [self.binary, "ticket", "add", "-R", str(repo_path)]
55
+ for key, value in fields.items():
56
+ cmd.append(f"{key}")
57
+ cmd.append(f"{value}")
58
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
59
+ return result.returncode == 0
3760
--- fossil/cli.py
+++ fossil/cli.py
@@ -32,5 +32,28 @@
32 try:
33 self._run("version")
34 return True
35 except (FileNotFoundError, subprocess.CalledProcessError):
36 return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
--- fossil/cli.py
+++ fossil/cli.py
@@ -32,5 +32,28 @@
32 try:
33 self._run("version")
34 return True
35 except (FileNotFoundError, subprocess.CalledProcessError):
36 return False
37
38 def wiki_commit(self, repo_path: Path, page_name: str, content: str, user: str = "") -> bool:
39 """Create or update a wiki page. Pipes content to fossil wiki commit."""
40 cmd = [self.binary, "wiki", "commit", page_name, "-R", str(repo_path)]
41 if user:
42 cmd.extend(["--technote-user", user])
43 result = subprocess.run(cmd, input=content, capture_output=True, text=True, timeout=30)
44 return result.returncode == 0
45
46 def wiki_create(self, repo_path: Path, page_name: str, content: str) -> bool:
47 """Create a new wiki page."""
48 cmd = [self.binary, "wiki", "create", page_name, "-R", str(repo_path)]
49 result = subprocess.run(cmd, input=content, capture_output=True, text=True, timeout=30)
50 return result.returncode == 0
51
52 def ticket_add(self, repo_path: Path, fields: dict) -> bool:
53 """Add a new ticket. Fields dict maps field names to values."""
54 cmd = [self.binary, "ticket", "add", "-R", str(repo_path)]
55 for key, value in fields.items():
56 cmd.append(f"{key}")
57 cmd.append(f"{value}")
58 result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
59 return result.returncode == 0
60
--- fossil/urls.py
+++ fossil/urls.py
@@ -11,10 +11,13 @@
1111
path("timeline/", views.timeline, name="timeline"),
1212
path("checkin/<str:checkin_uuid>/", views.checkin_detail, name="checkin_detail"),
1313
path("tickets/", views.ticket_list, name="tickets"),
1414
path("tickets/<str:ticket_uuid>/", views.ticket_detail, name="ticket_detail"),
1515
path("wiki/", views.wiki_list, name="wiki"),
16
+ path("wiki/create/", views.wiki_create, name="wiki_create"),
1617
path("wiki/page/<path:page_name>", views.wiki_page, name="wiki_page"),
18
+ path("wiki/edit/<path:page_name>", views.wiki_edit, name="wiki_edit"),
19
+ path("tickets/create/", views.ticket_create, name="ticket_create"),
1720
path("forum/", views.forum_list, name="forum"),
1821
path("forum/<str:thread_uuid>/", views.forum_thread, name="forum_thread"),
1922
path("user/<str:username>/", views.user_activity, name="user_activity"),
2023
]
2124
--- fossil/urls.py
+++ fossil/urls.py
@@ -11,10 +11,13 @@
11 path("timeline/", views.timeline, name="timeline"),
12 path("checkin/<str:checkin_uuid>/", views.checkin_detail, name="checkin_detail"),
13 path("tickets/", views.ticket_list, name="tickets"),
14 path("tickets/<str:ticket_uuid>/", views.ticket_detail, name="ticket_detail"),
15 path("wiki/", views.wiki_list, name="wiki"),
 
16 path("wiki/page/<path:page_name>", views.wiki_page, name="wiki_page"),
 
 
17 path("forum/", views.forum_list, name="forum"),
18 path("forum/<str:thread_uuid>/", views.forum_thread, name="forum_thread"),
19 path("user/<str:username>/", views.user_activity, name="user_activity"),
20 ]
21
--- fossil/urls.py
+++ fossil/urls.py
@@ -11,10 +11,13 @@
11 path("timeline/", views.timeline, name="timeline"),
12 path("checkin/<str:checkin_uuid>/", views.checkin_detail, name="checkin_detail"),
13 path("tickets/", views.ticket_list, name="tickets"),
14 path("tickets/<str:ticket_uuid>/", views.ticket_detail, name="ticket_detail"),
15 path("wiki/", views.wiki_list, name="wiki"),
16 path("wiki/create/", views.wiki_create, name="wiki_create"),
17 path("wiki/page/<path:page_name>", views.wiki_page, name="wiki_page"),
18 path("wiki/edit/<path:page_name>", views.wiki_edit, name="wiki_edit"),
19 path("tickets/create/", views.ticket_create, name="ticket_create"),
20 path("forum/", views.forum_list, name="forum"),
21 path("forum/<str:thread_uuid>/", views.forum_thread, name="forum_thread"),
22 path("user/<str:username>/", views.user_activity, name="user_activity"),
23 ]
24
--- fossil/views.py
+++ fossil/views.py
@@ -574,10 +574,104 @@
574574
"thread_uuid": thread_uuid,
575575
"active_tab": "forum",
576576
},
577577
)
578578
579
+
580
+# --- Wiki CRUD ---
581
+
582
+
583
+@login_required
584
+def wiki_create(request, slug):
585
+ P.PROJECT_CHANGE.check(request.user)
586
+ project, fossil_repo, reader = _get_repo_and_reader(slug)
587
+
588
+ if request.method == "POST":
589
+ page_name = request.POST.get("name", "").strip()
590
+ content = request.POST.get("content", "")
591
+ if page_name:
592
+ from fossil.cli import FossilCLI
593
+
594
+ cli = FossilCLI()
595
+ # Try create first, fall back to commit (update)
596
+ success = cli.wiki_create(fossil_repo.full_path, page_name, content)
597
+ if not success:
598
+ success = cli.wiki_commit(fossil_repo.full_path, page_name, content)
599
+ if success:
600
+ from django.contrib import messages
601
+
602
+ messages.success(request, f'Wiki page "{page_name}" created.')
603
+ from django.shortcuts import redirect
604
+
605
+ return redirect("fossil:wiki_page", slug=slug, page_name=page_name)
606
+
607
+ return render(request, "fossil/wiki_form.html", {"project": project, "active_tab": "wiki", "title": "New Wiki Page"})
608
+
609
+
610
+@login_required
611
+def wiki_edit(request, slug, page_name):
612
+ P.PROJECT_CHANGE.check(request.user)
613
+ project, fossil_repo, reader = _get_repo_and_reader(slug)
614
+
615
+ with reader:
616
+ page = reader.get_wiki_page(page_name)
617
+
618
+ if not page:
619
+ raise Http404(f"Wiki page not found: {page_name}")
620
+
621
+ if request.method == "POST":
622
+ content = request.POST.get("content", "")
623
+ from fossil.cli import FossilCLI
624
+
625
+ cli = FossilCLI()
626
+ success = cli.wiki_commit(fossil_repo.full_path, page_name, content)
627
+ if success:
628
+ from django.contrib import messages
629
+
630
+ messages.success(request, f'Wiki page "{page_name}" updated.')
631
+ from django.shortcuts import redirect
632
+
633
+ return redirect("fossil:wiki_page", slug=slug, page_name=page_name)
634
+
635
+ return render(
636
+ request,
637
+ "fossil/wiki_form.html",
638
+ {"project": project, "page": page, "active_tab": "wiki", "title": f"Edit: {page_name}"},
639
+ )
640
+
641
+
642
+# --- Ticket CRUD ---
643
+
644
+
645
+@login_required
646
+def ticket_create(request, slug):
647
+ P.PROJECT_CHANGE.check(request.user)
648
+ project, fossil_repo, reader = _get_repo_and_reader(slug)
649
+
650
+ if request.method == "POST":
651
+ title = request.POST.get("title", "").strip()
652
+ body = request.POST.get("body", "")
653
+ ticket_type = request.POST.get("type", "Code_Defect")
654
+ severity = request.POST.get("severity", "")
655
+ if title:
656
+ from fossil.cli import FossilCLI
657
+
658
+ cli = FossilCLI()
659
+ fields = {"title": title, "type": ticket_type, "comment": body, "status": "Open"}
660
+ if severity:
661
+ fields["severity"] = severity
662
+ success = cli.ticket_add(fossil_repo.full_path, fields)
663
+ if success:
664
+ from django.contrib import messages
665
+
666
+ messages.success(request, f'Ticket "{title}" created.')
667
+ from django.shortcuts import redirect
668
+
669
+ return redirect("fossil:tickets", slug=slug)
670
+
671
+ return render(request, "fossil/ticket_form.html", {"project": project, "active_tab": "tickets", "title": "New Ticket"})
672
+
579673
580674
# --- User Activity ---
581675
582676
583677
@login_required
584678
--- fossil/views.py
+++ fossil/views.py
@@ -574,10 +574,104 @@
574 "thread_uuid": thread_uuid,
575 "active_tab": "forum",
576 },
577 )
578
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
580 # --- User Activity ---
581
582
583 @login_required
584
--- fossil/views.py
+++ fossil/views.py
@@ -574,10 +574,104 @@
574 "thread_uuid": thread_uuid,
575 "active_tab": "forum",
576 },
577 )
578
579
580 # --- Wiki CRUD ---
581
582
583 @login_required
584 def wiki_create(request, slug):
585 P.PROJECT_CHANGE.check(request.user)
586 project, fossil_repo, reader = _get_repo_and_reader(slug)
587
588 if request.method == "POST":
589 page_name = request.POST.get("name", "").strip()
590 content = request.POST.get("content", "")
591 if page_name:
592 from fossil.cli import FossilCLI
593
594 cli = FossilCLI()
595 # Try create first, fall back to commit (update)
596 success = cli.wiki_create(fossil_repo.full_path, page_name, content)
597 if not success:
598 success = cli.wiki_commit(fossil_repo.full_path, page_name, content)
599 if success:
600 from django.contrib import messages
601
602 messages.success(request, f'Wiki page "{page_name}" created.')
603 from django.shortcuts import redirect
604
605 return redirect("fossil:wiki_page", slug=slug, page_name=page_name)
606
607 return render(request, "fossil/wiki_form.html", {"project": project, "active_tab": "wiki", "title": "New Wiki Page"})
608
609
610 @login_required
611 def wiki_edit(request, slug, page_name):
612 P.PROJECT_CHANGE.check(request.user)
613 project, fossil_repo, reader = _get_repo_and_reader(slug)
614
615 with reader:
616 page = reader.get_wiki_page(page_name)
617
618 if not page:
619 raise Http404(f"Wiki page not found: {page_name}")
620
621 if request.method == "POST":
622 content = request.POST.get("content", "")
623 from fossil.cli import FossilCLI
624
625 cli = FossilCLI()
626 success = cli.wiki_commit(fossil_repo.full_path, page_name, content)
627 if success:
628 from django.contrib import messages
629
630 messages.success(request, f'Wiki page "{page_name}" updated.')
631 from django.shortcuts import redirect
632
633 return redirect("fossil:wiki_page", slug=slug, page_name=page_name)
634
635 return render(
636 request,
637 "fossil/wiki_form.html",
638 {"project": project, "page": page, "active_tab": "wiki", "title": f"Edit: {page_name}"},
639 )
640
641
642 # --- Ticket CRUD ---
643
644
645 @login_required
646 def ticket_create(request, slug):
647 P.PROJECT_CHANGE.check(request.user)
648 project, fossil_repo, reader = _get_repo_and_reader(slug)
649
650 if request.method == "POST":
651 title = request.POST.get("title", "").strip()
652 body = request.POST.get("body", "")
653 ticket_type = request.POST.get("type", "Code_Defect")
654 severity = request.POST.get("severity", "")
655 if title:
656 from fossil.cli import FossilCLI
657
658 cli = FossilCLI()
659 fields = {"title": title, "type": ticket_type, "comment": body, "status": "Open"}
660 if severity:
661 fields["severity"] = severity
662 success = cli.ticket_add(fossil_repo.full_path, fields)
663 if success:
664 from django.contrib import messages
665
666 messages.success(request, f'Ticket "{title}" created.')
667 from django.shortcuts import redirect
668
669 return redirect("fossil:tickets", slug=slug)
670
671 return render(request, "fossil/ticket_form.html", {"project": project, "active_tab": "tickets", "title": "New Ticket"})
672
673
674 # --- User Activity ---
675
676
677 @login_required
678
--- templates/dashboard.html
+++ templates/dashboard.html
@@ -1,54 +1,141 @@
11
{% extends "base.html" %}
22
{% load static %}
33
{% block title %}Dashboard — Fossilrepo{% endblock %}
44
5
-{% block content %}
6
-<div class="md:flex md:items-center md:justify-between mb-8">
7
- <h1 class="text-2xl font-bold text-gray-100">Dashboard</h1>
8
-</div>
9
-
10
-<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
11
- {% if perms.projects.view_project %}
12
- <a href="{% url 'projects:list' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all">
13
- <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Projects</h3>
14
- <p class="mt-2 text-sm text-gray-400">Manage projects and their team access controls.</p>
15
- </a>
16
- {% endif %}
17
-
18
- {% if perms.organization.view_team %}
19
- <a href="{% url 'organization:team_list' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all">
20
- <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Teams</h3>
21
- <p class="mt-2 text-sm text-gray-400">Organize members into teams for project access.</p>
22
- </a>
23
- {% endif %}
24
-
25
- {% if perms.pages.view_page %}
26
- <a href="{% url 'pages:list' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all">
27
- <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Knowledge Base</h3>
28
- <p class="mt-2 text-sm text-gray-400">Org-wide guides, runbooks, and internal documentation.</p>
29
- </a>
30
- {% endif %}
31
-
32
- {% if perms.organization.view_organization %}
33
- <a href="{% url 'organization:settings' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all">
34
- <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Settings</h3>
35
- <p class="mt-2 text-sm text-gray-400">Organization settings, members, and configuration.</p>
36
- </a>
37
- {% endif %}
38
-
39
- {% if user.is_staff %}
40
- <a href="{% url 'admin:index' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all">
41
- <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Admin</h3>
42
- <p class="mt-2 text-sm text-gray-400">Django admin for managing users, groups, and permissions.</p>
43
- </a>
44
- {% endif %}
45
-
46
- <div class="rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm">
47
- <div class="flex items-center gap-4 mb-3">
48
- <img src="{% static 'img/fossilrepo-logo-dark.png' %}" alt="Fossilrepo" class="h-8 w-auto">
49
- </div>
50
- <h3 class="text-lg font-semibold text-gray-100">Welcome, {{ user.get_full_name|default:user.username }}</h3>
51
- <p class="mt-2 text-sm text-gray-400">Self-hosted Fossil forge with Django + HTMX management layer.</p>
52
- </div>
53
-</div>
5
+{% block extra_head %}
6
+<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
7
+{% endblock %}
8
+
9
+{% block content %}
10
+<div class="mb-6">
11
+ <h1 class="text-2xl font-bold text-gray-100">Dashboard</h1>
12
+ <p class="mt-1 text-sm text-gray-400">Welcome back, {{ user.get_full_name|default:user.username }}</p>
13
+</div>
14
+
15
+<!-- Stats cards -->
16
+<div class="grid grid-cols-2 gap-4 sm:grid-cols-4 mb-6">
17
+ <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
18
+ <div class="text-2xl font-bold text-gray-100">{{ total_projects }}</div>
19
+ <div class="text-xs text-gray-500 mt-1">Projects</div>
20
+ </div>
21
+ <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
22
+ <div class="text-2xl font-bold text-gray-100">{{ total_checkins|default:"0" }}</div>
23
+ <div class="text-xs text-gray-500 mt-1">Total Checkins</div>
24
+ </div>
25
+ <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
26
+ <div class="text-2xl font-bold text-gray-100">{{ total_tickets|default:"0" }}</div>
27
+ <div class="text-xs text-gray-500 mt-1">Tickets</div>
28
+ </div>
29
+ <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
30
+ <div class="text-2xl font-bold text-gray-100">{{ total_wiki|default:"0" }}</div>
31
+ <div class="text-xs text-gray-500 mt-1">Wiki Pages</div>
32
+ </div>
33
+</div>
34
+
35
+<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
36
+ <!-- Main column -->
37
+ <div class="lg:col-span-2 space-y-6">
38
+ {% if system_activity_json and system_activity_json != "[]" %}
39
+ <!-- System-wide activity chart -->
40
+ <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
41
+ <h3 class="text-sm font-medium text-gray-300 mb-3">System Activity (26 weeks)</h3>
42
+ <div style="height: 140px;">
43
+ <canvas id="systemChart"></canvas>
44
+ </div>
45
+ </div>
46
+ {% endif %}
47
+
48
+ {% if recent_across_all %}
49
+ <!-- Recent activity across all projects -->
50
+ <div class="rounded-lg bg-gray-800 border border-gray-700">
51
+ <div class="px-4 py-3 border-b border-gray-700">
52
+ <h3 class="text-sm font-medium text-gray-300">Recent Activity</h3>
53
+ </div>
54
+ <div class="divide-y divide-gray-700">
55
+ {% for item in recent_across_all %}
56
+ <div class="px-4 py-3 flex items-start gap-3">
57
+ <div class="flex-shrink-0 mt-1">
58
+ <div class="w-2.5 h-2.5 rounded-full bg-brand"></div>
59
+ </div>
60
+ <div class="flex-1 min-w-0">
61
+ <a href="{% url 'fossil:checkin_detail' slug=item.project.slug checkin_uuid=item.entry.uuid %}"
62
+ class="text-sm text-gray-200 hover:text-brand-light">{{ item.entry.comment|truncatechars:70 }}</a>
63
+ <div class="mt-0.5 flex items-center gap-3 text-xs text-gray-500">
64
+ <a href="{% url 'projects:detail' slug=item.project.slug %}" class="text-brand-light hover:text-brand">{{ item.project.name }}</a>
65
+ <a href="{% url 'fossil:user_activity' slug=item.project.slug username=item.entry.user %}" class="hover:text-gray-300">{{ item.entry.user }}</a>
66
+ <a href="{% url 'fossil:checkin_detail' slug=item.project.slug checkin_uuid=item.entry.uuid %}" class="font-mono text-brand-light hover:text-brand">{{ item.entry.uuid|truncatechars:10 }}</a>
67
+ <span>{{ item.entry.timestamp|timesince }} ago</span>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ {% endfor %}
72
+ </div>
73
+ </div>
74
+ {% endif %}
75
+ </div>
76
+
77
+ <!-- Sidebar -->
78
+ <div class="space-y-4">
79
+ <!-- Quick links -->
80
+ {% if perms.projects.view_project %}
81
+ <a href="{% url 'projects:list' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors">
82
+ <h3 class="text-sm font-semibold text-gray-100">Projects</h3>
83
+ <p class="mt-1 text-xs text-gray-500">Manage projects and team access</p>
84
+ </a>
85
+ {% endif %}
86
+ {% if perms.organization.view_team %}
87
+ <a href="{% url 'organization:team_list' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors">
88
+ <h3 class="text-sm font-semibold text-gray-100">Teams</h3>
89
+ <p class="mt-1 text-xs text-gray-500">Organize members into teams</p>
90
+ </a>
91
+ {% endif %}
92
+ {% if perms.pages.view_page %}
93
+ <a href="{% url 'pages:list' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors">
94
+ <h3 class="text-sm font-semibold text-gray-100">Knowledge Base</h3>
95
+ <p class="mt-1 text-xs text-gray-500">Guides, runbooks, documentation</p>
96
+ </a>
97
+ {% endif %}
98
+ {% if perms.organization.view_organization %}
99
+ <a href="{% url 'organization:settings' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors">
100
+ <h3 class="text-sm font-semibold text-gray-100">Settings</h3>
101
+ <p class="mt-1 text-xs text-gray-500">Organization configuration</p>
102
+ </a>
103
+ {% endif %}
104
+ {% if user.is_staff %}
105
+ <a href="{% url 'admin:index' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors">
106
+ <h3 class="text-sm font-semibold text-gray-100">Admin</h3>
107
+ <p class="mt-1 text-xs text-gray-500">Users, groups, permissions</p>
108
+ </a>
109
+ {% endif %}
110
+ </div>
111
+</div>
112
+
113
+{% if system_activity_json and system_activity_json != "[]" %}
114
+<script>
115
+ new Chart(document.getElementById('systemChart').getContext('2d'), {
116
+ type: 'bar',
117
+ data: {
118
+ labels: {{ system_activity_json|safe }}.map((_, i) => ''),
119
+ datasets: [{
120
+ data: {{ system_activity_json|safe }},
121
+ backgroundColor: '#DC394C',
122
+ borderRadius: 2,
123
+ barPercentage: 0.8,
124
+ categoryPercentage: 0.9,
125
+ }]
126
+ },
127
+ options: {
128
+ responsive: true,
129
+ maintainAspectRatio: false,
130
+ plugins: { legend: { display: false }, tooltip: {
131
+ callbacks: { title: (items) => { const w = 25 - items[0].dataIndex; return w === 0 ? 'This week' : w + ' week' + (w > 1 ? 's' : '') + ' ago'; } }
132
+ }},
133
+ scales: {
134
+ x: { display: false, grid: { display: false } },
135
+ y: { display: false, grid: { display: false }, beginAtZero: true }
136
+ }
137
+ }
138
+ });
139
+</script>
140
+{% endif %}
54141
{% endblock %}
55142
56143
ADDED templates/fossil/ticket_form.html
--- templates/dashboard.html
+++ templates/dashboard.html
@@ -1,54 +1,141 @@
1 {% extends "base.html" %}
2 {% load static %}
3 {% block title %}Dashboard — Fossilrepo{% endblock %}
4
5 {% block content %}
6 <div class="md:flex md:items-center md:justify-between mb-8">
7 <h1 class="text-2xl font-bold text-gray-100">Dashboard</h1>
8 </div>
9
10 <div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
11 {% if perms.projects.view_project %}
12 <a href="{% url 'projects:list' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all">
13 <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Projects</h3>
14 <p class="mt-2 text-sm text-gray-400">Manage projects and their team access controls.</p>
15 </a>
16 {% endif %}
17
18 {% if perms.organization.view_team %}
19 <a href="{% url 'organization:team_list' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all">
20 <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Teams</h3>
21 <p class="mt-2 text-sm text-gray-400">Organize members into teams for project access.</p>
22 </a>
23 {% endif %}
24
25 {% if perms.pages.view_page %}
26 <a href="{% url 'pages:list' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all">
27 <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Knowledge Base</h3>
28 <p class="mt-2 text-sm text-gray-400">Org-wide guides, runbooks, and internal documentation.</p>
29 </a>
30 {% endif %}
31
32 {% if perms.organization.view_organization %}
33 <a href="{% url 'organization:settings' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all">
34 <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Settings</h3>
35 <p class="mt-2 text-sm text-gray-400">Organization settings, members, and configuration.</p>
36 </a>
37 {% endif %}
38
39 {% if user.is_staff %}
40 <a href="{% url 'admin:index' %}" class="group rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm hover:shadow-md hover:border-brand transition-all">
41 <h3 class="text-lg font-semibold text-gray-100 group-hover:text-brand">Admin</h3>
42 <p class="mt-2 text-sm text-gray-400">Django admin for managing users, groups, and permissions.</p>
43 </a>
44 {% endif %}
45
46 <div class="rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-sm">
47 <div class="flex items-center gap-4 mb-3">
48 <img src="{% static 'img/fossilrepo-logo-dark.png' %}" alt="Fossilrepo" class="h-8 w-auto">
49 </div>
50 <h3 class="text-lg font-semibold text-gray-100">Welcome, {{ user.get_full_name|default:user.username }}</h3>
51 <p class="mt-2 text-sm text-gray-400">Self-hosted Fossil forge with Django + HTMX management layer.</p>
52 </div>
53 </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54 {% endblock %}
55
56 DDED templates/fossil/ticket_form.html
--- templates/dashboard.html
+++ templates/dashboard.html
@@ -1,54 +1,141 @@
1 {% extends "base.html" %}
2 {% load static %}
3 {% block title %}Dashboard — Fossilrepo{% endblock %}
4
5 {% block extra_head %}
6 <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
7 {% endblock %}
8
9 {% block content %}
10 <div class="mb-6">
11 <h1 class="text-2xl font-bold text-gray-100">Dashboard</h1>
12 <p class="mt-1 text-sm text-gray-400">Welcome back, {{ user.get_full_name|default:user.username }}</p>
13 </div>
14
15 <!-- Stats cards -->
16 <div class="grid grid-cols-2 gap-4 sm:grid-cols-4 mb-6">
17 <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
18 <div class="text-2xl font-bold text-gray-100">{{ total_projects }}</div>
19 <div class="text-xs text-gray-500 mt-1">Projects</div>
20 </div>
21 <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
22 <div class="text-2xl font-bold text-gray-100">{{ total_checkins|default:"0" }}</div>
23 <div class="text-xs text-gray-500 mt-1">Total Checkins</div>
24 </div>
25 <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
26 <div class="text-2xl font-bold text-gray-100">{{ total_tickets|default:"0" }}</div>
27 <div class="text-xs text-gray-500 mt-1">Tickets</div>
28 </div>
29 <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
30 <div class="text-2xl font-bold text-gray-100">{{ total_wiki|default:"0" }}</div>
31 <div class="text-xs text-gray-500 mt-1">Wiki Pages</div>
32 </div>
33 </div>
34
35 <div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
36 <!-- Main column -->
37 <div class="lg:col-span-2 space-y-6">
38 {% if system_activity_json and system_activity_json != "[]" %}
39 <!-- System-wide activity chart -->
40 <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
41 <h3 class="text-sm font-medium text-gray-300 mb-3">System Activity (26 weeks)</h3>
42 <div style="height: 140px;">
43 <canvas id="systemChart"></canvas>
44 </div>
45 </div>
46 {% endif %}
47
48 {% if recent_across_all %}
49 <!-- Recent activity across all projects -->
50 <div class="rounded-lg bg-gray-800 border border-gray-700">
51 <div class="px-4 py-3 border-b border-gray-700">
52 <h3 class="text-sm font-medium text-gray-300">Recent Activity</h3>
53 </div>
54 <div class="divide-y divide-gray-700">
55 {% for item in recent_across_all %}
56 <div class="px-4 py-3 flex items-start gap-3">
57 <div class="flex-shrink-0 mt-1">
58 <div class="w-2.5 h-2.5 rounded-full bg-brand"></div>
59 </div>
60 <div class="flex-1 min-w-0">
61 <a href="{% url 'fossil:checkin_detail' slug=item.project.slug checkin_uuid=item.entry.uuid %}"
62 class="text-sm text-gray-200 hover:text-brand-light">{{ item.entry.comment|truncatechars:70 }}</a>
63 <div class="mt-0.5 flex items-center gap-3 text-xs text-gray-500">
64 <a href="{% url 'projects:detail' slug=item.project.slug %}" class="text-brand-light hover:text-brand">{{ item.project.name }}</a>
65 <a href="{% url 'fossil:user_activity' slug=item.project.slug username=item.entry.user %}" class="hover:text-gray-300">{{ item.entry.user }}</a>
66 <a href="{% url 'fossil:checkin_detail' slug=item.project.slug checkin_uuid=item.entry.uuid %}" class="font-mono text-brand-light hover:text-brand">{{ item.entry.uuid|truncatechars:10 }}</a>
67 <span>{{ item.entry.timestamp|timesince }} ago</span>
68 </div>
69 </div>
70 </div>
71 {% endfor %}
72 </div>
73 </div>
74 {% endif %}
75 </div>
76
77 <!-- Sidebar -->
78 <div class="space-y-4">
79 <!-- Quick links -->
80 {% if perms.projects.view_project %}
81 <a href="{% url 'projects:list' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors">
82 <h3 class="text-sm font-semibold text-gray-100">Projects</h3>
83 <p class="mt-1 text-xs text-gray-500">Manage projects and team access</p>
84 </a>
85 {% endif %}
86 {% if perms.organization.view_team %}
87 <a href="{% url 'organization:team_list' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors">
88 <h3 class="text-sm font-semibold text-gray-100">Teams</h3>
89 <p class="mt-1 text-xs text-gray-500">Organize members into teams</p>
90 </a>
91 {% endif %}
92 {% if perms.pages.view_page %}
93 <a href="{% url 'pages:list' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors">
94 <h3 class="text-sm font-semibold text-gray-100">Knowledge Base</h3>
95 <p class="mt-1 text-xs text-gray-500">Guides, runbooks, documentation</p>
96 </a>
97 {% endif %}
98 {% if perms.organization.view_organization %}
99 <a href="{% url 'organization:settings' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors">
100 <h3 class="text-sm font-semibold text-gray-100">Settings</h3>
101 <p class="mt-1 text-xs text-gray-500">Organization configuration</p>
102 </a>
103 {% endif %}
104 {% if user.is_staff %}
105 <a href="{% url 'admin:index' %}" class="block rounded-lg bg-gray-800 border border-gray-700 p-4 hover:border-brand transition-colors">
106 <h3 class="text-sm font-semibold text-gray-100">Admin</h3>
107 <p class="mt-1 text-xs text-gray-500">Users, groups, permissions</p>
108 </a>
109 {% endif %}
110 </div>
111 </div>
112
113 {% if system_activity_json and system_activity_json != "[]" %}
114 <script>
115 new Chart(document.getElementById('systemChart').getContext('2d'), {
116 type: 'bar',
117 data: {
118 labels: {{ system_activity_json|safe }}.map((_, i) => ''),
119 datasets: [{
120 data: {{ system_activity_json|safe }},
121 backgroundColor: '#DC394C',
122 borderRadius: 2,
123 barPercentage: 0.8,
124 categoryPercentage: 0.9,
125 }]
126 },
127 options: {
128 responsive: true,
129 maintainAspectRatio: false,
130 plugins: { legend: { display: false }, tooltip: {
131 callbacks: { title: (items) => { const w = 25 - items[0].dataIndex; return w === 0 ? 'This week' : w + ' week' + (w > 1 ? 's' : '') + ' ago'; } }
132 }},
133 scales: {
134 x: { display: false, grid: { display: false } },
135 y: { display: false, grid: { display: false }, beginAtZero: true }
136 }
137 }
138 });
139 </script>
140 {% endif %}
141 {% endblock %}
142
143 DDED templates/fossil/ticket_form.html
--- a/templates/fossil/ticket_form.html
+++ b/templates/fossil/ticket_form.html
@@ -0,0 +1,28 @@
1
+{% extends "base.html" %}
2
+{% block title %}{{ title }} — {{ project.name }} — Fossilrepo{% endblock %}
3
+
4
+{% block content %}
5
+<h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1>
6
+{% include "fossil/_project_nav.html" %}
7
+
8
+<div class="mb-4">
9
+ <a href="{% url 'fossil:tickets' slug=project.slug %}" class="text-sm text-brand-light hover:text-brand">&larr; Back to Tickets</a>
10
+</div>
11
+
12
+<div class="mx-auto max-w-2xl">
13
+ <h2 class="text-xl font-bold text-gray-100 mb-4">{{ title }}</h2>
14
+
15
+ <form method="post" class="space-y-4 rounded-lg bg-gray-800 p-6 shadow border border-gray-700">
16
+ {% csrf_token %}
17
+
18
+ <div>
19
+ <label class="block text-s..."
20
+ class="w-full rounded-md border-gray-600 bg-gray-900 text-gray-100 shadow-inner focus:border-brand focus:ring-1 focus:ring-brand sm:text-sm transition-colors"></textarea>
21
+ </div>
22
+
23
+ {% if custom_fields %}
24
+ <div class="border-t border-gray-700 pt-4 mt-4">
25
+ <h3 class="text-sm font-semibold text-gray-300 mb-3">Custom Fields</h3>
26
+ <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
27
+ {% for cf in custom_fields %}
28
+
--- a/templates/fossil/ticket_form.html
+++ b/templates/fossil/ticket_form.html
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/templates/fossil/ticket_form.html
+++ b/templates/fossil/ticket_form.html
@@ -0,0 +1,28 @@
1 {% extends "base.html" %}
2 {% block title %}{{ title }} — {{ project.name }} — Fossilrepo{% endblock %}
3
4 {% block content %}
5 <h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1>
6 {% include "fossil/_project_nav.html" %}
7
8 <div class="mb-4">
9 <a href="{% url 'fossil:tickets' slug=project.slug %}" class="text-sm text-brand-light hover:text-brand">&larr; Back to Tickets</a>
10 </div>
11
12 <div class="mx-auto max-w-2xl">
13 <h2 class="text-xl font-bold text-gray-100 mb-4">{{ title }}</h2>
14
15 <form method="post" class="space-y-4 rounded-lg bg-gray-800 p-6 shadow border border-gray-700">
16 {% csrf_token %}
17
18 <div>
19 <label class="block text-s..."
20 class="w-full rounded-md border-gray-600 bg-gray-900 text-gray-100 shadow-inner focus:border-brand focus:ring-1 focus:ring-brand sm:text-sm transition-colors"></textarea>
21 </div>
22
23 {% if custom_fields %}
24 <div class="border-t border-gray-700 pt-4 mt-4">
25 <h3 class="text-sm font-semibold text-gray-300 mb-3">Custom Fields</h3>
26 <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
27 {% for cf in custom_fields %}
28
--- templates/fossil/ticket_list.html
+++ templates/fossil/ticket_list.html
@@ -15,11 +15,14 @@
1515
<a href="{% url 'fossil:tickets' slug=project.slug %}?status=Fixed"
1616
class="rounded-full px-2.5 py-1 {% if status_filter == 'Fixed' %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">Fixed</a>
1717
<a href="{% url 'fossil:tickets' slug=project.slug %}?status=Closed"
1818
class="rounded-full px-2.5 py-1 {% if status_filter == 'Closed' %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">Closed</a>
1919
</div>
20
- <div>
20
+ <div class="flex items-center gap-3">
21
+ {% if perms.projects.change_project %}
22
+ <a href="{% url 'fossil:ticket_create' slug=project.slug %}" class="inline-flex items-center rounded-md bg-brand px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-brand-hover">New Ticket</a>
23
+ {% endif %}
2124
<input type="search"
2225
name="search"
2326
value="{{ search }}"
2427
placeholder="Search tickets..."
2528
class="rounded-md border-gray-700 bg-gray-800 text-gray-100 shadow-sm focus:border-brand focus:ring-brand text-sm px-3 py-1.5"
2629
2730
ADDED templates/fossil/wiki_form.html
--- templates/fossil/ticket_list.html
+++ templates/fossil/ticket_list.html
@@ -15,11 +15,14 @@
15 <a href="{% url 'fossil:tickets' slug=project.slug %}?status=Fixed"
16 class="rounded-full px-2.5 py-1 {% if status_filter == 'Fixed' %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">Fixed</a>
17 <a href="{% url 'fossil:tickets' slug=project.slug %}?status=Closed"
18 class="rounded-full px-2.5 py-1 {% if status_filter == 'Closed' %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">Closed</a>
19 </div>
20 <div>
 
 
 
21 <input type="search"
22 name="search"
23 value="{{ search }}"
24 placeholder="Search tickets..."
25 class="rounded-md border-gray-700 bg-gray-800 text-gray-100 shadow-sm focus:border-brand focus:ring-brand text-sm px-3 py-1.5"
26
27 DDED templates/fossil/wiki_form.html
--- templates/fossil/ticket_list.html
+++ templates/fossil/ticket_list.html
@@ -15,11 +15,14 @@
15 <a href="{% url 'fossil:tickets' slug=project.slug %}?status=Fixed"
16 class="rounded-full px-2.5 py-1 {% if status_filter == 'Fixed' %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">Fixed</a>
17 <a href="{% url 'fossil:tickets' slug=project.slug %}?status=Closed"
18 class="rounded-full px-2.5 py-1 {% if status_filter == 'Closed' %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">Closed</a>
19 </div>
20 <div class="flex items-center gap-3">
21 {% if perms.projects.change_project %}
22 <a href="{% url 'fossil:ticket_create' slug=project.slug %}" class="inline-flex items-center rounded-md bg-brand px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-brand-hover">New Ticket</a>
23 {% endif %}
24 <input type="search"
25 name="search"
26 value="{{ search }}"
27 placeholder="Search tickets..."
28 class="rounded-md border-gray-700 bg-gray-800 text-gray-100 shadow-sm focus:border-brand focus:ring-brand text-sm px-3 py-1.5"
29
30 DDED templates/fossil/wiki_form.html
--- a/templates/fossil/wiki_form.html
+++ b/templates/fossil/wiki_form.html
@@ -0,0 +1,17 @@
1
+{% extends "base.html" %}
2
+{% block title %}{{ title }} — {{ project.name }} — Fossilre>
3
+{% endblock %}
4
+
5
+{% block content %}
6
+<h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1>
7
+{% include "fossil/_project_nav.html" %}
8
+
9
+<div class="mb-4">
10
+ <a href="{% url 'fossil:wiki' slug=project.slug %}" class="text-sm text-brand-light hover:text-brand">&lar4xl">
11
+r justify-between mb-4">{{ title }}</h2>
12
+t-xs">
13
+ <button @click="tab = 'write'" :class="tab === 'write' ? 'bg-brand text-white' : 'text-gray-500 hover:text-white'" class="px-3 py-1 rounded">Write</button>
14
+ <button @click="tab = 'preview'; document.getElementById('preview-pane').innerHTML = rHTML = DOMPurify.sanitize(marked" :class ? 'bg-brand text-white' : 'text-gray-500 hover:text-white'" class="px-3 py-1 rounded">Preview>Preview</button>
15
+ </ddiv>
16
+
17
+ <form method8
--- a/templates/fossil/wiki_form.html
+++ b/templates/fossil/wiki_form.html
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/templates/fossil/wiki_form.html
+++ b/templates/fossil/wiki_form.html
@@ -0,0 +1,17 @@
1 {% extends "base.html" %}
2 {% block title %}{{ title }} — {{ project.name }} — Fossilre>
3 {% endblock %}
4
5 {% block content %}
6 <h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1>
7 {% include "fossil/_project_nav.html" %}
8
9 <div class="mb-4">
10 <a href="{% url 'fossil:wiki' slug=project.slug %}" class="text-sm text-brand-light hover:text-brand">&lar4xl">
11 r justify-between mb-4">{{ title }}</h2>
12 t-xs">
13 <button @click="tab = 'write'" :class="tab === 'write' ? 'bg-brand text-white' : 'text-gray-500 hover:text-white'" class="px-3 py-1 rounded">Write</button>
14 <button @click="tab = 'preview'; document.getElementById('preview-pane').innerHTML = rHTML = DOMPurify.sanitize(marked" :class ? 'bg-brand text-white' : 'text-gray-500 hover:text-white'" class="px-3 py-1 rounded">Preview>Preview</button>
15 </ddiv>
16
17 <form method8
--- templates/fossil/wiki_list.html
+++ templates/fossil/wiki_list.html
@@ -2,10 +2,17 @@
22
{% block title %}Wiki — {{ project.name }} — Fossilrepo{% endblock %}
33
44
{% block content %}
55
<h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1>
66
{% include "fossil/_project_nav.html" %}
7
+
8
+<div class="flex items-center justify-between mb-4">
9
+ <div></div>
10
+ {% if perms.projects.change_project %}
11
+ <a href="{% url 'fossil:wiki_create' slug=project.slug %}" class="inline-flex items-center rounded-md bg-brand px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-brand-hover">New Page</a>
12
+ {% endif %}
13
+</div>
714
815
<div class="flex gap-6">
916
<!-- Main content: home page or page index -->
1017
<div class="flex-1 min-w-0">
1118
{% if home_page %}
1219
--- templates/fossil/wiki_list.html
+++ templates/fossil/wiki_list.html
@@ -2,10 +2,17 @@
2 {% block title %}Wiki — {{ project.name }} — Fossilrepo{% endblock %}
3
4 {% block content %}
5 <h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1>
6 {% include "fossil/_project_nav.html" %}
 
 
 
 
 
 
 
7
8 <div class="flex gap-6">
9 <!-- Main content: home page or page index -->
10 <div class="flex-1 min-w-0">
11 {% if home_page %}
12
--- templates/fossil/wiki_list.html
+++ templates/fossil/wiki_list.html
@@ -2,10 +2,17 @@
2 {% block title %}Wiki — {{ project.name }} — Fossilrepo{% endblock %}
3
4 {% block content %}
5 <h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1>
6 {% include "fossil/_project_nav.html" %}
7
8 <div class="flex items-center justify-between mb-4">
9 <div></div>
10 {% if perms.projects.change_project %}
11 <a href="{% url 'fossil:wiki_create' slug=project.slug %}" class="inline-flex items-center rounded-md bg-brand px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-brand-hover">New Page</a>
12 {% endif %}
13 </div>
14
15 <div class="flex gap-6">
16 <!-- Main content: home page or page index -->
17 <div class="flex-1 min-w-0">
18 {% if home_page %}
19
--- templates/fossil/wiki_page.html
+++ templates/fossil/wiki_page.html
@@ -9,11 +9,16 @@
99
<!-- Main wiki content -->
1010
<div class="flex-1 min-w-0">
1111
<div class="overflow-hidden rounded-lg bg-gray-800 shadow border border-gray-700">
1212
<div class="px-6 py-4 border-b border-gray-700 flex items-center justify-between">
1313
<h2 class="text-lg font-semibold text-gray-100">{{ page.name }}</h2>
14
- <span class="text-xs text-gray-500">{{ page.last_modified|timesince }} ago by {{ page.user }}</span>
14
+ <div class="flex items-center gap-3">
15
+ <span class="text-xs text-gray-500">{{ page.last_modified|timesince }} ago by {{ page.user }}</span>
16
+ {% if perms.projects.change_project %}
17
+ <a href="{% url 'fossil:wiki_edit' slug=project.slug page_name=page.name %}" class="rounded-md bg-gray-700 px-2 py-1 text-xs font-semibold text-gray-300 hover:bg-gray-600">Edit</a>
18
+ {% endif %}
19
+ </div>
1520
</div>
1621
<div class="px-6 py-6">
1722
<div class="prose prose-invert prose-gray max-w-none">
1823
{{ content_html }}
1924
</div>
2025
--- templates/fossil/wiki_page.html
+++ templates/fossil/wiki_page.html
@@ -9,11 +9,16 @@
9 <!-- Main wiki content -->
10 <div class="flex-1 min-w-0">
11 <div class="overflow-hidden rounded-lg bg-gray-800 shadow border border-gray-700">
12 <div class="px-6 py-4 border-b border-gray-700 flex items-center justify-between">
13 <h2 class="text-lg font-semibold text-gray-100">{{ page.name }}</h2>
14 <span class="text-xs text-gray-500">{{ page.last_modified|timesince }} ago by {{ page.user }}</span>
 
 
 
 
 
15 </div>
16 <div class="px-6 py-6">
17 <div class="prose prose-invert prose-gray max-w-none">
18 {{ content_html }}
19 </div>
20
--- templates/fossil/wiki_page.html
+++ templates/fossil/wiki_page.html
@@ -9,11 +9,16 @@
9 <!-- Main wiki content -->
10 <div class="flex-1 min-w-0">
11 <div class="overflow-hidden rounded-lg bg-gray-800 shadow border border-gray-700">
12 <div class="px-6 py-4 border-b border-gray-700 flex items-center justify-between">
13 <h2 class="text-lg font-semibold text-gray-100">{{ page.name }}</h2>
14 <div class="flex items-center gap-3">
15 <span class="text-xs text-gray-500">{{ page.last_modified|timesince }} ago by {{ page.user }}</span>
16 {% if perms.projects.change_project %}
17 <a href="{% url 'fossil:wiki_edit' slug=project.slug page_name=page.name %}" class="rounded-md bg-gray-700 px-2 py-1 text-xs font-semibold text-gray-300 hover:bg-gray-600">Edit</a>
18 {% endif %}
19 </div>
20 </div>
21 <div class="px-6 py-6">
22 <div class="prose prose-invert prose-gray max-w-none">
23 {{ content_html }}
24 </div>
25

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button