what2do/app/templates/source_form.html

28 lines
6.8 KiB
HTML

{% extends "base.html" %}
{% block title %}Configure source · What2Do{% endblock %}
{% block content %}
<div class="section-head"><div><div class="eyebrow">SOURCE CONFIGURATION</div><div class="form-title">Configure website</div><div class="muted">Auto-detect standard HTML or configure a reusable extraction strategy.</div></div><a href="/sources">← Sources</a></div>
{% if detect_message %}<div class="notice success">{{ detect_message }} Review the strategy below before saving.</div>{% endif %}
{% if preview_error %}<div class="notice error">Detection or preview failed: {{ preview_error }}</div>{% endif %}
<form class="config-grid" method="post" action="{{ action }}">
<section class="card form-section"><div class="form-heading">Website and venue</div>
<div class="field">Source name<input required name="name" value="{{ source['name'] or '' }}"></div>
<div class="field">Page URL<div class="url-detect-row"><input required type="url" name="url" value="{{ source['url'] or '' }}" placeholder="https://venue.example/events"><input class="detect-submit" type="submit" value="Auto-detect HTML" formaction="/auto-detect" formnovalidate></div></div>
<div class="field">Venue name<input required name="venue_name" value="{{ source['venue_name'] or '' }}"></div>
<div class="field address-field">Venue address<input required id="venue-address" name="venue_address" value="{{ source['venue_address'] or '' }}" autocomplete="off"><div id="address-options" class="location-options" hidden></div></div>
<div class="two"><div class="field">Latitude<input id="venue-latitude" type="number" step="any" name="latitude" value="{{ source['latitude'] if source['latitude'] is not none else '' }}"></div><div class="field">Longitude<input id="venue-longitude" type="number" step="any" name="longitude" value="{{ source['longitude'] if source['longitude'] is not none else '' }}"></div></div>
<div class="field">Timezone<input required name="timezone" value="{{ source['timezone'] or 'Australia/Sydney' }}"></div></section>
<section class="card picker-panel"><div class="form-heading">Visual selector picker</div><div class="hint">Loads a sanitized, non-interactive copy of the downloaded HTML. Dynamic JavaScript content may not appear.</div><div class="picker-controls"><button type="button" id="load-picker">Load visual picker</button><button type="button" id="reset-picker" class="secondary" hidden>Start</button> over</button></div><ol id="picker-steps" class="picker-steps" hidden><li data-step="item_selector">Pick one event container</li><li data-step="title_selector">Pick the title inside it</li><li data-step="date_selector">Pick the date or range</li><li data-step="time_selector">Pick the time <span>(optional)</span></li></ol><div id="picker-status" class="hint"></div><div id="picker-workspace" class="picker-workspace" hidden><iframe id="selector-picker" class="selector-picker" sandbox="allow-scripts" title="Sanitized website selector picker"></iframe><aside class="picker-preview"><div class="picker-preview-head"><strong></strong>Live extraction preview</strong><span id="picker-container-count"></span></div><div id="picker-warning" class="notice error" hidden></div><div id="picker-preview-empty" class="hint">Choose a container to begin.</div><div id="picker-preview-rows" class="picker-preview-rows"></div></aside></div><div id="picker-selection" class="picker-selection" hidden><div><strong>Selected</strong><code id="picked-selector"></code><span id="picked-count"></span></div><div><strong>Pattern</strong><code id="picked-container"></code><span id="picked-relative"></span></div><span id="picked-text"></span><div class="picker-actions"><button type="button" id="apply-selector">Use this selection</button></div></div></section>
<details class="card rules-details" {% if detect_message or preview_error %}open{% endif %}><summary>Extraction strategy — advanced</summary><section class="form-section">
<div class="hint">HTML mode uses the convenient selector fields below. JSON and HTML-fragment modes use the generic JSON strategy directly.</div>
<div class="two"><div class="field">Event container selector<input name="item_selector" value="{{ source['item_selector'] or '' }}"></div><div class="field">Title selector<input name="title_selector" value="{{ source['title_selector'] or '' }}"></div><div class="field">Date or range selector<input name="date_selector" value="{{ source['date_selector'] or '' }}"></div><div class="field">Time selector<input name="time_selector" value="{{ source['time_selector'] or '' }}"></div><div class="field">Link selector<input name="link_selector" value="{{ source['link_selector'] or '' }}"></div><div class="field">Description selector<input name="description_selector" value="{{ source['description_selector'] or '' }}"></div><div class="field">Location selector<input name="location_selector" value="{{ source['location_selector'] or '' }}"></div><div class="field">Date format<input name="date_format" value="{{ source['date_format'] or '' }}"></div></div>
<label class="check"><input type="checkbox" name="recurring_weekly" {% if source['recurring_weekly'] %}checked{% endif %}> Generate weekly occurrences from weekday labels</label>
<div class="field">Strategy template<select id="strategy-template"><option value="">Keep current strategy</option><option value="html">HTML page</option><option value="json">JSON API</option><option value="json_join">JSON API with metadata join</option><option value="html_fragment">API returning HTML fragments</option></select></div><div class="field">Persisted strategy JSON<textarea id="strategy-json" name="strategy_json" rows="18">{{ source['strategy_json'] or '{}' }}</textarea><div class="hint">Supports request configuration, dotted JSON paths, CSS selectors and attributes, pagination, joins, and response limits. See STRATEGIES.md.</div></div>
</section></details>
<section class="card form-section"><div class="form-heading">Scheduling</div><div class="field">Crawl every (minutes)<input type="number" min="15" name="crawl_interval_minutes" value="{{ source['crawl_interval_minutes'] or 360 }}"></div><label class="check"><input type="checkbox" name="enabled" {% if source['enabled'] %}checked{% endif %}> Enable scheduled crawling</label></section>
<div class="sticky-actions">{% if source.get('id') %}<input type="hidden" name="source_id" value="{{ source['id'] }}">{% endif %}<input type="submit" value="Test strategy" formaction="/preview"><input type="submit" class="primary" value="Save source"></div></form>
{% if preview is not none %}<section class="card preview"><div class="form-heading">Preview ({{ preview|length }})</div>{% for event in preview %}<div><strong>{{ event.title }}</strong><span>{{ event.starts_at }}{% if event.ends_at %} → {{ event.ends_at }}{% endif %}</span></div>{% else %}<p>No events matched this strategy.</p>{% endfor %}</section>{% endif %}
{% endblock %}
{% block scripts %}<script src="/static/source.js"></script>{% endblock %}