Skip to content

Dashy

LibrisLog can be integrated into Dashy, a self-hosted dashboard for your services, using one of its widget types:

  • Custom API widget (Dashy ≥4.3.1) — a simple, built-in widget that fetches your stats directly (recommended).
  • HTML embedded widget — a fully customizable widget with styled stat cards.

Prerequisites

  • A running LibrisLog instance reachable from your Dashy server
  • An API key with access to the statistics endpoint

Since Dashy v4.3.1 you can use the built-in custom API widget. Add the following to your Dashy conf.yml:

yaml
widgets:
  - type: customapi
    options:
      url: <LIBRISLOG-URL>/api/books/stats
      headers:
        X-API-Key: "<API-KEY>"
      refreshInterval: 60000
      display: block
      mappings:
        - field: books_read
          label: Read
          format: number
        - field: books_reading
          label: Reading
          format: number
        - field: books_want_to_read
          label: Want to read
          format: number
        - field: total_books
          label: Total
          format: number

The refreshInterval is specified in milliseconds. 60000 equals 1 minute.

HTML Embedded Widget

For full control over the appearance you can use the HTML embedded widget.

Add the following to your Dashy conf.yml:

yaml
widgets:
  - type: embed
    updateInterval: 300
    options:
      html: |
        <div class="librislog-widget">
          <div class="ll-stat-item">
            <span class="ll-label">Reading</span>
            <span class="ll-value" id="ll-reading">-</span>
          </div>
          <div class="ll-stat-item">
            <span class="ll-label">Read</span>
            <span class="ll-value" id="ll-read">-</span>
          </div>
          <div class="ll-stat-item">
            <span class="ll-label">Want to Read</span>
            <span class="ll-value" id="ll-wtr">-</span>
          </div>
          <div class="ll-stat-item">
            <span class="ll-label">Total Books</span>
            <span class="ll-value" id="ll-total">-</span>
          </div>
        </div>
      css: |
        .librislog-widget {
          display: grid;
          grid-template-columns: repeat(2, 1fr);
          gap: 0.75rem;
          padding: 0.5rem;
          font-family: inherit;
        }
        .ll-stat-item {
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          background: var(--background-elevated, rgba(255,255,255,0.05));
          border: 1px solid var(--outline-color, rgba(255,255,255,0.1));
          border-radius: 6px;
          padding: 0.5rem;
          text-align: center;
        }
        .ll-label {
          font-size: 0.8rem;
          opacity: 0.7;
          color: var(--text-color, #fff);
          margin-bottom: 0.25rem;
          text-transform: uppercase;
          letter-spacing: 0.05em;
        }
        .ll-value {
          font-size: 1.4rem;
          font-weight: bold;
          color: var(--primary, #00bc8c);
        }
      script: |
        (async function() {
          const apiUrl = '<LIBRISLOG-URL>/api/books/stats';
          const apiKey = '<API-KEY>';

          try {
            const response = await fetch(apiUrl, {
              method: 'GET',
              headers: {
                'X-API-Key': apiKey,
                'Content-Type': 'application/json'
              }
            });

            if (!response.ok) throw new Error('API request failed');

            const data = await response.json();

            document.getElementById('ll-reading').innerText = data.books_currently_reading ?? data.books_reading ?? 0;
            document.getElementById('ll-read').innerText = data.books_read ?? 0;
            document.getElementById('ll-wtr').innerText = data.books_want_to_read ?? 0;
            document.getElementById('ll-total').innerText = data.total_books ?? 0;

          } catch (error) {
            console.error('LibrisLog Widget Error:', error);
            const elements = ['ll-reading', 'll-read', 'll-wtr', 'll-total'];
            elements.forEach(id => {
              const el = document.getElementById(id);
              if (el) el.innerText = '!';
              if (el) el.style.color = 'var(--danger, #ff0033)';
            });
          }
        })();

The updateInterval is specified in seconds. 300 equals 5 minutes.

Placeholders

Replace the placeholders with your own values:

PlaceholderExampleDescription
<LIBRISLOG-URL>http://192.168.1.100:8000The base URL of your LibrisLog instance (http or https)
<API-KEY>lk_nRHsF3jxIBDa9u....An API key with access to the statistics endpoint

CORS

The embed widget runs inside the Dashy iframe and fetches the API directly from the browser. You must add your Dashy URL to the CORS_ORIGINS environment variable of the LibrisLog backend:

CORS_ORIGINS=["<LIBRISLOG-URL>"]

The custom API widget also requires CORS if Dashy is configured to render widgets client-side. If CORS errors occur, add the origin as shown above.

Results

Custom API (API Response) Widget

Dashy Custom API Widget

HTML Embed Widget

Dashy HTML Embedded Widget

Released under the MIT License.