mirror of
				https://github.com/meilisearch/meilisearch.git
				synced 2025-11-04 09:56:28 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			334 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			334 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<!DOCTYPE html>
 | 
						|
<html>
 | 
						|
  <head>
 | 
						|
    <meta charset="utf-8">
 | 
						|
    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
						|
    <link rel="stylesheet" href="bulma.min.css">
 | 
						|
    <title>MeiliSearch</title>
 | 
						|
    <style>
 | 
						|
      em {
 | 
						|
        color: hsl(204, 86%, 25%);
 | 
						|
        font-style: inherit;
 | 
						|
        background-color: hsl(204, 86%, 88%);
 | 
						|
      }
 | 
						|
 | 
						|
      #results {
 | 
						|
        max-width: 900px;
 | 
						|
        margin: 20px auto 0 auto;
 | 
						|
        padding: 0;
 | 
						|
      }
 | 
						|
 | 
						|
      .notification {
 | 
						|
        display: flex;
 | 
						|
        justify-content: center;
 | 
						|
      }
 | 
						|
 | 
						|
      .level-left {
 | 
						|
        margin-right: 50px;
 | 
						|
      }
 | 
						|
 | 
						|
      .document {
 | 
						|
        border-radius: 4px;
 | 
						|
        margin-bottom: 20px;
 | 
						|
        display: flex;
 | 
						|
      }
 | 
						|
 | 
						|
      .document ol {
 | 
						|
        flex: 0 0 75%;
 | 
						|
        max-width: 75%;
 | 
						|
        padding: 0;
 | 
						|
        margin: 0;
 | 
						|
        list-style-type: none;
 | 
						|
      }
 | 
						|
 | 
						|
      .document ol li {
 | 
						|
        list-style: none;
 | 
						|
      }
 | 
						|
 | 
						|
 | 
						|
      .document .image {
 | 
						|
        max-width: 50%;
 | 
						|
        margin: 0 auto;
 | 
						|
        box-sizing: border-box;
 | 
						|
      }
 | 
						|
 | 
						|
      @media screen and (min-width: 770px) {
 | 
						|
        .document .image {
 | 
						|
          max-width: 25%;
 | 
						|
          flex: 0 0 25%;
 | 
						|
          margin: 0;
 | 
						|
          padding-left: 30px;
 | 
						|
          box-sizing: border-box;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      .document .image img {
 | 
						|
        width: 100%;
 | 
						|
      }
 | 
						|
 | 
						|
      .attribute {
 | 
						|
        text-align: center;
 | 
						|
        box-sizing: border-box;
 | 
						|
        text-transform: uppercase;
 | 
						|
        font-weight: bold;
 | 
						|
        color: rgba(0,0,0,.7);
 | 
						|
      }
 | 
						|
 | 
						|
      @media screen and (min-width: 770px) {
 | 
						|
        .attribute {
 | 
						|
          flex: 0 0 25%;
 | 
						|
          max-width: 25%;
 | 
						|
          text-align: right;
 | 
						|
          padding-right: 10px;
 | 
						|
          font-weight: normal;
 | 
						|
          box-sizing: border-box;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      @media screen and (max-width: 770px) {
 | 
						|
        .attribute {
 | 
						|
          padding-bottom: 0;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      .content {
 | 
						|
        flex: 0 0 75%;
 | 
						|
        box-sizing: border-box;
 | 
						|
        color: rgba(0,0,0,.9);
 | 
						|
        overflow-wrap: anywhere;
 | 
						|
      }
 | 
						|
 | 
						|
      .hero-foot {
 | 
						|
        padding-bottom: 3rem;
 | 
						|
      }
 | 
						|
 | 
						|
      @media screen and (max-width: 770px) {
 | 
						|
        .align-on-mobile {
 | 
						|
          text-align: center;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    </style>
 | 
						|
  </head>
 | 
						|
  <body>
 | 
						|
 | 
						|
    <section class="hero is-light">
 | 
						|
      
 | 
						|
      <div class="hero-body">
 | 
						|
        <div class="container">
 | 
						|
          <div class="content is-medium align-on-mobile">
 | 
						|
            <h1 class="title is-1 is-spaced">
 | 
						|
              Welcome to MeiliSearch
 | 
						|
            </h1>
 | 
						|
            <p class="subtitle is-4">
 | 
						|
              This dashboard will help you check the search results with ease.
 | 
						|
            </p>
 | 
						|
          </div>
 | 
						|
          <div class="columns">
 | 
						|
            <div class="column is-4">
 | 
						|
              <div class="field">
 | 
						|
                  <!-- API Key -->
 | 
						|
                    <label class="label" for="apiKey">API Key (optionnal)</label>
 | 
						|
                    <div class="control">
 | 
						|
                      <input id="apiKey" class="input is-small" type="password" placeholder="Enter your API key">
 | 
						|
                    </div>
 | 
						|
                    <p class="help">At least a private API key is required for the dashboard to access the indexes list.</p>
 | 
						|
              </div>
 | 
						|
            </div>
 | 
						|
          </div>
 | 
						|
          <div class="columns">
 | 
						|
            <div class="column is-8">
 | 
						|
                <label class="label" for="search">Search something</label>
 | 
						|
                <div class="field has-addons">
 | 
						|
                  <div class="control">
 | 
						|
                    <span class="select">
 | 
						|
                      <select role="listbox" id="index" aria-label="Select the index you want to search on">
 | 
						|
                        <!-- indexes names -->
 | 
						|
                      </select>
 | 
						|
                    </span>
 | 
						|
                  </div>
 | 
						|
                  <div class="control is-expanded">
 | 
						|
                    <input id="search" class="input" type="search" autofocus placeholder="e.g. George Clooney" aria-label="Search through your documents">
 | 
						|
                  </div>
 | 
						|
                </div>
 | 
						|
            </div>
 | 
						|
            <div class="column is-4">
 | 
						|
              <div class="columns">
 | 
						|
                <div class="column is-6 has-text-centered">
 | 
						|
                  <p class="heading">Documents</p>
 | 
						|
                  <p id="count" class="title">0</p>
 | 
						|
                </div>
 | 
						|
                <div class="column is-6 has-text-centered">
 | 
						|
                  <p class="heading">Time Spent</p>
 | 
						|
                  <p id="time" class="title">N/A</p>
 | 
						|
                </div>
 | 
						|
              </div>
 | 
						|
            </div>
 | 
						|
          </div>
 | 
						|
        </div>
 | 
						|
      </div>
 | 
						|
    </section>
 | 
						|
 | 
						|
    <section>
 | 
						|
      <div class="container">
 | 
						|
        <ol id="results" class="content">
 | 
						|
          <!-- documents matching resquests -->
 | 
						|
        </ol>
 | 
						|
      </div>
 | 
						|
    </section>
 | 
						|
  </body>
 | 
						|
 | 
						|
  <script>
 | 
						|
    function sanitizeHTMLEntities(str) {
 | 
						|
        if (str && typeof str === 'string') {
 | 
						|
            str = str.replace(/</g,"<");
 | 
						|
            str = str.replace(/>/g,">");
 | 
						|
            str = str.replace(/<em>/g,"<em>");
 | 
						|
            str = str.replace(/<\/em>/g,"<\/em>");
 | 
						|
        }
 | 
						|
        return str;
 | 
						|
    }
 | 
						|
 | 
						|
    function httpGet(theUrl, apiKey) {
 | 
						|
        var xmlHttp = new XMLHttpRequest();
 | 
						|
        xmlHttp.open("GET", theUrl, false); // false for synchronous request
 | 
						|
        if (apiKey) {
 | 
						|
          xmlHttp.setRequestHeader("x-Meili-API-Key", apiKey);
 | 
						|
        }
 | 
						|
        xmlHttp.send(null);
 | 
						|
        return xmlHttp.responseText;
 | 
						|
    }
 | 
						|
 | 
						|
    function refreshIndexList() {
 | 
						|
        // TODO we must not block here
 | 
						|
        let result = JSON.parse(httpGet(`${baseUrl}/indexes`, localStorage.getItem('apiKey')));
 | 
						|
 | 
						|
        if (!Array.isArray(result)) { return }
 | 
						|
 | 
						|
        let select = document.getElementById("index");
 | 
						|
        select.innerHTML = '';
 | 
						|
 | 
						|
        for (index of result) {
 | 
						|
            const option = document.createElement('option');
 | 
						|
            option.value = index.uid;
 | 
						|
            option.innerHTML = index.name;
 | 
						|
            select.appendChild(option);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    let lastRequest = undefined;
 | 
						|
 | 
						|
    function triggerSearch() {
 | 
						|
        var e = document.getElementById("index");
 | 
						|
        if (e.selectedIndex == -1) { return }
 | 
						|
        var index = e.options[e.selectedIndex].value;
 | 
						|
 | 
						|
        let theUrl = `${baseUrl}/indexes/${index}/search?q=${encodeURIComponent(search.value)}&attributesToHighlight=*`;
 | 
						|
 | 
						|
        if (lastRequest) { lastRequest.abort() }
 | 
						|
        lastRequest = new XMLHttpRequest();
 | 
						|
 | 
						|
        lastRequest.open("GET", theUrl, true);
 | 
						|
 | 
						|
        if (localStorage.getItem('apiKey')) {
 | 
						|
          lastRequest.setRequestHeader("x-Meili-API-Key", localStorage.getItem('apiKey'));
 | 
						|
        }
 | 
						|
 | 
						|
        lastRequest.onload = function (e) {
 | 
						|
            if (lastRequest.readyState === 4 && lastRequest.status === 200) {
 | 
						|
                let sanitizedResponseText = sanitizeHTMLEntities(lastRequest.responseText);
 | 
						|
                let httpResults = JSON.parse(sanitizedResponseText);
 | 
						|
                results.innerHTML = '';
 | 
						|
 | 
						|
                let processingTimeMs = httpResults.processingTimeMs;
 | 
						|
                let numberOfDocuments = httpResults.nbHits;
 | 
						|
                time.innerHTML = `${processingTimeMs}ms`;
 | 
						|
                count.innerHTML = `${numberOfDocuments}`;
 | 
						|
 | 
						|
                for (result of httpResults.hits) {
 | 
						|
                    const element = {...result, ...result._formatted };
 | 
						|
                    delete element._formatted;
 | 
						|
 | 
						|
                    const elem = document.createElement('li');
 | 
						|
                    elem.classList.add("document","box");
 | 
						|
 | 
						|
                    const div = document.createElement('div');
 | 
						|
                    div.classList.add("columns","is-desktop","is-tablet");
 | 
						|
                    const info = document.createElement('div');
 | 
						|
                    info.classList.add("column","align-on-mobile");
 | 
						|
                    let image = undefined;
 | 
						|
 | 
						|
                    for (const prop in element) {
 | 
						|
                        // Check if property is an image url link.
 | 
						|
                        if (typeof result[prop] === 'string') {
 | 
						|
                            if (image == undefined && result[prop].match(/^(https|http):\/\/.*(jpe?g|png|gif)(\?.*)?$/g)) {
 | 
						|
                                image = result[prop];
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
 | 
						|
                        const field = document.createElement('div');
 | 
						|
                        field.classList.add("columns");
 | 
						|
 | 
						|
                        const attribute = document.createElement('div');
 | 
						|
                        attribute.classList.add("attribute", "column");
 | 
						|
                        attribute.innerHTML = prop;
 | 
						|
 | 
						|
                        const content = document.createElement('div');
 | 
						|
                        content.classList.add("content", "column");
 | 
						|
                        
 | 
						|
                        if (typeof (element[prop]) === "object") {
 | 
						|
                          content.innerHTML = JSON.stringify(element[prop]);
 | 
						|
                        } else {
 | 
						|
                          content.innerHTML = element[prop];
 | 
						|
                        }
 | 
						|
 | 
						|
                        field.appendChild(attribute);
 | 
						|
                        field.appendChild(content);
 | 
						|
 | 
						|
                        info.appendChild(field);
 | 
						|
                    }
 | 
						|
 | 
						|
                    div.appendChild(info);
 | 
						|
                    elem.appendChild(div);
 | 
						|
 | 
						|
                    if (image != undefined) {
 | 
						|
                        const divImage = document.createElement('div');
 | 
						|
                        divImage.classList.add("image","column","align-on-mobile");
 | 
						|
                        
 | 
						|
                        const img = document.createElement('img');
 | 
						|
                        img.src = image;
 | 
						|
                        img.setAttribute("alt","Item illustration");
 | 
						|
 | 
						|
                        divImage.appendChild(img);
 | 
						|
                        div.appendChild(divImage);
 | 
						|
                        elem.appendChild(div);
 | 
						|
                    }
 | 
						|
 | 
						|
                    results.appendChild(elem)
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                console.error(lastRequest.statusText);
 | 
						|
            }
 | 
						|
        };
 | 
						|
        lastRequest.send(null);
 | 
						|
    }
 | 
						|
 | 
						|
    if (!apiKey.value) {
 | 
						|
        apiKey.value = localStorage.getItem('apiKey');
 | 
						|
    }
 | 
						|
 | 
						|
    apiKey.addEventListener('input', function(e) {
 | 
						|
        localStorage.setItem('apiKey', apiKey.value);
 | 
						|
        refreshIndexList();
 | 
						|
    }, false);
 | 
						|
 | 
						|
    let baseUrl = window.location.origin;
 | 
						|
    refreshIndexList();
 | 
						|
 | 
						|
    search.oninput = triggerSearch;
 | 
						|
    
 | 
						|
    let select = document.getElementById("index");
 | 
						|
    select.onchange = triggerSearch;
 | 
						|
 | 
						|
    triggerSearch();
 | 
						|
  </script>
 | 
						|
</html>
 |