<h2 dir="auto">Motivation</h2>
<p dir="auto">Currently, Druid has three web consoles:</p>
<ol dir="auto">
<li>The coordinator console at <a href="http://coordinator:8081/#/" rel="nofollow">http://coordinator:8081/#/</a>, whose source lives at <a href="https://github.com/druid-io/druid-console">https://github.com/druid-io/druid-console</a></li>
<li>The overlord console at <a href="http://overlord:8090/" rel="nofollow">http://overlord:8090/</a>.</li>
<li>The old coordinator console at <a href="http://coordinator:8081/old-console/" rel="nofollow">http://coordinator:8081/old-console/</a>.</li>
</ol>
<p dir="auto">Each one has a distinct design and there are very few cross-links between them. This leads to a disjointed user experience. Also, the fact that the sources for console (1) are not in the Druid repo make it difficult to keep it up-to-date.</p>
<p dir="auto">A unified console would improve the user experience and would make it easier for Druid developers to keep it up-to-date as Druid's APIs evolve. The change also provides an opportunity to update their design and functionality a bit.</p>
<h2 dir="auto">Proposed change</h2>
<p dir="auto">The proposed change is to create a new unified web console that subsumes the functionality of the three existing consoles. The unified UI would have its code live inside the Druid repository, making it easier to keep up to date.</p>
<p dir="auto">The UI may end up being based on Druid SQL system tables (<a href="http://druid.io/docs/latest/querying/sql#retrieving-metadata" rel="nofollow">http://druid.io/docs/latest/querying/sql#retrieving-metadata</a>), which would allow it to be more flexible than a console based on the currently-available coordinator and overlord APIs. The currently-available APIs were designed to support the existing consoles and return exactly what they need to know, but the system tables are more generic, and could power a more flexible new UI. (Imagine being able to show the distribution of data on historical nodes just for a particular datasource -- that kind of thing.) It would also allow the UI to more easily gain new powers as new columns or tables are added to system tables.</p>
<p dir="auto">Since a unified UI would presumably need to access APIs on both the coordinator and overlord, we would need to either use proxying or CORS to allow that to happen. Proxying makes more sense, imo, since CORS requires extra configuration and some kind of consistent domain, which is not necessarily common amongst Druid deployments (many of which would use raw IPs). It could be hosted on the coordinator if the coordinator makes a proxy available to the overlord and broker. Or, it could be hosted on the router, which can already proxy to the coordinator, overlord, and broker (the former two via the optional management proxy). Or, it could be hosted on a new node type (CliWebConsole?). But we have enough node types already…</p>
<h2 dir="auto">New or changed public interfaces</h2>
<p dir="auto">No new or changed APIs, but there would be a new human interface.</p>
<h2 dir="auto">Migration plan and compatibility</h2>
<p dir="auto">Keep the existing consoles as-is and offer the new console as a new option. But deprecate the existing consoles and plan to remove them in a future version.</p>
<h2 dir="auto">Rejected alternatives</h2>
<p dir="auto">None.</p>
<p dir="auto">Complement of roaring bit map returns incorrect result.<br>
It seems that complement does not work on the last bit of the roaring bitmap.</p>
<p dir="auto">I used following test code and all tests passed with concise bitmap but failed with roaring bitmap.<br>
Result shows the last bit of roaring bitmap is unchanged even after complement operation.</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="public class RoaringBitmapTest
{
  @Parameterized.Parameters
  public static Iterable&lt;Object[]&gt; constructorFeeder()
  {
    return ImmutableList.of(
        new Object[]{new ConciseBitmapFactory()},
        new Object[]{new RoaringBitmapFactory()}
    );
  }

  private final BitmapFactory testBitmapFactory;
  public RoaringBitmapTest(BitmapFactory bitmapFactory)
  {

    this.testBitmapFactory = bitmapFactory;
  }

  @Test
  public void testComplement1()
  {
    final MutableBitmap mutableBitmap = testBitmapFactory.makeEmptyMutableBitmap();
    mutableBitmap.add(0);
    mutableBitmap.add(2);
    mutableBitmap.add(7);
    ImmutableBitmap testBitmap = testBitmapFactory.makeImmutableBitmap(mutableBitmap);
    ImmutableBitmap immutableBitmap = testBitmapFactory.complement(testBitmap, 8);
    String result = immutableBitmap.toString().replaceAll(&quot;\\s&quot;,&quot;&quot;).split(&quot;\\[|\\]|\\{|\\}&quot;)[1];
    Assert.assertEquals(&quot;1,3,4,5,6&quot;, result);
  }

  @Test
  public void testComplement2()
  {
    final MutableBitmap mutableBitmap = testBitmapFactory.makeEmptyMutableBitmap();
    mutableBitmap.add(0);
    mutableBitmap.add(2);
    ImmutableBitmap testBitmap = testBitmapFactory.makeImmutableBitmap(mutableBitmap);
    ImmutableBitmap immutableBitmap = testBitmapFactory.complement(testBitmap, 4);
    String result = immutableBitmap.toString().replaceAll(&quot;\\s&quot;,&quot;&quot;).split(&quot;\\[|\\]|\\{|\\}&quot;)[1];
    Assert.assertEquals(&quot;1,3&quot;, result);
  }

  @Test
  public void testComplement3()
  {
    final MutableBitmap mutableBitmap = testBitmapFactory.makeEmptyMutableBitmap();
    mutableBitmap.add(4);
    mutableBitmap.add(5);
    ImmutableBitmap testBitmap = testBitmapFactory.makeImmutableBitmap(mutableBitmap);
    ImmutableBitmap immutableBitmap = testBitmapFactory.complement(testBitmap, 10);
    String result = immutableBitmap.toString().replaceAll(&quot;\\s&quot;,&quot;&quot;).split(&quot;\\[|\\]|\\{|\\}&quot;)[1];
    Assert.assertEquals(&quot;0,1,2,3,6,7,8,9&quot;, result);
  }
}"><pre lang="@RunWith(Parameterized.class)" class="notranslate"><code class="notranslate">public class RoaringBitmapTest
{
  @Parameterized.Parameters
  public static Iterable&lt;Object[]&gt; constructorFeeder()
  {
    return ImmutableList.of(
        new Object[]{new ConciseBitmapFactory()},
        new Object[]{new RoaringBitmapFactory()}
    );
  }

  private final BitmapFactory testBitmapFactory;
  public RoaringBitmapTest(BitmapFactory bitmapFactory)
  {

    this.testBitmapFactory = bitmapFactory;
  }

  @Test
  public void testComplement1()
  {
    final MutableBitmap mutableBitmap = testBitmapFactory.makeEmptyMutableBitmap();
    mutableBitmap.add(0);
    mutableBitmap.add(2);
    mutableBitmap.add(7);
    ImmutableBitmap testBitmap = testBitmapFactory.makeImmutableBitmap(mutableBitmap);
    ImmutableBitmap immutableBitmap = testBitmapFactory.complement(testBitmap, 8);
    String result = immutableBitmap.toString().replaceAll("\\s","").split("\\[|\\]|\\{|\\}")[1];
    Assert.assertEquals("1,3,4,5,6", result);
  }

  @Test
  public void testComplement2()
  {
    final MutableBitmap mutableBitmap = testBitmapFactory.makeEmptyMutableBitmap();
    mutableBitmap.add(0);
    mutableBitmap.add(2);
    ImmutableBitmap testBitmap = testBitmapFactory.makeImmutableBitmap(mutableBitmap);
    ImmutableBitmap immutableBitmap = testBitmapFactory.complement(testBitmap, 4);
    String result = immutableBitmap.toString().replaceAll("\\s","").split("\\[|\\]|\\{|\\}")[1];
    Assert.assertEquals("1,3", result);
  }

  @Test
  public void testComplement3()
  {
    final MutableBitmap mutableBitmap = testBitmapFactory.makeEmptyMutableBitmap();
    mutableBitmap.add(4);
    mutableBitmap.add(5);
    ImmutableBitmap testBitmap = testBitmapFactory.makeImmutableBitmap(mutableBitmap);
    ImmutableBitmap immutableBitmap = testBitmapFactory.complement(testBitmap, 10);
    String result = immutableBitmap.toString().replaceAll("\\s","").split("\\[|\\]|\\{|\\}")[1];
    Assert.assertEquals("0,1,2,3,6,7,8,9", result);
  }
}
</code></pre></div>