diff --git a/app/static/app.js b/app/static/app.js new file mode 100644 index 0000000..eedf6cd --- /dev/null +++ b/app/static/app.js @@ -0,0 +1,54 @@ +// ─── Sidebar ─── +function initSidebar() { + document.querySelectorAll('.ni[data-pg]').forEach(el => { + el.addEventListener('click', function(e) { + e.preventDefault(); + window.location.href = this.getAttribute('href'); + }); + }); + document.querySelectorAll('.nst').forEach(el => { + el.addEventListener('click', function() { this.classList.toggle('on'); }); + }); +} +initSidebar(); + +// ─── Global Time Control ─── +function openTimeModal() { + const display = document.getElementById('sim-time-display'); + if (!display) return; + const modal = document.getElementById('time-modal'); + const input = document.getElementById('time-input'); + if (modal && input) { + input.value = display.textContent.trim().replace(' ', 'T'); + modal.classList.add('open'); + } +} +function closeTimeModal() { + const modal = document.getElementById('time-modal'); + if (modal) modal.classList.remove('open'); +} +async function applyTime() { + const input = document.getElementById('time-input'); + if (!input || !input.value) return; + await setSimTimeAndReload(input.value.replace('T', ' ') + ':00'); +} +async function shiftTime(days) { + const display = document.getElementById('sim-time-display'); + if (!display) return; + const d = new Date(display.textContent.trim().replace(' ', 'T') + 'Z'); + d.setUTCDate(d.getUTCDate() + days); + await setSimTimeAndReload(d.toISOString().slice(0, 10) + ' 09:00'); +} +async function resetTime() { + await setSimTimeAndReload('2060-03-12 09:00'); +} +async function setSimTimeAndReload(timeStr) { + try { + await fetch('/api/v1/sim-time', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({sim_time: timeStr}) + }); + } catch(e) {} + location.reload(); +} diff --git a/app/templates/asset_detail.html b/app/templates/asset_detail.html index 5e11d91..82ec843 100644 --- a/app/templates/asset_detail.html +++ b/app/templates/asset_detail.html @@ -1,231 +1,432 @@ {% extends "base.html" %} -{% from "_datetime_picker_field.html" import datetime_picker_field %} {% block content %} -{% include "_mission_ops_tabs.html" %} -{% include "_mission_preview_header.html" %} +
+
+
Mission Asset
+

{{ asset.name }}

+

{{ asset.asset_type }} · {{ 'Retired' if asset.is_retired else 'Active' }}

+
+
+ Edit Asset + ← Back +
+
-
-
-
-
-
-

Asset Summary

-

{{ asset.name }}

-

{{ asset.asset_type }} · {{ asset.program or 'No program assigned' }}

-
-
- {{ asset_summary.current_state }} - {{ asset_summary.current_location }} - {% if asset.home_region %} - {{ asset.home_region }} - {% endif %} -
-
+
+
+ +
+
Type
{{ asset.asset_type }}
+
Location
{{ asset_summary.current_location or '—' }}
+
Mission
{{ asset_summary.current_mission or '—' }}
+
Entries
{{ total_entry_count }}
+
-
-
-
Current Mission
-
{{ asset_summary.current_mission }}
-
-
-
Active Window
-
{{ asset_summary.state_window }}
-
-
-
Next Planned Event
-
{{ asset_summary.next_event }}
-
-
-
- -
-
-
-

未来事件

-

可编辑的未来记录

-

这里汇总了未来的 Event Point、State Interval,以及当前区间里尚未发生的 State Node。

-
-
{{ future_events|length }} future items
-
- - {% if future_events %} -
- {% for item in future_events %} -
-
-
-

{{ item.when }}

-

{{ item.title }}

-

{{ item.kind_label }} · {{ item.status_label }}

-
- {{ item.action_label }} -
- -

{{ item.scope }}

-

{{ item.summary }}

- {% if item.edit_hint %} -

{{ item.edit_hint }}

- {% endif %} - - {% if item.state_labels or item.mission_labels %} - - {% endif %} -
- {% endfor %} -
- {% else %} -
-

没有未来记录

-

当前选定的 Simulation Time 之后还没有排定的事件。

-
- {% endif %} -
- -
-
-
-

全部日志

-

资产日志

-
-
{{ filtered_entry_count }} / {{ total_entry_count }} entries
-
- -
- - {{ datetime_picker_field('Start', 'start', entry_filter_start_input, field_id='asset-log-start') }} - {{ datetime_picker_field('End', 'end', entry_filter_end_input, field_id='asset-log-end') }} - - 重置 -
- - {% if entries %} -
- - - - - - - - - - - {% for entry in entries %} - - - - - - - {% endfor %} - -
TimeEntryLabelsAction
- {{ entry.timestamp }} - {% if entry.end_timestamp %} -
→ {{ entry.end_timestamp }}
- {% endif %} -
{{ entry.location }}
-
- {{ entry.title }} -
{{ entry.mode_label }} · {{ entry.status_label }}
-
{{ entry.summary }}
-
- {% if entry.state_labels or entry.mission_labels %} - - {% else %} - - - {% endif %} - - -
-
- - {% if entry_total_pages > 1 %} - - {% endif %} - {% else %} -
- {% if total_entry_count %} -

没有命中筛选条件的日志条目

-

调整起止时间,或者重置筛选后重试。

- {% else %} -

还没有日志条目

-

先为这个资产新增一个 Event Point 或 State Interval。

- {% endif %} -
- {% endif %} -
+ +
+
+
+ Docking Target + +
+
+ {% if asset_summary.docking_target %} +
+ + {{ asset_summary.docking_target.name }} + {{ asset_summary.docking_target.current_location }} + since {{ asset_summary.docking_target.docked_at }} + + × Remove +
+ {% else %} +
Not docked to any asset
+ {% endif %} +
+
+
+ Docked Vehicles + +
+
+ {% if asset_summary.docked_vehicles %} + {% for v in asset_summary.docked_vehicles %} +
+ + {% if v.id %} + {{ v.name }} + {% else %} + {{ v.name }} + {% endif %} + since {{ v.docked_at }} + + × Remove +
+ {% endfor %} + {% else %} +
No vehicles docked
+ {% endif %} +
+
+
-
- {% include "_mission_upcoming_events.html" %} +
+
Upcoming Events
+
+ {% for ev in future_events %} +
{{ ev.date }}{{ ev.title }}
+ {% endfor %} +
+
Quick Info
+
+
Retired: {{ 'Yes' if asset.is_retired else 'No' }}
+
Program: {{ asset.program or '—' }}
+
Home Region: {{ asset.home_region or '—' }}
+
+
+ -
-
-

操作

-

日志操作

-
- -
- -
-{% endblock %} \ No newline at end of file + + + + + + + + + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/app/templates/asset_entry_form.html b/app/templates/asset_entry_form.html index b239e02..75934d7 100644 --- a/app/templates/asset_entry_form.html +++ b/app/templates/asset_entry_form.html @@ -1,270 +1,426 @@ {% extends "base.html" %} -{% from "_datetime_picker_field.html" import datetime_picker_field %} {% block content %} -{% include "_mission_ops_tabs.html" %} {% set field_error_map = field_errors|default({}, true) %} -{% macro render_end_at_field(end_at_value, has_error=False) %} -{{ datetime_picker_field('End At', 'end_at', end_at_value, field_id='entry-end-at-input', wrapper_class='field-span-two mission-datetime-field', wrapper_id='entry-end-at-field', error=has_error) }} -{% endmacro %} -{% macro render_state_node_row(node, row_index, field_error_map) %} -{% set title_error = field_error_map.get('state_node_title:' ~ row_index) %} -{% set at_error = field_error_map.get('state_node_at:' ~ row_index) %} -
- + - +{% set is_state_entry = form_values.entry_kind == 'state' %} +{% set node_count = form_values.state_nodes|length %} +{% set all_locations = ['LEO','GEO/MEO','Lunar Orbit','Lunar Surface','Earth Surface','Mercury System','Mercury Orbit','Mercury Surface','Venus System','Venus Orbit','Venus Surface','Earth System','Mars System','Mars Orbit','Mars Surface','Phobos Orbit','Phobos Surface','Deimos Orbit','Deimos Surface','Ceres Orbit','Ceres Surface','Vesta Orbit','Vesta Surface','Jupiter System','Jupiter Orbit','Io Orbit','Io Surface','Europa Orbit','Europa Surface','Ganymede Orbit','Ganymede Surface','Callisto Orbit','Callisto Surface','Saturn System','Saturn Orbit','Mimas Orbit','Mimas Surface','Enceladus Orbit','Enceladus Surface','Tethys Orbit','Tethys Surface','Dione Orbit','Dione Surface','Rhea Orbit','Rhea Surface','Titan Orbit','Titan Surface','Iapetus Orbit','Iapetus Surface','Uranus System','Uranus Orbit','Miranda Orbit','Miranda Surface','Ariel Orbit','Ariel Surface','Umbriel Orbit','Umbriel Surface','Titania Orbit','Titania Surface','Oberon Orbit','Oberon Surface','Neptune System','Neptune Orbit','Triton Orbit','Triton Surface','Pluto System','Pluto Orbit','Pluto Surface','Charon Orbit','Charon Surface','Solar Orbit','Transfer'] %} +{% if not all_states %}{% set all_states = ['start-transit','transit-complete','Exploration','Maintenance','Docked','Construction','Testing','Autonomous Standby','Crew Handoff','Cargo Delivery','Surface Expedition','Planetary Operations','Base Construction','Outpost Construction','Base Expansion','Outpost Expansion'] %}{% endif %} - {{ datetime_picker_field('Node At', 'state_node_at', node.at if node else '', field_id='state-node-at-' ~ row_index, wrapper_class='state-node-datetime', error=at_error) }} - - - - -
-{% endmacro %} - -
-
-

资产日志

-

{{ heading }}

+
+
+
+ 任务资产 + / + {{ asset.name }} + / + {{ '编辑日志' if delete_url else '新增日志' }}
+

{{ form_values.title or heading }}

+

{{ asset.asset_type }}{% if asset.program %} · {{ asset.program }}{% endif %}

+
+
+
+ Entry Type + {{ 'State Interval' if is_state_entry else 'Event Point' }} +
+
+ Simulation Time + {{ simulation_time_input }} +
+
+ State Window + {{ form_values.start_at or '—' }}{% if form_values.end_at %} → {{ form_values.end_at }}{% endif %} +
+
+ State Nodes + {{ node_count }} node{{ '' if node_count == 1 else 's' }} +
+
-
- -
- {% set is_event_kind = form_values.entry_kind == 'event' %} -
- + + - + + +
+
+
+

State Nodes

+

状态节点时间轴

+

每个节点代表这段状态中的一个具体时刻。时间、事件和备注会一起保存。

+
+
+ 按 Node At 升序 + +
+
+ +
+ Start + Course + Watch + End + {{ node_count }} node{{ '' if node_count == 1 else 's' }} +
+ + {% set node_section_error = field_error_map.get('state_nodes') %} +
+ {% if not form_values.state_nodes %} +
+ 还没有 State Node + 添加节点后,这里会按时间显示“什么时间发生了什么”。 + +
+ {% endif %} + {% for node in form_values.state_nodes %} + {% set node_title_error = field_error_map.get('state_node_title:' ~ loop.index0) %} + {% set node_at_error = field_error_map.get('state_node_at:' ~ loop.index0) %} +
+
{{ node.at or '未设置时间' }}
+
+
+
+
+ {{ 'Node ' ~ loop.index }} +
+ {{ node.title or 'Untitled node' }} + {{ node.detail or 'No detail yet.' }} +
+ +
+
+ + + + + {% macro combobox_location(name, value, placeholder) %} +
    {% for loc in all_locations %}
  • {{ loc }}
  • {% endfor %}
+ {% endmacro %} + {% macro combobox_state(name, value) %} +
    {% for s in all_states %}
  • {{ s }}
  • {% endfor %}
+ {% endmacro %} + + + + +
+
+
+
+ {% endfor %} +
+
{% if delete_url %} -
-

删除后不可恢复,请谨慎操作。

-
- - -
-
+
+ +
{% endif %} - + -