{% extends "utilities/_base.html" %} {% from "components/chat_dialog.html" import chat_dialog %} {% from "components/page_header.html" import page_header %} {% from "components/modal.html" import render_modal with context %} {% from "utilities/_sign_in_fields.html" import api_key_field, server_address_field %} {# The service builder: a chat that writes a custom sync adapter, beside five tabs (plain links, so each tab is its own page load): Overview, Test account, Web access, Code, Test. Publish approves the endpoint list on Web access. #} {% block title %}{{ _("%(name)s - Services - Yaffo", name=row.name) }}{% endblock %} {% block utility_styles %} {% endblock %} {% set draft = service.draft %} {% set provider = service.provider %} {% set metadata = draft.metadata if draft else {} %} {% macro tab_link(key, label) %} {{ label }} {% endmacro %} {# An info hover, a section heading (with an optional hover and status chip), and a field label (with an optional hover): explanations live in hovers, not paragraphs. #} {% macro help_tip(text) %}{% endmacro %} {% macro section_heading(title, help=none, status=none) %}

{{ title }}

{% if help %}{{ help_tip(help) }}{% endif %} {% if status %}{{ status }}{% endif %}
{% endmacro %} {% macro field_label(for_id, text, help=none) %}
{% if help %}{{ help_tip(help) }}{% endif %}
{% endmacro %} {% macro render_publish_review() %}

{{ _("Publishing makes this draft the version syncs run. It may call only these:") }}

{% if service.used_by %}

{{ ngettext("%(num)d sync uses this service. If the new version can call more than before, it asks for your approval again before it runs.", "%(num)d syncs use this service. If the new version can call more than before, they ask for your approval again before they run.", service.used_by) }}

{% endif %} {% endmacro %} {% block utility_content %} {% set header_actions %} {% if service.regenerate %}
{% endif %} {% if service.can_publish %} {% endif %}
{% endset %} {% call page_header(row.name, actions=header_actions) %} {% if service.regenerate %}{{ _("Needs updating") }} {% elif row.published %}{{ _("Published") }} {% else %}{{ _("Draft") }}{% endif %} {{ _("A service Yaffo can import from or send to. Describe it in the chat; the builder writes and tests it, and you publish it.") }} {% endcall %} {% if service_error %} {% elif notice == 'published' %}
{{ _("Published. Choose it under Import from… or Send to… in New automation.") }}
{% elif notice == 'app_saved' %}
{{ _("App credentials saved.") }}
{% endif %} {% if service.regenerate %}
{{ _("This service needs updating: it doesn't pass the current version's checks, so syncs using it are paused. Regenerate asks the builder to fix it.") }}
{% endif %}
{{ chat_dialog('service-chat', _('Service builder'), _("Say which service it is and whether photos go to it or come from it."), _('Describe the service…'), messages=messages, empty_text=_('No conversation yet. For example: "Import photos from my Immich server" or "Send albums to Flickr as sets".')) }}
{% if service.problems %}
{{ _("The draft doesn't pass its check yet:") }}
    {% for problem in service.problems %}
  • {{ problem }}
  • {% endfor %}
{% endif %} {% if tab == 'overview' %} {% if draft %} {% set caps = metadata.capabilities or {} %}
{{ _("Name") }}
{{ metadata.title }}
{{ _("Direction") }}
{{ _("Imports photos") if draft.role == 'source' else _("Sends photos") }}
{{ _("Sign-in") }}
{{ {'oauth2': _("Service sign-in page"), 'oauth1a': _("Service sign-in page"), 'api_key': _("API key"), 'basic': _("Username and password")}.get(provider.auth_kind, provider.auth_kind) }}
{{ _("Hosts") }}
{% if provider.instance %}{{ _("Your server") }}{% else %}{{ provider.base_urls | map('replace', 'https://', '') | join(', ') }}{% endif %}
{% if draft.options %}
{{ _("Options") }}
{% for key, spec in draft.options.items() %}{{ spec.label }}{{ ", " if not loop.last }}{% endfor %}
{% endif %} {% if draft.role == 'target' %}
{{ _("Can") }}
{{ _("Add") }}{% if caps.remove %} · {{ _("Remove") }}{% endif %}{% if caps.update %} · {{ _("Captions") }}{% endif %}{% if caps.reconcile %} · {{ _("Recover") }}{% endif %}
{{ _("Batch size") }}
{{ caps.max_batch or 50 }}
{% endif %}
{{ _("Used by") }}
{{ ngettext("%(num)d sync", "%(num)d syncs", service.used_by) }}
{% else %}

{{ _("Nothing yet. The builder fills this in as it writes the service.") }}

{% endif %} {% elif tab == 'account' %} {% if not provider %}

{{ _("Appears once the builder knows how this service signs in.") }}

{% else %} {% if service.needs_app %} {{ section_heading(_("App credentials"), _("%(service)s needs to know which app is asking: register Yaffo with it once, and every sync using this service reuses it. Kept in your system's keychain, never in Yaffo's database, never sent to the AI model. Saving new ones signs every sync using this service out.", service=provider.label), _("Saved") if service.app_saved else none) }}
{% if service.return_address %}
{{ field_label('service-return-address', service.app_labels.redirect, _("Paste this into the service's app form.")) }}
{% endif %}
{{ field_label('service-client-id', service.app_labels.client_id) }}
{{ field_label('service-client-secret', service.app_labels.client_secret) }}
{% endif %} {{ section_heading(_("Test account"), _("The account Test reads from while you build. It's saved like any sign-in, so a sync set up with this service finds it already signed in.")) }} {% if service.test_account %} {% endif %} {% if live_reads %} {% endif %}
{% if provider.is_oauth %}
{% else %} {% if provider.instance %} {{ server_address_field('service-base-url') }} {% endif %} {% if provider.auth_kind == 'basic' %}
{{ field_label('service-username', _("Username")) }}
{{ field_label('service-password', _("App password"), _("Yaffo keeps it in your system's keychain to sign in again. Use an app password or a dedicated user if the service offers one.")) }}
{% else %} {{ api_key_field('service-api-key', provider.key_label, provider.key_help) }} {% endif %}
{% endif %}
{% endif %} {% elif tab == 'web' %} {% if service.endpoints %} {{ section_heading(_("Endpoints"), _("Found in the code. Nothing else can be called. You approve this list when you publish.")) }} {% else %}

{{ _("No web calls yet.") }}

{% endif %} {% elif tab == 'code' %} {% if row.working_code %} {% if row.published and row.published_code != row.working_code %}

{{ _("Differs from the published version.") }}

{% endif %}
{{ row.working_code }}
{% else %}

{{ _("No code yet.") }}

{% endif %} {% elif tab == 'test' %} {% if draft %} {{ section_heading(_("Test the draft"), _("Runs the draft once: reads go to the test account for real, and nothing is ever sent or changed. The builder sees the result with your next message.")) }}
{% for key, spec in draft.options.items() %}
{{ field_label('service-option-' ~ key, spec.label, _("An id from the choices a Test lists. Leave it empty to see them.") if key in service.remote_options else spec.help) }}
{% endfor %}
{% else %}

{{ _("Nothing to test yet.") }}

{% endif %} {% endif %}
{% if service.can_publish %} {{ render_modal('servicePublishModal', _('Publish this service?'), url_for('sync_service_publish', slug=row.slug), render_publish_review, _('Publish')) }} {% endif %} {% endblock %} {% block scripts %} {% endblock %}