edit sources
This commit is contained in:
parent
847aeae772
commit
328a70ff9b
7 changed files with 512 additions and 38 deletions
|
|
@ -41,6 +41,19 @@ PANGEA_CONTENT_TYPES = (
|
|||
)
|
||||
|
||||
|
||||
def _value(source: Mapping[str, object] | None, key: str, default: str = "") -> str:
|
||||
if source is None:
|
||||
return default
|
||||
return str(source.get(key, default))
|
||||
|
||||
|
||||
def _checked(source: Mapping[str, object] | None, key: str, default: bool) -> bool:
|
||||
if source is None:
|
||||
return default
|
||||
value = source.get(key, default)
|
||||
return bool(value)
|
||||
|
||||
|
||||
def _source_row(source: Mapping[str, object]) -> tuple[Node, ...]:
|
||||
return (
|
||||
h.div[
|
||||
|
|
@ -64,7 +77,9 @@ def _source_row(source: Mapping[str, object]) -> tuple[Node, ...]:
|
|||
h.p(class_="mt-2 text-xs text-slate-500")[str(source["last_run"])],
|
||||
],
|
||||
h.div(class_="flex flex-nowrap items-center gap-3")[
|
||||
inline_link(href="/sources/create", label="Edit", tone="amber"),
|
||||
inline_link(
|
||||
href=f"/sources/{source['slug']}/edit", label="Edit", tone="amber"
|
||||
),
|
||||
inline_link(href="/runs", label="View runs"),
|
||||
],
|
||||
)
|
||||
|
|
@ -97,7 +112,27 @@ def sources_page(
|
|||
)
|
||||
|
||||
|
||||
def create_source_form(*, action_path: str = "/actions/sources/create") -> Renderable:
|
||||
def source_form(
|
||||
*,
|
||||
mode: str,
|
||||
action_path: str,
|
||||
source: Mapping[str, object] | None = None,
|
||||
) -> Renderable:
|
||||
source_type = _value(source, "source_type", "pangea")
|
||||
slug = _value(source, "slug")
|
||||
title = "Source and job setup" if mode == "create" else "Edit source"
|
||||
eyebrow = "Create" if mode == "create" else "Edit"
|
||||
description = (
|
||||
"Create the source and its paired job record."
|
||||
if mode == "create"
|
||||
else "Update the existing source and its paired job record."
|
||||
)
|
||||
status_label = "New source" if mode == "create" else "Existing source"
|
||||
submit_label = "Create source" if mode == "create" else "Save changes"
|
||||
initial_signals = "{sourceType: 'pangea'}"
|
||||
if mode == "edit":
|
||||
initial_signals = f"{{sourceType: '{source_type}', sourceSlug: '{slug}'}}"
|
||||
|
||||
return section_card(
|
||||
content=(
|
||||
h.div(
|
||||
|
|
@ -106,20 +141,16 @@ def create_source_form(*, action_path: str = "/actions/sources/create") -> Rende
|
|||
h.div[
|
||||
h.p(
|
||||
class_="text-xs font-semibold uppercase tracking-[0.22em] text-amber-600"
|
||||
)["Create"],
|
||||
h.h2(class_="mt-2 text-xl font-semibold text-slate-950")[
|
||||
"Source and job setup"
|
||||
],
|
||||
h.p(class_="mt-2 max-w-3xl text-sm text-slate-600")[
|
||||
"The create flow lives on its own page and creates the source plus its paired job record. This pass is visual only, but the fields already reflect the intended shape."
|
||||
],
|
||||
)[eyebrow],
|
||||
h.h2(class_="mt-2 text-xl font-semibold text-slate-950")[title],
|
||||
h.p(class_="mt-2 max-w-3xl text-sm text-slate-600")[description],
|
||||
],
|
||||
status_badge(label="New source", tone="scheduled"),
|
||||
status_badge(label=status_label, tone="scheduled"),
|
||||
],
|
||||
h.form(
|
||||
{
|
||||
"data-signals": "{_formError: '', _formSuccess: ''}",
|
||||
"data-signals__ifmissing": "{sourceType: 'pangea'}",
|
||||
"data-signals__ifmissing": initial_signals,
|
||||
"data-on:submit": f"@post('{action_path}')",
|
||||
},
|
||||
class_="mt-5 space-y-6",
|
||||
|
|
@ -142,13 +173,16 @@ def create_source_form(*, action_path: str = "/actions/sources/create") -> Rende
|
|||
input_field(
|
||||
label="Source name",
|
||||
field_id="source-name",
|
||||
value=_value(source, "name"),
|
||||
signal_name="sourceName",
|
||||
),
|
||||
input_field(
|
||||
label="Slug",
|
||||
field_id="source-slug",
|
||||
value=slug,
|
||||
help_text="Immutable after creation.",
|
||||
signal_name="sourceSlug",
|
||||
disabled=mode == "edit",
|
||||
),
|
||||
h.div[
|
||||
h.label(
|
||||
|
|
@ -161,8 +195,12 @@ def create_source_form(*, action_path: str = "/actions/sources/create") -> Rende
|
|||
name="source-type",
|
||||
class_="mt-2 block w-full rounded-2xl border-0 bg-white px-3.5 py-2.5 text-sm text-slate-900 shadow-sm ring-1 ring-slate-200 focus:outline-hidden focus:ring-2 focus:ring-amber-500",
|
||||
)[
|
||||
h.option(value="feed")["feed"],
|
||||
h.option(value="pangea", selected=True)["pangea"],
|
||||
h.option(value="feed", selected=source_type == "feed")[
|
||||
"feed"
|
||||
],
|
||||
h.option(value="pangea", selected=source_type == "pangea")[
|
||||
"pangea"
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
|
@ -185,6 +223,7 @@ def create_source_form(*, action_path: str = "/actions/sources/create") -> Rende
|
|||
input_field(
|
||||
label="Feed URL",
|
||||
field_id="feed-url",
|
||||
value=_value(source, "feed_url"),
|
||||
placeholder="https://example.com/feed.xml",
|
||||
signal_name="feedUrl",
|
||||
),
|
||||
|
|
@ -209,37 +248,39 @@ def create_source_form(*, action_path: str = "/actions/sources/create") -> Rende
|
|||
input_field(
|
||||
label="Pangea domain",
|
||||
field_id="pangea-domain",
|
||||
value=_value(source, "pangea_domain"),
|
||||
signal_name="pangeaDomain",
|
||||
),
|
||||
input_field(
|
||||
label="Category name",
|
||||
field_id="pangea-category",
|
||||
value=_value(source, "pangea_category"),
|
||||
signal_name="pangeaCategory",
|
||||
),
|
||||
select_field(
|
||||
label="Content format",
|
||||
field_id="content-format",
|
||||
options=PANGEA_CONTENT_FORMATS,
|
||||
selected="MOBILE_3",
|
||||
selected=_value(source, "content_format", "MOBILE_3"),
|
||||
signal_name="contentFormat",
|
||||
),
|
||||
select_field(
|
||||
label="Content type",
|
||||
field_id="content-type",
|
||||
options=PANGEA_CONTENT_TYPES,
|
||||
selected="articles",
|
||||
selected=_value(source, "content_type", "articles"),
|
||||
signal_name="contentType",
|
||||
),
|
||||
input_field(
|
||||
label="Max articles",
|
||||
field_id="max-articles",
|
||||
value="10",
|
||||
value=_value(source, "max_articles", "10"),
|
||||
signal_name="maxArticles",
|
||||
),
|
||||
input_field(
|
||||
label="Oldest article (days)",
|
||||
field_id="oldest-article",
|
||||
value="3",
|
||||
value=_value(source, "oldest_article", "3"),
|
||||
signal_name="oldestArticle",
|
||||
),
|
||||
],
|
||||
|
|
@ -248,25 +289,25 @@ def create_source_form(*, action_path: str = "/actions/sources/create") -> Rende
|
|||
label="Only newest",
|
||||
description="Limit Pangea syncs to the newest material available in the selected category.",
|
||||
signal_name="onlyNewest",
|
||||
checked=True,
|
||||
checked=_checked(source, "only_newest", True),
|
||||
),
|
||||
toggle_field(
|
||||
label="Include authors",
|
||||
description="Carry author bylines into mirrored output where upstream data exists.",
|
||||
signal_name="includeAuthors",
|
||||
checked=True,
|
||||
checked=_checked(source, "include_authors", True),
|
||||
),
|
||||
toggle_field(
|
||||
label="Exclude media",
|
||||
description="Skip image and media attachment mirroring for this source.",
|
||||
signal_name="excludeMedia",
|
||||
checked=False,
|
||||
checked=_checked(source, "exclude_media", False),
|
||||
),
|
||||
toggle_field(
|
||||
label="Include content",
|
||||
description="Store article body content in mirrored output when the upstream provides it.",
|
||||
signal_name="includeContent",
|
||||
checked=True,
|
||||
checked=_checked(source, "include_content", True),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
|
@ -274,13 +315,17 @@ def create_source_form(*, action_path: str = "/actions/sources/create") -> Rende
|
|||
textarea_field(
|
||||
label="Notes",
|
||||
field_id="source-notes",
|
||||
value="",
|
||||
value=_value(source, "notes"),
|
||||
signal_name="sourceNotes",
|
||||
),
|
||||
textarea_field(
|
||||
label="Spider arguments",
|
||||
field_id="spider-arguments",
|
||||
value="language=en\ndownload_media=true",
|
||||
value=_value(
|
||||
source,
|
||||
"spider_arguments",
|
||||
"language=en\ndownload_media=true",
|
||||
),
|
||||
signal_name="spiderArguments",
|
||||
),
|
||||
],
|
||||
|
|
@ -300,31 +345,31 @@ def create_source_form(*, action_path: str = "/actions/sources/create") -> Rende
|
|||
input_field(
|
||||
label="Minute",
|
||||
field_id="cron-minute",
|
||||
value="*/30",
|
||||
value=_value(source, "cron_minute", "*/30"),
|
||||
signal_name="cronMinute",
|
||||
),
|
||||
input_field(
|
||||
label="Hour",
|
||||
field_id="cron-hour",
|
||||
value="*",
|
||||
value=_value(source, "cron_hour", "*"),
|
||||
signal_name="cronHour",
|
||||
),
|
||||
input_field(
|
||||
label="Day of month",
|
||||
field_id="cron-day-of-month",
|
||||
value="*",
|
||||
value=_value(source, "cron_day_of_month", "*"),
|
||||
signal_name="cronDayOfMonth",
|
||||
),
|
||||
input_field(
|
||||
label="Day of week",
|
||||
field_id="cron-day-of-week",
|
||||
value="*",
|
||||
value=_value(source, "cron_day_of_week", "*"),
|
||||
signal_name="cronDayOfWeek",
|
||||
),
|
||||
input_field(
|
||||
label="Month",
|
||||
field_id="cron-month",
|
||||
value="*",
|
||||
value=_value(source, "cron_month", "*"),
|
||||
signal_name="cronMonth",
|
||||
),
|
||||
],
|
||||
|
|
@ -341,7 +386,7 @@ def create_source_form(*, action_path: str = "/actions/sources/create") -> Rende
|
|||
label="Job enabled",
|
||||
description="Scheduler will consider the new job immediately after creation.",
|
||||
signal_name="jobEnabled",
|
||||
checked=True,
|
||||
checked=_checked(source, "enabled", True),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
|
@ -353,7 +398,7 @@ def create_source_form(*, action_path: str = "/actions/sources/create") -> Rende
|
|||
h.button(
|
||||
type="submit",
|
||||
class_="rounded-full bg-slate-950 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-slate-800",
|
||||
)["Create source"],
|
||||
)[submit_label],
|
||||
],
|
||||
],
|
||||
)
|
||||
|
|
@ -369,7 +414,27 @@ def create_source_page(*, action_path: str = "/actions/sources/create") -> Rende
|
|||
current_path="/sources/create",
|
||||
eyebrow="Source creation",
|
||||
title="Create source",
|
||||
description="Dedicated create page for the source form. The list page stays focused on scanning existing sources, while this page handles the new source and job configuration flow.",
|
||||
description="Create a new source and its paired job configuration.",
|
||||
actions=actions,
|
||||
content=create_source_form(action_path=action_path),
|
||||
content=source_form(mode="create", action_path=action_path),
|
||||
)
|
||||
|
||||
|
||||
def edit_source_page(
|
||||
*,
|
||||
slug: str,
|
||||
source: Mapping[str, object],
|
||||
action_path: str,
|
||||
) -> Renderable:
|
||||
actions = (
|
||||
muted_action_link(href="/sources", label="Back to sources"),
|
||||
header_action_link(href="/runs", label="View runs"),
|
||||
)
|
||||
return page_shell(
|
||||
current_path=f"/sources/{slug}/edit",
|
||||
eyebrow="Source editing",
|
||||
title="Edit source",
|
||||
description="Update an existing source and its paired job configuration.",
|
||||
actions=actions,
|
||||
content=source_form(mode="edit", action_path=action_path, source=source),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue