text:
/* global Diff2HtmlUI */
import createDebug from 'debug';

import { route } from './util';

const debug = createDebug('ms:code_status');

route('/status/code', () => {
  $('#show-all-gem-versions').click(e => {
    e.preventDefault();
    $('table#gems-versions-table .minor').toggleClass('hide');
    $(e.currentTarget).toggleClass('shown');
  });
  $('#toggle-compare-diff').click(e => {
    e.preventDefault();
    $(e.currentTarget).toggleClass('shown')
                      .parents('details')
                      .find('.compare-diff')
                      .toggleClass('hide');
  });
  $('#toggle-commit-diff').click(e => {
    e.preventDefault();
    $(e.currentTarget).toggleClass('shown')
                      .parents('details')
                      .find('.commit-diff')
                      .toggleClass('hide');
  });
  $.get('/status/code.json', ({ repo: { default_branch: defaultBranch }, compare, compare_diff, commit, commit_diff }) => {
    $('.fill-branch')
      .text(defaultBranch)
      .attr('href', `https://github.com/Charcoal-SE/metasmoke/tree/${defaultBranch}`);

    const status = [];
    if (compare.ahead_by > 0) {
      status.push(`${compare.ahead_by} commit${compare.ahead_by === 1 ? '' : 's'} behind`);
    }
    if (compare.behind_by > 0) {
      status.push(`${compare.behind_by} commit${compare.behind_by === 1 ? '' : 's'} ahead of`);
    }
    if (status.length === 0) {
      status.push('even with');
    }
    $('.fill-status').text(status.join(', '));

    renderDiff('compare', compare_diff);
    renderDiff('commit', commit_diff);

    const [message, ...other] = commit.commit.message.split('\n\n');
    const escapedMessge = escapeAndLinkify(message);
    const escapedDetails = escapeAndLinkify(other.join('\n\n').trim());
    if (other.join('').length > 0) {
      $('.commit summary').html(escapedMessge);
      $('.commit pre').html(escapedDetails);
    }
    else {
      $('.commit').html(escapedMessge);
    }
  }).fail(debug);
});

const $escaper = $('
');
function escapeAndLinkify(message) {
  return $escaper
    .text(message)
    .html()
    .replace(/(^|\W)@([a-z0-9][a-z0-9-]*)(?!\/)(?=\.+[ \t\W]|\.+$|[^0-9a-zA-Z_.]|$)/ig, '$1@$2')
    .replace(/#(\d+)/g, '#$1');
}

function renderDiff(type, diff) {
  if (!(diff && typeof diff === 'string')) {
    $(`#toggle-${type}-diff`).addClass('no-diff');
    return;
  }
  const diff2htmlUi = new Diff2HtmlUI({
    diff
  });
  diff2htmlUi.draw(`.${type}-diff`, {
    showFiles: true,
    matching: 'words'
  });
  diff2htmlUi.highlightCode(`.${type}-diff`);
  diff2htmlUi.fileListCloseable(`.${type}-diff`, false);
}


text:
import { route } from './util';

route('/flagging/settings/sites', () => {
  $('.unsaved-changes').hide();

  $('.site-search').on('change', ev => {
    const search = $(ev.target).val();
    $('tbody tr').each((i, e) => {
      const sid = $(e).data('sid');
      const siteData = window.flagging_settings[sid];
      if (siteData.name.indexOf(search) === -1 && siteData.domain.indexOf(search) === -1) {
        $(e).hide();
      }
      else {
        $(e).show();
      }
    });
  });

  $('input[type=checkbox]').on('change', ev => {
    $('.unsaved-changes').show();

    const sid = $(ev.target).parents('tr').data('sid').toString();
    const setting = $(ev.target).attr('name');
    window.flagging_settings[sid][setting] = $(ev.target).is(':checked');
    window.flagging_settings[sid].changed = true;
  });

  $('input[type=number]').on('keyup change', ev => {
    $('.unsaved-changes').show();

    const sid = $(ev.target).parents('tr').data('sid').toString();
    window.flagging_settings[sid].max_flags = parseInt($(ev.target).val(), 10);
    window.flagging_settings[sid].changed = true;
  });

  $('.save-changes').on('click', async ev => {
    ev.preventDefault();

    await fetch('/flagging/settings/sites', {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ data: window.flagging_settings })
    });
    $('.unsaved-changes').fadeOut(200);
  });
});

repo_name: Charcoal-SE/metasmoke

repo_language: Ruby

mime_type: text/plain


text:
/* globals Highcharts */

$(document).ajaxSuccess((ev, xhr, options) => {
  // Equivalent detection to route() from './util', but doesn't add multiple listeners.
  if (window.location.pathname === '/flagging/conditions/sandbox') {
    if (options.url.indexOf('/graphs/af_accuracy') >= 0) {
      Highcharts.charts[0].update({ series: [{ marker: { enabled: false }, linecap: 'butt' }, { marker: { enabled: false }, linecap: 'butt' },
        { yAxis: 1, marker: { enabled: false }, linecap: 'butt' }] });
    }
  }
});


text:
import createDebug from 'debug';
import { onLoad } from './util';

const debug = createDebug('ms:comments');

onLoad(() => {
  $(document.body).on('click', '.new-comment', ev => {
    debug('.new-comment click');
    ev.preventDefault();
    $('.add-comment').show();
    $(ev.target).remove();
  });

  $(document.body).on('click', '.comment-edit', async ev => {
    ev.preventDefault();

    const $comment = $(ev.target).parents('.post-comment, .abuse-comment');
    const $commentPanel = $comment.find('.panel-body');
    const id = $comment.data('cid');
    const uri = $comment.hasClass('post-comment') ? `/comments/${id}` : `/abuse/comments/${id}`;

    const resp = await fetch(uri, {
      credentials: 'include'
    });
    const json = await resp.json();
    const text = json.text;

    $commentPanel.html(`<form method="POST" action="${uri}/edit">

            <textarea name="text" rows="3" cols="100" placeholder="Useful information about this post that others might need..."
                      class="form-control"></textarea>

            <input type="submit" value="Update comment" class="btn btn-primary" />

`);
    /* We don't put the text in the above HTML, because
     *   1. It's a potential security risk, as the text is interpreted as raw HTML and could result in
     *      execution of arbitrary code. This is particularly an issue here, because the provided comment text
     *      is likely to have been provided by a different user and isn't sanitized.
     *   2. Having it in the above HTML text converts some HTML in the comment text (e.g. HTML entities are
     *      converted to characters). This can result in being unable to save the comment, even if unedited.
     */
    $commentPanel.find('textarea').val(text);
  });
});


text:
import { route } from './util';

route(/^\/spam-waves/, () => {
  const forExpiry = $('[for="expiry"]');
  const expiryInput = $('#expiry');
  const existingButton = $('.expiry-in-48-hours-button');
  if (forExpiry.length > 0 && expiryInput.length > 0 && existingButton.length === 0) {
    const new48HoursButton = $('<button class="expiry-in-48-hours-button">in 48 hours</button>').on('click', event => {
      const newExpire = new Date(Date.now() + 48 * 60 * 60 * 1000);
      newExpire.setMilliseconds(0);
      expiryInput.val(newExpire.toISOString().replace('.000Z', '+00.0'));
      event.preventDefault();
    })
      .css({ marginLeft: '10px' });
    forExpiry.after(new48HoursButton);
    const valCriteriaList = ['title', 'body', 'username', 'max_user_rep'];
    const newSearchButton = $('(Search)')
      .css({
        'font-size': '14px',
        'margin-left': '10px'
      });
    newSearchButton
      .find('a')
      .on('mousedown mouseover', function () {
        const criteriaInPage = valCriteriaList.map(key => [key, $(`#conditions_${key}${key.includes('rep') ? '' : '_regex'}`).val()]);
        const encodedCriteria = criteriaInPage.reduce((sum, [key, value]) => {
          if (key === 'max_user_rep') {
            return sum + `&user_rep_direction=%3C%3D&user_reputation=${value}`;
          } // Else
          // Convert the Unicode codepoint escapes from the format needed in spam waves to what's needed in search.
          value = value.replace(/\\u([\da-fA-F]{4})/g, '\\x{$1}');
          return sum + `&${key}_is_regex=1&${key}=${encodeURIComponent(value)}`;
        }, '');
        const sitesSelect = $('#sites');
        const selectionInnerUlLi = sitesSelect.closest('.bootstrap-select').find('.inner.open ul.inner > li');
        const selectedSiteLi = selectionInnerUlLi.filter('.selected');
        let siteParameter = '';
        let siteOptions = $();
        if (selectionInnerUlLi.length === 0) {
          // The select dropdown has not been opened, so we get the <option selected="selected">
          siteOptions = sitesSelect.find('option[selected="selected"]');
        }
        else if (selectedSiteLi.length === 1) {
          // The site dropdown has been opened and populated and there's only one site selected.
          // Search only supports a single site, so we don't try to handle converting multiple selections.
          const site = selectedSiteLi.text().trim();
          siteOptions = sitesSelect.find('option').filter(function () {
            return this.textContent.trim() === site;
          });
        }
        if (siteOptions.length === 1) {
          // Search only supports a single site
          siteParameter = `&site=${siteOptions.val()}`;
        }
        const url = `https://metasmoke.erwaysoftware.com/search?utf8=%E2%9C%93${encodedCriteria}${siteParameter}`;
        $(this).attr('href', url);
      });
    $('h3:contains("Conditions")').append(newSearchButton);
  }
  $('[type="datetime-local"]').attr('type', 'datetime');
});


text:
import createDebug from 'debug';
import { route } from './util';

const debug = createDebug('ms:site_settings');

route('/admin/settings', () => {
  $('.setting-checkbox').on('change', async ev => {
    const target = $(ev.target);
    const name = target.data('name');
    const checked = target.is(':checked');
    const resp = await fetch(`/admin/settings/${name}?value=${checked}`, {
      method: 'post',
      credentials: 'include'
    });
    try {
      const json = await resp.json();
      if (json.success) {
        debug('Changed :)');
      }
      else {
        debug('???');
      }
    }
    catch (err) {
      debug('???', resp);
    }
  });

  $('.editable-value').on('click', ev => {
    ev.stopPropagation();
    const value = $(ev.target).text();
    const type = $(ev.target).data('type');
    const step = $(ev.target).data('step') || 1;
    $(ev.target).html(`<input type="${type}" step="${step}" class="editing-value form-control input-sm" value="${value}" />`);
  });

  $(document.body).on('click', () => {
    $('.editing-value').each((i, e) => {
      const value = $(e).val();
      $(e).parent().text(value);
    });
  });

  $(document.body).on('keypress', '.editing-value', async ev => {
    if (ev.charCode === 13) {
      const name = $(ev.target).parent().data('name');
      const value = $(ev.target).val();
      const resp = await fetch(`/admin/settings/${name}?value=${value}`, {
        method: 'post',
        credentials: 'include'
      });
      try {
        const json = await resp.json();
        if (json.success) {
          debug('Changed :)');
          $(document).click();
        }
        else {
          debug('???');
        }
      }
      catch (err) {
        debug('???', resp);
      }
    }
  });
});


text:
# From glennfu on Github Gist: https://gist.github.com/glennfu/936548b58091f284dbb5c1a769130bfc

import Turbolinks from 'turbolinks'

OldHttpRequest = Turbolinks.HttpRequest

class Turbolinks.CachedHttpRequest extends Turbolinks.HttpRequest
  constructor: (_, location, referrer) ->
    super(this, location, referrer)

  requestCompletedWithResponse: (response, redirectedToLocation) ->
    @response = response
    @redirect = redirectedToLocation

  requestFailedWithStatusCode: (code) ->
    @failCode = code

  oldSend: ->
    if @xhr and not @sent
      @notifyApplicationBeforeRequestStart()
      @setProgress(0)
      @xhr.send()
      @sent = true
      @delegate?.requestStarted?()

  send: () ->
    if @failCode
      @delegate.requestFailedWithStatusCode(@failCode, @failText)
    else if @response
      @delegate.requestCompletedWithResponse(@response, @redirect)
    else
      @oldSend()

class Turbolinks.HttpRequest
  constructor: (delegate, location, referrer) ->
    cache = Turbolinks.controller.cache.get("prefetch" + location)
    if cache
      Turbolinks.controller.cache.delete("prefetch" + location)
      cache.delegate = delegate
      return cache
    else
      return new OldHttpRequest(delegate, location, referrer)

(Turbolinks.Cache || Turbolinks.SnapshotCache)::delete = (location) ->
  key = Turbolinks.Location.wrap(location).toCacheKey()
  delete @snapshots[key]

preload = (event) ->
  if link = Turbolinks.controller.getVisitableLinkForNode(event.target)
    if location = Turbolinks.controller.getVisitableLocationForLink(link)
      if Turbolinks.controller.applicationAllowsFollowingLinkToLocation(link, location)
        if (method = link.attributes["data-method"])? && method.value != 'get'
          return
        if location.anchor or location.absoluteURL.endsWith("#")
          return
        if location.absoluteURL == window.location.href
          return

        # If Turbolinks has already cached this location internally, use that default behavior
        # otherwise we can try and prefetch it here
        cache = Turbolinks.controller.cache.get(location)
        if !cache
          cache = Turbolinks.controller.cache.get("prefetch" + location)

        if !cache
          request = new Turbolinks.CachedHttpRequest(null, location, window.location)
          Turbolinks.controller.cache.put("prefetch" + location, request)
          request.send()

document.addEventListener("touchstart", preload)
document.addEventListener("mousedown", preload)


text:
import createDebug from 'debug';

import { onLoad } from './util';

const debug = createDebug('ms:admin');

onLoad(() => {
  let nukeUserConfirmed = false;

  $('a.nuke-user-link').click(function (e) {
    e.preventDefault();

    const $this = $(this);
    if (!nukeUserConfirmed) {
      nukeUserConfirmed = window.confirm('Are you sure?  This will permanently destroy \'' + $this.data('username') + '\' and all associated records.  ' +
                                         'Note that you will not be prompted for confirmation to nuke another user until you refresh the page.');
      if (!nukeUserConfirmed) {
        return;
      }
    }
    $.ajax({
      type: 'delete',
      url: '/admin/permissions/' + $this.data('user-id'),
      success() {
        $this.closest('tr').remove();
      }
    });
  });

  $('input.permissions-checkbox').change(function () {
    const $this = $(this);
    $this.disabled = true;
    $.ajax({
      type: 'put',
      data: {
        permitted: $this.is(':checked'),
        user_id: $this.data('user-id'),
        role: $this.data('role')
      },
      dataType: 'json',
      url: '/admin/permissions/update',
      success() {
        $this.disabled = false;
      }
    });
  });

  $('input.pin-checkbox').change(function () {
    const $this = $(this);
    $this.disabled = true;
    $.ajax({
      type: 'put',
      data: {
        permitted: $this.is(':checked'),
        pinned: true,
        user_id: $this.data('user-id'),
        role: $this.data('role')
      },
      dataType: 'json',
      url: '/admin/permissions/update',
      success() {
        $this.disabled = false;
      }
    });
  });

  $('input.trust-checkbox').change(function () {
    const $this = $(this);
    $this.disabled = true;
    $.ajax({
      type: 'post',
      data: {
        trusted: $this.is(':checked')
      },
      dataType: 'json',
      url: `/admin/keys/${$this.data('key-id')}/trust`,
      success: data => {
        if (data !== 'OK') {
          debug('toggle failed:', data);
        }
        $this.disabled = false;
      }
    });
  });

  $('#admin-report-modal form').on('ajax:success', ev => {
    const $target = $(ev.target);
    $target.parents('.modal').modal('hide');
  });
});


text:
// Vendor stuff

/* eslint-disable import/no-unassigned-import */

import 'bootstrap-select';
import 'jquery-tablesort';
import 'jquery-ujs';
import 'livestamp';


text:
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
//
// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate
// layout file, like app/views/layouts/application.html.erb

/* eslint-disable
  import/no-unassigned-import,
  import/newline-after-import,
  import/first
*/

import createDebug from 'debug';

import Turbolinks from 'turbolinks';
import '../turbolinks_prefetch.coffee'; // The original is in coffee.
Turbolinks.start();

import '../cable';

import '../admin';
import '../api';
import '../code_status';
import '../data';
import '../developer';
import '../domain_links';
import '../email_preferences';
import '../flag_conditions';
import '../flag_settings';
import '../reasons';
import '../review';
import '../stack_exchange_users';
import '../status';
import '../user_site_settings';
import '../graphs';
import '../site_settings';
import '../comments';
import '../spam_waves';
import '../general_fixes';
import '../post_preview';

import { onLoad, installSelectpickers, uuid4, hashCode } from '../util';

const metasmoke = window.metasmoke = {
  debug: createDebug('ms:application'),

  storage: new Proxy(localStorage, {
    get: (target, name) => {
      return target.metasmoke ? JSON.parse(target.metasmoke)[name] : null;
    },

    set: (target, name, value) => {
      if (!target.metasmoke) {
        target.metasmoke = JSON.stringify({});
      }
      const data = JSON.parse(target.metasmoke);
      data[name] = value;
      target.metasmoke = JSON.stringify(data);
      return true;
    }
  }),

  init: Object.assign(() => {
    $('[data-toggle="tooltip"]').tooltip();
    $('[data-toggle="popover"]').popover();

    $('.sortable-table').tablesort();

    $('.form-submit').click(ev => {
      $(ev.target).parent().submit();
    });

    $('.wave-preview').click(() => {
      $.ajax({
        url: '/spam-waves/preview',
        data: $('form').serialize()
      });
    });

    installSelectpickers();

    metasmoke.init.setupAnnouncementCollapse();
    metasmoke.init.initFormParamCleanups();
    metasmoke.init.setupAjaxDeduplicator();
    metasmoke.init.checkReviewCountKicker();
    metasmoke.init.setPostRenderModes();
    metasmoke.init.setDocumentTitle();
  }, {
    setupAnnouncementCollapse: () => {
      $('.announcement-collapse').click(ev => {
        ev.preventDefault();

        const collapser = $('.announcement-collapse');
        const announcements = $('.announcements').children('.alert-info');
        const showing = collapser.text().indexOf('Hide') > -1;
        if (showing) {
          const text = announcements.map((i, x) => $('p', x).text()).toArray().join(' ');
          metasmoke.storage['read-announcements'] = text;
          $('.announcements:not(body)').slideUp(500);
          collapser.text('Show announcements');
        }
        else {
          metasmoke.storage['read-announcements'] = null;
          $('.announcements:not(body)').slideDown(500);
          collapser.text('Hide announcements');
        }
      });

      const announcements = $('.announcements').children('.alert-info');
      const text = announcements.map((i, x) => $('p', x).text()).toArray().join(' ');

      const read = metasmoke.storage['read-announcements'];
      if (read && read === text) {
        $('.announcements:not(body)').hide();
        $('.announcement-collapse').text('Show announcements');
      }
    },

    initFormParamCleanups: () => {
      const formParameterCleanups = [];

      // This excludes data-remote, because other JavaScript creates the POST, so this code
      // results in a duplicated POST being sent.
      $(document.body).on('submit', 'form:not([data-remote="true"])', ev => {
        const tgt = $(ev.target);
        if (formParameterCleanups.indexOf(tgt[0]) === -1) {
          ev.preventDefault();
          $(tgt.find(':input').toArray().filter(e => $(e).val() === '')).attr('disabled', true);
          formParameterCleanups.push(tgt[0]);
          tgt.submit();
        }
      });
    },

    setupAjaxDeduplicator: () => {
      $(document.body).on('ajax:beforeSend', 'form[data-deduplicate]', (ev, xhr) => {
        const $tgt = $(ev.target);
        if (!$tgt.data('dedup-uuid')) {
          $tgt.attr('data-dedup-uuid', uuid4());
        }

        const dedupUuid = $tgt.data('dedup-uuid');
        const data = $(ev.target).serialize();
        const requestId = `${dedupUuid}/${hashCode(data)}`;
        xhr.setRequestHeader('X-AJAX-Deduplicate', requestId);
      });
    },

    checkReviewCountKicker: () => {
      const reviewCounter = $('.reviews-count');
      if (reviewCounter.length > 0 && parseInt(reviewCounter.text().trim(), 10) > 50) {
        const reviewAlertedAt = parseInt(metasmoke.storage['review-alerted-at'] || 0, 10);
        const diff = (Date.now() - reviewAlertedAt) / 1000;
        if (diff >= 14400) {
          reviewCounter.attr('data-toggle', 'tooltip').attr('data-placement', 'bottom')
          .attr('title', 'Got 5 minutes to do 10 reviews?');
          reviewCounter.tooltip('show');
          metasmoke.storage['review-alerted-at'] = Date.now().toString();
        }
      }
    },

    setPostRenderModes: () => {
      $(document.body).on('click', '.post-render-mode', ev => {
        const mode = $(ev.target).data('render-mode');
        metasmoke.storage['post-render-mode'] = mode;
        metasmoke.debug(`default render mode updated to ${mode}`);
      });

      if (metasmoke.storage['post-render-mode']) {
        $(`.post-render-mode[data-render-mode="${metasmoke.storage['post-render-mode']}"]`).tab('show');
      }

      $(document.body).on('DOMNodeInserted', '.post-body, .review-item-container', () => {
        $(`.post-render-mode[data-render-mode="${metasmoke.storage['post-render-mode']}"]`).tab('show');
      });
    },

    setDocumentTitle: () => {
      const pathname = window.location.pathname;
      if (document.title === 'metasmoke') {
        const headers = $('h1, h2, h3');
        let headerText = headers.first().text().trim();
        let postfix = '';
        headerText = pathname === '/reasons' ? 'Reasons' : headerText;
        headerText = pathname === '/spammers/sites' ? 'Spammers by Site' : headerText;
        headerText = pathname === '/spammers' ? 'Spammers' : headerText;
        headerText = pathname === '/status/code' ? 'Code Status' : headerText;
        if (headerText === 'Disambiguation' && pathname.indexOf('/posts/') > -1) {
          postfix = window.location.search.replace(/^\?[\w-]+=/, ': ');
        }
        if (headerText) {
          document.title = `${headerText}${postfix} - metasmoke`;
        }
      }
      if (pathname === '/search') {
        const urlParams = new URLSearchParams(window.location.search);
        let searchInfo = ['title', 'body', 'username', 'why', 'site', 'post_type', 'feedback', 'autoflagged', 'reason', 'page'].reduce((sum, type) => {
          const value = urlParams.get(type);
          if (value) {
            sum += sum ? '; ' : '';
            const invertText = urlParams.get(`${type}_is_inverse_regex`) ? ' (not)' : '';
            sum += `${type}${invertText}: ${value}`;
          }
          return sum;
        }, '');
        const userRep = urlParams.get('user_reputation');
        if (userRep !== null) {
          const repDirection = decodeURIComponent(urlParams.get('user_rep_direction'));
          searchInfo += searchInfo ? '; ' : '';
          searchInfo += `rep: ${repDirection} ${userRep}`;
        }
        const filter = urlParams.get('feedback_filter');
        if (filter) {
          searchInfo = `${filter.toUpperCase()} only: ${searchInfo}`;
        }
        if (searchInfo) {
          document.title = `Search - ${searchInfo} - metasmoke`;
        }
      }
    }
  })
};

onLoad(() => {
  metasmoke.init();
});


text:
import createDebug from 'debug';
import { route } from '../util';
import cable from './cable';

const debug = createDebug('ms:status');

const failoverElement = link => {
  return `<a class="text-danger"
    data-confirm="This will take effect at the next ping, within a minute. Sure?" rel="nofollow" data-method="post" href="${link}">
    Failover
  </a>`;
};

let statusSocket;
route('/status', () => {
  statusSocket = cable.subscriptions.create('StatusChannel', {
    received(data) {
      debug('received', data);

      const { id, ts_unix: tsUnix, ts_ago: tsAgo, ts_raw: tsRaw, location, is_standby: isStandby, active, failover_link: failoverLink } = data;
      if ([id, tsUnix, tsAgo, tsRaw, location].some(x => x === null || x === undefined)) {
        return;
      }

      const row = $(`.status-row[data-id=${id}]`);
      row.find('.location-cell').find('.location').text(location);

      const standByLabel = row.find('.location-cell').find('.label');
      if (isStandby) {
        standByLabel.show();
        const newLabelClass = `label-${active ? 'primary' : 'default'}`;
        standByLabel.removeClass((_, classNames) => (classNames.match(/label-\S+/) || []).join(' '));
        standByLabel.addClass(newLabelClass);
      }
      else {
        standByLabel.hide();
      }

      row.children('td.status-failover-cell').html(failoverLink ? failoverElement(failoverLink) : '');

      const pingCell = row.find('.ping-cell');
      pingCell.attr('data-livestamp', tsUnix).attr('title', tsRaw);
      pingCell.text(`${tsAgo} ago`);
    }
  });
}, () => {
  statusSocket.unsubscribe();
});


text:
import { route } from '../util';
import cable from './cable';

let postSocket;
route(/^\/post\/(\d*)(\/)?$/, () => {
  postSocket = cable.subscriptions.create({
    channel: 'PostsChannel',
    post_id: location.pathname.match(/^\/post\/(\d*)(\/)?$/)[1]
  }, {
    received(data) {
      $('strong.post-feedbacks').append(data.feedback).append(' ').find('[data-toggle="tooltip"]').tooltip();
    }
  });
}, () => {
  if (!postSocket) {
    return;
  }
  postSocket.unsubscribe();
  postSocket = null;
});


text:
import ActionCable from 'actioncable';

export default ActionCable.createConsumer();


text:
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the rails generate channel command.

/* eslint-disable import/no-unassigned-import */
import './flag_logs';
import './posts';
import './post';
import './topbar';
import './status';
import './redis_logs';


text:
import { route } from '../util';
import cable from './cable';

let isPageVisible = true;
let numUnseenPosts = 0;

let postsSocket;
route('/posts', () => {
  postsSocket = cable.subscriptions.create('PostsChannel', {
    received(data) {
      $('table#posts-index-table tbody').prepend(data.row);
      if (!isPageVisible) {
        numUnseenPosts++;
        document.title = `(${numUnseenPosts}*) Recent posts - metasmoke`;
      }
    }
  });
}, () => {
  if (!postsSocket) {
    return;
  }
  postsSocket.unsubscribe();
  postsSocket = null;
});

$(window).on('blur', () => {
  isPageVisible = false;
});

$(window).on('focus', () => {
  if (location.pathname === '/posts') {
    isPageVisible = true;
    numUnseenPosts = 0;
    document.title = 'Recent posts - metasmoke';
  }
});


text:
import { route } from '../util';
import cable from './cable';

$(document).ready(() => {
  $('#logs').on('click', '.redis-log-link', e => {
    e.stopPropagation();
  });
});

function getLogs(data) {
  const matchedLog = $('#logs div[data-log-id="' + data.key + '"]');
  const parsedHtml = $.parseHTML(data.html)[1];
  if (matchedLog.length === 0) {
    parsedHtml.setAttribute('precedence', data.precedence);
    $('#logs').prepend(parsedHtml);
  }
  else if (matchedLog[0].getAttribute('precedence') < data.precedence) {
    matchedLog[0].replaceWith(parsedHtml);
  }
}

let redis;
route(/^\/dev\/request-log.*$/, () => {
  const params = { channel: 'RedisLogChannel' };
  const status = location.pathname.match(/^\/dev\/request-log\/status\/([^/]*).*$/);
  const path = location.pathname.match(/^\/dev\/request-log\/by_path\/(\w*)\/(.*)\.[^.]*$/);
  const session = location.pathname.match(/^\/dev\/request-log\/session\/(.*)$/);
  if (status) {
    params.status = status[1];
  }
  else if (path) {
    params.path = decodeURIComponent(path[2]);
  }
  else if (session) {
    params.session = session[1];
  }
  redis = cable.subscriptions.create(params, {
    received(data) {
      getLogs(data);
    }
  });
}, () => {
  if (!redis) {
    return;
  }
  redis.unsubscribe();
  redis = null;
});


text:
import { route } from '../util';
import cable from './cable';

let flagLogs;
route('/flagging/logs', () => {
  flagLogs = cable.subscriptions.create('FlagLogsChannel', {
    received(data) {
      $('table#all-flag-logs tbody').prepend(data.row);
    }
  });
}, () => {
  flagLogs.unsubscribe();
  flagLogs = null;
});


text:
import createDebug from 'debug';
import moment from 'moment';

import cable from './cable';

const debug = createDebug('ms:topbar');

$(() => {
  cable.subscriptions.create('TopbarChannel', {
    received(arg) {
      const { review, commit, last_ping: lastPing } = arg;
      debug('received', arg);
      if (review != null) {
        $('.navbar .reviews-count').text(review || '');
      }
      if (commit != null) {
        if ($('.commit-sha').data('sha') === commit) {
          return; // Don’t worry! nothing’s changed!
        }
        $('.commit-sha').attr('href', `https://github.com/Charcoal-SE/metasmoke/commit/${commit}`)
                        .children('code').text(commit.slice(0, 7));
        $('#metasmoke-deployed-banner').slideDown();
      }
      if (lastPing != null) {
        $('.navbar .status').data('last-ping', lastPing);
        // Update the display for the new last ping time.
        updateStatus();
      }
    }
  });

  function setOnlyStatusClassIfNotHasClass(el, statusClass) {
    if (!el.hasClass(statusClass)) {
      el.removeClass('status-good status-warning status-critical')
        .addClass(statusClass);
    }
  }

  let updateStatusTimer = 0;
  function updateStatus() {
    clearTimeout(updateStatusTimer);
    if (document.hidden) {
      // If the document is hidden, then there isn't much benefit from updating the UI. The
      //   UI will be updated when the visibility of the page changes.
      return;
    }
    const $status = $('.navbar #smokedetector-status');
    const lastPing = parseFloat($status.data('last-ping')) * 1e3;
    const ago = Date.now() - lastPing;
    const title = `Last ping was ${moment(lastPing).fromNow()}.`;
    let status = 'critical';
    // The .fromNow() method only provides minute resolution. So time passing can cause the status text
    //   to change change at the earliest at the point when "ago" passes the next minute.
    //   It actually changes at 45 s, then at 1:30, 2:30 ...
    let msToNextPossibleChange = 60e3 - (ago % 60e3); // Time to next minute ago;
    // Time to next moment change
    if (ago < 45e3) {
      msToNextPossibleChange = Math.min(msToNextPossibleChange, 45e3 - ago); // Time to next moment ago change;
    }
    else {
      msToNextPossibleChange = Math.min(msToNextPossibleChange, 60e3 - ((ago - 30e3) % 60e3)); // Time to next :30
    }
    // Time to next status change
    if (ago < 90e3) {
      status = 'good';
      msToNextPossibleChange = Math.min(msToNextPossibleChange, 90e3 - ago); // Time to 1.5 minutes ago, if less;
    }
    else if (ago < 3 * 60e3) {
      status = 'warning';
      msToNextPossibleChange = Math.min(msToNextPossibleChange, 3 * 60e3 - ago); // Time to 3 minutes ago, if less;
    }
    const navbarToggle = $('.navbar-toggle');
    const statusClass = `status-${status}`;
    setOnlyStatusClassIfNotHasClass(navbarToggle, statusClass);
    setOnlyStatusClassIfNotHasClass($status, statusClass);
    const currentOrigTitle = $status.attr('data-original-title');
    const currentTooltipInnerText = $status.find('.status + .tooltip .tooltip-inner')
                                           .text();
    if (currentOrigTitle !== title || currentTooltipInnerText !== title) {
      $status.attr('data-original-title', title)
             .tooltip()
             .parent()
             .find('.status + .tooltip .tooltip-inner')
             .text(title);
    }
    // Get called again just after when anything displayed to the user might update.
    updateStatusTimer = setTimeout(updateStatus, msToNextPossibleChange + 1);
  }
  updateStatus();
  window.addEventListener('visibilitychange', updateStatus);
});


text:
Clear Feedback

      Post

      <%= render "posts/post", post: @post %>

      Feedback
      User
      Timestamp

    <% @feedbacks.each do |feedback| %>
      <tr class="<%= feedback.is_invalidated? ? "danger" : "" %>">
        <td class="<%= element_class_for_feedback feedback %>"><%= feedback.feedback_type %>
        <%= feedback.user_name || ("#{feedback.user.try(:username)} (From #{feedback.api_key.try(:app_name) || ("Chat" if feedback.chat_user_id.present?) || "Review"})" if feedback.user.present?) %>
        <span title="<%= feedback.created_at %>"><%= feedback.created_at.present? ? time_ago_in_words(feedback.created_at) + " ago" : "no data" %>
        <%= link_to("delete", delete_feedback_path(feedback), method: :delete) if (!feedback.is_invalidated? && (current_user.has_role?(:admin) || feedback.user_id == current_user.id)) %>

    <% end %>


text:
json.post_link feedback.post.link
json.feedback_type feedback.feedback_type
json.user_name feedback.user_name || feedback.user&.username
json.app_name feedback.api_key&.app_name
json.symbol element_symbol_for_feedback(feedback)


text:
      <%= link_to signup_auth_url, class: "btn btn-default se-oauth-button" do %>
          Sign up with
          <%= image_tag "se-icon.svg", class: "se-icon" %>Stack Exchange
      <% end %>

or

      <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
          <% if resource.errors.any? %>
<%= devise_error_messages! %>
<% end %>

            <%= f.label :email %>

            <%= f.email_field :email, autofocus: true, class: "form-control" %>

            <%= f.label :username %>

            <%= f.text_field :username, class: "form-control" %>

            <%= f.label :password %>
            <% if @minimum_password_length %>
                (<%= @minimum_password_length %> characters minimum)
            <% end %>

            <%= f.password_field :password, autocomplete: "off", class: "form-control" %>

            <%= f.label :password_confirmation %>

            <%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %>

            <%= f.check_box :eu_resident %>
            <%= f.label :eu_resident, 'I am an EU resident' %>

            <%= f.check_box :privacy_accepted %>
            <%= f.label :privacy_accepted do %>
                I have read and accept the <%= link_to 'Privacy Statement', data_privacy_path %>
            <% end %>

            <%= f.submit "Sign up", class: "btn btn-primary" %>

            <%= render "devise/shared/links" %>

      <% end %>


text:
Account Settings

        <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
          <% if resource.errors.any? %>
<%= devise_error_messages! %>
<% end %>

            <%= f.label :username %>

            <%= f.text_field :username, class: 'form-control' %>

            <%= f.label :email %>

            <%= f.email_field :email, class: 'form-control' %>

          <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>

Currently waiting confirmation for: <%= resource.unconfirmed_email %>

          <% end %>

            <%= f.label :password %> (leave blank if you don't want to change it)

            <%= f.password_field :password, autocomplete: "off", class: 'form-control' %>

            <%= f.label :password_confirmation %>

            <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control' %>

            <%= f.label :current_password, class: 'control-label' %>

            <%= f.password_field :current_password, autocomplete: "off", class: 'form-control' %>
            We need your current password to confirm your changes.

            <%= f.submit "Update", class: 'btn btn-primary' %>

        <% end %>

        <% if /\d+@se-oauth\.metasmoke/.match?(current_user.email) %>

No password? No problem!

          <%= form_tag url_for(controller: '/users', action: :update_email) do %>

              <%= label_tag :email %>

              <%= email_field_tag :email, nil, class: 'form-control' %>

              <%= submit_tag 'Save', class: 'btn btn-success' %>

          <% end %>
        <% end %>

Two-factor authentication

<%= link_to 'Configure two-factor authentication', url_for(controller: '/users', action: :tf_status) %>

Announcement emails

        <%= form_tag url_for(controller: '/users', action: :set_announcement_emails) do %>

            <%= check_box_tag :emails, nil, current_user.announcement_emails %> <%= label_tag :emails, "Send me emails about new announcements" %>

            <%= submit_tag "Save", class: 'btn btn-success' %>

        <% end %>

Stack Overflow Channel email

        <%= link_to 'Get sign up information for Stack Overflow Channels', channels_email_path, class: 'btn btn-primary' %>

Cancel my account

          If you'd like to delete your metasmoke account, ping Undo in <%= link_to 'Charcoal HQ', 'https://chat.stackexchange.com/rooms/11540' %>.
          Deleting an account has been known to cause errors before, so deletions are done manually so that this can be monitored.

<%= link_to 'Back', :back %>


text:
<% if @eligible_users.present? %>

Eligible Users

If this post were made now, these users would be eligible to flag it

      Core

      <% @eligible_users.with_role(:core).each do |user| %>

          <%= user.username %>

      <% end %>

        Everyone else

        <% @eligible_users.without_role(:core).each do |user| %>

            <%= user.username %>

        <% end %>

<% end %>


text:
<% with_app ||= false %>
<% with_comment ||= false %>
<% without_post ||= false %>
<% with_timestamp ||= false %>
<% with_auto_manual ||= false %>
<% with_condition ||= false %>
<% with_type ||= false %>

    <%# As for why we need render_source, see metasmoke#510 - Devise doesn't like being rendered from a controller %>
    <% if (!defined?(render_source) || render_source != :controller) && current_user&.has_role?(:admin) && log.user.present? %>
      <%= link_to log.user.try(:username), user_overview_path(user: log.user.try(:id)) %>
    <% else %>
      <%= log.user.try(:username) %>
    <% end %>

  <% if with_auto_manual %>
    <%= log.is_auto ? "auto" : "manual" %>
  <% end %>
  <% if with_timestamp %>

      <%= log.created_at %>

  <% end %>
  <% unless without_post %>
    <%= render "posts/post", post: log.post, wrap_in_tr: false %>
  <% end %>
  <% if with_app %>

        <% unless log.is_auto %>
          <%= log.api_key.present? ? log.api_key.app_name : 'MS Web UI' %>
        <% end %>

  <% end %>
  <% if with_condition %>
    <%= log.flag_condition_id %>
  <% end %>
  <% if with_type %>
    <%= log.flag_type %>
  <% end %>
  <% if with_comment %>
    <%= log.comment %>
  <% end %>
  <td class='<%= "success" if log.success %>' title="<%= log.error_message %>"><%= log.success ? "Success" : "Fail" %><%= " (dry run)" if log.is_dry_run %>


text: $("#eligible_flaggers").html("<%= escape_javascript(render partial: 'eligible_flaggers') %>");


text:
Unflagged posts

Solely posts which have not been automatically flagged.

      Post

    <% @posts.each do |post| %>
        <%= render 'posts/post', post: post %>
    <% end %>

  <%= will_paginate @posts, renderer: BootstrapPagination::Rails %>


text:
json.post_link flag_log.post.link
json.user_name flag_log.user.username
json.user do
  json.user_name flag_log.user.username
  json.stackexchange_chat_id flag_log.user.stackexchange_chat_id
  json.meta_stackexchange_chat_id flag_log.user.meta_stackexchange_chat_id
  json.stackoverflow_chat_id flag_log.user.stackoverflow_chat_id
end

json.post do
  json.partial! 'posts/post', post: flag_log.post
end


text:
  <%= title "Flag logs" %>
  <% if @individual_post.present? %>
    for <bdi><%= link_to @individual_post.title, "/post/#{@individual_post.id}" %></bdi>
  <% end %>
  <% if @individual_user.present? %>
    for <%= @individual_user.username %>
  <% end %>

<% if @individual_post.nil? %>
  <%= line_chart [
        {name: 'Flags', data: @applicable_flag_logs.group_by_day("flag_logs.created_at", range: 1.month.ago.to_date..Time.now).count},
        {name: 'Posts', data: @applicable_flag_logs.select(Arel.sql('DISTINCT post_id')).group_by_day("flag_logs.created_at", range: 1.month.ago.to_date..Time.now).count}
      ], colors: ['#76ADE1', '#e0a976'],
      library: { "xAxis": { "plotLines" => flag_settings_plot_lines(1.month.ago)}} %>
<% end %>

<% if @individual_post.nil? %>

    <li class="<%= 'active' if params[:user_id].nil? and (params[:filter].nil? or params[:filter] == 'all') %>"><%= link_to "All (#{FlagLog.where(is_auto: true, success: true).count})", flag_logs_path %>

    <% if current_user %>
      <li class="<%= 'active' if params[:user_id].to_i == current_user.id %>"><%= link_to "Mine (#{FlagLog.where(is_auto: true, success: true, user: current_user).count})", flag_logs_by_user_path(user_id: current_user.id) %>

    <% end %>
    <li class="<%= 'active' if params[:filter] == 'fps' %>"><%= link_to "False Positives", flag_logs_path(filter: 'fps') %>

    <li class="<%= 'active' if params[:filter] == 'failures' %>"><%= link_to 'Failures', flag_logs_path(filter: 'failures') %>

    <li class="<%= 'active' if params[:filter] == 'manual' %>"><%= link_to "Manual", flag_logs_path(filter: 'manual') %>

    <li class="<%= 'active' if params[:filter] == 'other' %>"><%= link_to "Other Auto", flag_logs_path(filter: 'other') %>

<% end %>

<table class="table" id="<%= 'all-flag-logs' if params[:user_id].nil? and (params[:filter].nil? or params[:filter] == 'all') %>">
  <% if @individual_post.present? %>

        User
        <% if @individual_post.present? %>
          Auto /
Manual
          Timestamp
          App
          Condition
ID
          Type
        <% else %>
          Post
          <% if params[:filter] == 'manual' %>
            App
          <% end %>
          <% if params[:filter] == 'other' %>
            Comment
          <% end %>
        <% end %>
        Result

  <% end %>

    <% @flag_logs.each do |log| %>
      <% if @individual_post.present? %>
        <%= render 'flag_log', log: log, without_post: true, with_app: true, with_timestamp: true, with_auto_manual: true, with_condition: true, with_type: true %>
      <% else %>
        <%= render 'flag_log', log: log, with_app: params[:filter] == 'manual', with_comment: params[:filter] == 'other' %>
      <% end %>
    <% end %>

  <%= will_paginate @flag_logs, renderer: BootstrapPagination::Rails %>

<% if @individual_post.present? %>

    <%= link_to "Eligible Flaggers", post_eligible_flaggers_path(@individual_post), remote: true %>

<% end %>


text:
Verify code

Because your account has two-factor authentication enabled, you now need to enter the code shown in your mobile
  authenticator app in the field below.

<%= form_tag url_for(controller: :custom_sessions, action: :verify_code) do %>
  <%= hidden_field_tag 'uid', params[:uid] %>

    <%= label_tag 'code' %>

    <%= text_field_tag 'code', '', class: 'form-control' %>

    <%= submit_tag 'Verify', class: 'btn btn-success' %>

<% end %>


text:
  <div class="panel panel-default" id="<%= table_name %>">

      <%= table_name %>

      <% cols.each do |c| %>
        <input type="checkbox" /> <%= table_name %>.<%= c %>

      <% end %>


text:
json.items @results, partial: 'posts/post', as: :post

json.has_more @more


text:
[
  {
    "type": "tp",
    "description": "True positive"
  },
  {
    "type": "tp-",
    "description": "True positive (silent)"
  },
  {
    "type": "tpu",
    "description": "True positive, blacklist user"
  },
  {
    "type": "tpu-",
    "description": "True positive, blacklist user (silent)",
    "aliases": ["k"]
  },
  {
    "type": "k",
    "description": "True positive, blacklist user (silent)",
    "alias_for": "tpu-"
  },
  {
    "type": "fp",
    "description": "False positive"
  },
  {
    "type": "fp-",
    "description": "False positive (silent)",
    "aliases": ["f"]
  },
  {
    "type": "fpu",
    "description": "False positive, whitelist user"
  },
  {
    "type": "fpu-",
    "description": "False positive, whitelist user (silent)"
  },
  {
    "type": "f",
    "description": "False positive (silent)",
    "alias_for": "fp-"
  }<% if @post.answer? %>,
  {
    "type": "naa",
    "description": "Not an answer"
  },
  {
    "type": "naa-",
    "description": "Not an answer (silent)",
    "aliases": ["n"]
  },
  {
    "type": "n",
    "description": "Not an answer (silent)",
    "alias_for": "naa-"
  }
  <% end %>
]


text:
json.items @users, partial: 'users/user', as: :user
json.has_more @has_more


text:
<?xml version="1.0" encoding="utf-8"?>

<rss xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="2.0">
  <channel>
    <title>MS posts | Charcoal</title>
    <link>https://metasmoke.erwaysoftware.com/search</link>
    <description>Posts matching search criteria from metasmoke.</description>
    <copyright>Charcoal</copyright>
    <lastBuildDate><%= posts.first.created_at.iso8601 %></lastBuildDate>
    <generator>Rails</generator>
    <managingEditor>smokey@charcoal-se.org</managingEditor>
    <webMaster>smokey@charcoal-se.org</webMaster>
    <meta xmlns="http://www.w3.org/1999/xhtml" name="robots" content="noindex" />
    <% posts.each do |p| %>
      <item>
        <title><%= p.title %>></title>
        <guid isPermaLink="true">https://metasmoke.erwaysoftware.com/post/<%= p.id %></guid>
        <link>https://metasmoke.erwaysoftware.com/post/<%= p.id %></link>
        <pubDate><%= p.created_at.iso8601 %></pubDate>
        <description><![CDATA[<%= p.body %>]]></description>
      </item>
    <% end %>
  </channel>
</rss>


text:
<?xml version="1.0" encoding="utf-8"?>

<feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text">MS posts | Charcoal</title>
  <link rel="self" href="https://metasmoke.erwaysoftware.com/api/v2.0/posts/search.atom" />
  <link rel="alternate" href="https://metasmoke.erwaysoftware.com/search" />
  <subtitle>Posts matching search criteria from metasmoke.</subtitle>
  <updated><%= posts.first.created_at.iso8601 %></updated>
  <id>https://metasmoke.erwaysoftware.com/api/v2.0/posts/search.atom</id>

  <% posts.each do |p| %>
    <entry>
      <id>https://metasmoke.erwaysoftware.com/post/<%= p.id %></id>
      <title type="text"><%= p.title %></title>
      <author>
        <name><%= p.stack_exchange_user&.username %></name>
        <uri><%= p.site.site_url %>/users/<%= p.stack_exchange_user&.user_id %></uri>
      </author>
      <link rel="alternate" href="https://metasmoke.erwaysoftware.com/post/<%= p.id %>" />
      <published><%= p.created_at.iso8601 %></published>
      <updated><%= p.updated_at.iso8601 %></updated>
      <summary type="html"><%= p.body.gsub('<', '<').gsub('>', '>') %></summary>
    </entry>
  <% end %>
</feed>


text:
API Filter Generator

API filters are based on some fairly convoluted logic. If you understand it fully, it's possible to generate
  your own filters on the fly; however, I'd advise using this page instead.

Select only the fields you need; if you select a field from a totally unrelated table and it breaks when you
  use it, you have only yourself to blame. If in doubt about which tables are available for particular routes, ask.

<%= filter_generator %>

  <button type="button" class="btn btn-primary" id="create_filter">Create Filter</button>


text:
This is a blank page.

There should be no queries associated with rendering it.


text:
Websocket Test

This lets you broadcast arbitrary content across the websockets. If you abuse this, I will find you.

<%= form_tag url_for(controller: :developer, action: :send_websocket_test), method: :post do %>

    <%= label_tag 'channel' %>

    <%= text_field_tag 'channel', '', class: 'form-control' %>

    <%= label_tag 'content' %>

    <%= text_field_tag 'content', '', class: 'form-control', placeholder: 'JSON format' %>

    <%= submit_tag 'Broadcast', class: 'btn btn-primary' %>

<% end %>


text:
Developer: production.log

Take care with the contents of this file - it may contain system or user details.

<%= link_to 'Download query times log', dev_query_times_log_path %>

<%= form_tag dev_prod_log_search_path, class: 'form-inline' do %>
  Search:
  <input type="text" name="grep" id="grep" class="form-control input-sm" />
  Context lines:
  <input type="number" name="context" id="context" class="form-control input-sm" />
  <input type="submit" value="Search" class="btn btn-primary btn-sm" />
<% end %>

<%= @log %>


text:
Stop impersonating

You (<%= @impersonator.username %>) are currently impersonating <%= current_user.username %>. To stop impersonating them, verify your password
  below:

<%= link_to login_auth_url, class: "btn btn-default se-oauth-button" do %>
  Login with
  <%= image_tag "se-icon.svg", class: "se-icon" %>Stack Exchange
<% end %>

or

<%= form_tag stop_impersonating_path, class: 'form-horizontal' do %>

    <%= label_tag :password, 'Password', class: 'col-sm-1 col-form-label' %>

      <%= password_field_tag :password, '', class: 'form-control' %>

    <%= submit_tag 'Verify & Stop Impersonating', class: 'btn btn-primary' %>
    <input type="reset" class="btn btn-danger" value="Cancel" />

<% end %>


text: This is a page with only the layout.


text:
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>

Admin report

Something wrong with this record? Describe the issue here to notify admins of the problem so that they can resolve it.

        <%= form_for Flag.new, url: admin_flag_post_path(post), remote: true do |f| %>

            <%= f.label :reason, 'Why does this post need admin attention?' %>

            <%= f.text_area :reason, rows: 3, cols: 80, class: 'form-control', autofocus: true %>

              <button type="submit" class="btn btn-warning"> Report</button>

        <% end %>


text:
  <%= render 'review_queues/responses', queue: queue, item: item %>

    Reason:
    <%= item.reviewable.reason %>
    <% if item.reviewable.user.present? %>
        — <%= item.reviewable.user.username %>
    <% end %>

  <%= render 'posts/post', post: item.reviewable.post, expand_post: true, preload_post_body: true, show_autoflagged: true, show_autoflaggers: true %>


text:
<!DOCTYPE html>

    <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />

Smokey is down

      FYI :)

The most recent ping was from <%= @location %>, <%= time_ago_in_words(@last_ping_date) %> ago.


text:
<%= form_tag graphql_path, method: :post do %>

    <%= label_tag :query, "Query GraphQL:" %>
    <%= text_area_tag :query, '', class: 'form-control', placeholder: "Try out the new GraphQL interface!" %>

  <%= submit_tag "Query!", class: 'btn btn-primary' %>
<% end %>

Here are some example queries:

Introspection:

<%= query = "{
  __type(name: \"Post\") {
    kind
    fields {
      name
      type {
        name
        kind
        ofType {
          name
          kind
        }
      }
    }
  }
}" %>

<%= JSON.pretty_generate(MetasmokeSchema.execute(query).to_hash) %>

<link rel="stylesheet"
      href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css">


text:
And the results are...

<%= @results %>

<link rel="stylesheet"
      href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css">


text:
Dumps

      File

    <% @dumps.each do |dump| %>

        <%= link_to dump[:name], dump[:url] %>

    <% end %>

Redis dumps

      File

    <% @redis_dumps&.each do |dump| %>

        <%= link_to dump[:name], dump[:url] %>

    <% end %>


text:
<% title "Posts for #{@user.username}" %>

<%= link_to @user.stack_link do %>
  <%= image_tag @user.flair_link, alt: @user.username, class: "stack_exchange_user_flair",
                data: { username: @user.username, reputation: @user.reputation || '-', site_logo: @user.site_logo } %>
<% end %>

    Posts

  <% @posts.each do |post| %>
    <%= render 'posts/post', post: post %>
  <% end %>

<%= will_paginate @posts, renderer: BootstrapPagination::Rails %>


text:
Sites

All the sites on the Stack Exchange network should be listed here. To find spammers on your site, search for and select it
  from the dropdown below, and hit View.

<%= form_tag url_for(controller: :stack_exchange_users, action: :on_site), method: :get do %>
  <%= select_tag 'site', options_for_select(@sites.map{|s| [s.site_name, s.id]}), {class: 'form-control selectpicker', 'data-live-search' => true, 'data-actions-box' => true, 'data-dropup-auto' => false} %>

  <%= submit_tag 'View', class: 'btn btn-primary' %>
<% end %>


text:
<% post_counts = false if post_counts.nil? %>

      MS ID
      Site ID
      <% if post_counts %>
        Post Count
      <% end %>
      Reputation
      <% if user_signed_in? && current_user.moderator_sites.exists? %>
        Handled
      <% end %>

    <% users.each do |u| %>

        <%= link_to u.id, url_for(controller: :stack_exchange_users, action: :show, id: u.id) %>
        <%= link_to "#{u.user_id} (#{u.username})", "#{@site.site_url}/users/#{u.user_id}" %>
        <% if post_counts %>
          <%= u.posts.count %>
        <% end %>
        <%= u.reputation %>
        <% if user_signed_in? && current_user.moderator_sites.exists? %>
          <a href="#" class="not-spammer" data-uid="<%= u.id %>">Remove from list
        <% end %>

    <% end %>

  <%= will_paginate users, renderer: BootstrapPagination::Rails %>


text:
Users

Users with tp posts on SO

    User
    Last tp post

  <% @users.each do |user| %>

      <bdi><%= link_to user.username, "//#{user.site.site_domain}/users/#{user.user_id}" %></bdi>
      <bdi><%= link_to user.posts.first.title, "/post/#{user.posts.first.id}" %></bdi>

  <% end %>


text:
Spammers on site: <%= @site.site_name %>

An overview of users who have been caught spamming by Smokey on this site. This list exists effectively as a "to do" list for moderators.
The intent is that a site moderator will go through this list and appropriately handle each user. What "appropraitely handle" means is
up to the moderator, but could be anywhere from nothing, through a moderator message, to a 365 day suspension and destroying the user. From
metasmoke's point of view, this is just a list of non-deleted users who have had a post marked TP and who a moderator hasn't told metasmoke
to remove the user from the list (i.e. mark the user as "handled").

<% if user_signed_in? && current_user.moderator_sites.exists? %>

Click Remove from list to tell the system that the user has been reviewed and the appropriate action taken.
    The user will then be removed from this list.

<% else %>

Users are removed from this list when a Stack Exchange moderator indicates from this page that the user has been reviewed
    and the appropriate action taken.

<% end %>

Data last updated: <%= @site.last_users_update.present? ? time_ago_in_words(@site.last_users_update) + ' ago' : "Never" %>
  — <%= link_to "Update now to automatically remove deleted users", url_for(controller: :stack_exchange_users, action: :update_data, site: @site.id), method: :post %>

<%= render 'table', users: @users %>


text:
RedisLog#status

Find me in app/views/redis_log/status.html.erb


text:
RedisLog#user

Find me in app/views/redis_log/user.html.erb


text:
Session: <%= @session_id %>

<%= render 'table', requests: @requests %>


text:
<% status_color = case req['status'].to_s when /(2\d{2}|API)/ then 'success' when /4\d{2}/ then 'warning' when /3\d{2}/ then 'info' when /5\d{2}/ then 'danger' else 'danger' end %>
<span class="text-<%= status_color %>" href="#<%= req[:key] %>">
  <%= render 'redis_log/user', impersonator_id: req['impersonator_id'], user_id: req['user_id'], session_id: req['session_id'], smoke_detector_id: req['smoke_detector_id'], api_key_id: req['api_key_id'] %>
  <%= req['status'].nil? ? 'N/A' : link_to(req['status'], redis_log_by_status_path(status: req['status']), style: 'display: inline;', class: 'redis-log-link') %> <%= req['method'] %> <%= req['path'] %>

      <%= Time.at(req[:timestamp]).strftime('%Y-%m-%d %H:%M:%S') %>
     ·

        <% if req['db_runtime'].nil? %>
           unknown
        <% else %>
          <%= number_with_precision(req['db_runtime'].to_f, precision: 3).rjust(7, "\u00A0") %>ms
        <% end %>

      ·

        <% if req['status'] == 'API' %>
             N/A
        <% elsif req['view_runtime'].nil? %>
           unknown
        <% else %>
          <%= number_with_precision(req['view_runtime'].to_f, precision: 3).rjust(7, "\u00A0") %>ms
        <% end %>


text:
<% wrapped ||= false %>

<% if wrapped %><div data-log-id="<%= req[:key] %>"><% end %>
<li class="list-group-item list-item-label" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="<%= req[:key] %>" href="#<%= req[:key] %>">
  <%= render 'redis_log/header', req: req %>

<div class="collapse" style="margin-bottom:-1px" id="<%= req[:key] %>">

      <%= req['session_id'].nil? ? "No Session" : link_to("session", redis_log_by_session_path(id: req['session_id'])) %>
      ·
      <%= req['user_id'].nil? ? "No User" : link_to("user", redis_log_by_user_path(id: req['user_id'])) %>
      ·
      <%= link_to("request", redis_log_request_path(req.slice(:request_id, :timestamp))) %>
      ·
      <%= %w[method path format].all? { |i| !(req[i].nil? || req[i].empty?) } ? link_to("path", redis_log_by_path_path(method: req['method'], format: req['format'], path: (Rails.sensible_routes.match_for(req['path'])&.path || req['path'].split('?').first))) : 'No Path' %>

<%= req['controller'] %>#<%= req['action'] %> (<%= req['path'] %>.<%= req['format'] %>)

    <% if !req[:logs].nil? %>

Logs:

    <%= raw(req[:logs].map { |l| sanitize(l) }.join("
")) %>
    <% end %>
    <% if !req[:exception].empty? %>

<%= req['status'] %> Exception:

<%= req['exception'] %>

    <%= render 'redis_log/param_table', params: req[:exception] %>
    <% end %>
    <% ["Params", "Request Headers", "Response Headers"].each do |key| %>

<%= key %>

      <% if req[key.downcase.gsub(' ', '_').to_sym].nil? || req[key.downcase.gsub(' ', '_').to_sym].empty? %>
        (empty)
      <% else %>
        <%= render 'redis_log/param_table', heading: key, params: req[key.downcase.gsub(' ', '_').to_sym] %>
      <% end %>
    <% end %>

        Request ID
        <%= req[:request_id] %>

        Commit SHA
        <%= req['sha'] %>

<% if wrapped %>
<% end %>


text:
<% if redis(logger:true).sismember("saved_requests", "#{@request[:timestamp]}/#{@request[:request_id]}") %>
  <%= link_to "Unsave", redis_log_unsave_request_path(@request.slice(:timestamp, :request_id)) %>
<% else %>
  <%= link_to "Save", redis_log_save_request_path(@request.slice(:timestamp, :request_id)) %>
<% end %>

<%= render 'row', req: @request %>


text:
<%= @method %> <%= @path %>.<%= @format %>

<%= render 'table', requests: @requests %>


text:
<% impersonator_id ||= nil %>
<% smoke_detector_id ||= nil %>
<% api_key_id ||= nil %>

<% if smoke_detector_id.present? && SmokeDetector.exists?(smoke_detector_id) %>
  [SD <%= SmokeDetector.find(smoke_detector_id).location %>]
<% elsif api_key_id.present? && APIKey.exists?(api_key_id) %>
  [API <%= APIKey.find(api_key_id).app_name %>]
<% elsif impersonator_id.present? && User.exists?(impersonator_id) && User.exists?(user_id) %>
  [<%= User.find(impersonator_id).username %> (impersonating <%= User.find(user_id).username %>)]
<% elsif user_id.present? && User.exists?(user_id) %>
  [<%= User.find(user_id).username %>]
<% elsif session_id.nil? %>
  [NOSESS]
<% else %>
  <img src="https://robohash.org/<%= session_id %>?size=20x20">
<% end %>


text:
Status: <%= @status %>

<%= render 'table', requests: @requests %>


text:
  <% params.each do |k, v| %>

     <%= k %>

       <% if v.include?("\n") %>

<%= raw(sanitize(v).gsub("\n", "
")) %>

        <% elsif v.nil? %>
          nil
        <% else %>
          <%= v %>
        <% end %>

  <% end %>


text:
  <% requests.each do |req| %>
    <%= render 'row', req: req, wrapped: true %>
  <% end %>


text:
Realtime Requests

<%= render 'table', requests: @requests %>


text:
  <% @sessions.each do |session| %>
    <% css_id = session[:id].gsub(/[^a-zA-Z0-9\-\_]/, '') %>
    <li class="list-group-item list-item-label" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="<%= css_id %>" href="#<%= css_id %>">
      <%= session[:count] %>
      <%= render 'redis_log/user', session_id: session[:id], user_id: session[:user_id] %>
      <%= Time.at(session['start'].to_i).iso8601 %> - <%= Time.at(session['end'].to_i).strftime('%Y-%m-%d %H:%M:%S') %>

    <div class="collapse" style="margin-bottom:-1px" id="<%= css_id %>">

        <% session[:requests].each do |req| %>
          <%= render 'redis_log/row', req: req %>
        <% end %>

  <% end %>


text:
# frozen_string_literal: true

xml.instruct! :xml, version: '1.0'
xml.rss version: '2.0' do
  xml.channel do
    xml.title 'Autoflagged Posts'
    xml.description 'Posts which have been autoflagged by Charcoal HQ'
    xml.link root_url
    # category
    xml.copyright 'Copyright 2018 CharcoalHQ'
    # docs
    xml.language 'en-us'
    xml.lastBuildDate DateTime.now.strftime('%a, %-d %b %Y %T %z')
    xml.managingEditor 'smokey@charcoal-se.org'
    xml.pubDate DateTime.now.strftime('%a, %-d %b %Y %T %z')
    xml.webMaster 'smokey@charcoal-se.org'
    xml.generator 'Ruby on Rails XML generator'

    xml.image do
      xml.url 'https://charcoal-se.org/assets/images/charcoal.png'
      xml.title 'Autoflagged Posts'
      xml.link root_url
      xml.description 'Posts which have been autoflagged by Charcoal HQ'
      xml.width 516
      xml.height 516
    end

    @results.each do |post|
      xml.item do
        xml.title post.title
        xml.description post.body
        xml.link url_for(controller: 'posts', action: 'show', id: post.id, only_path: false)
        # category
        # comments
        xml.pubDate post.created_at.strftime('%a, %-d %b %Y %T %z')
      end
    end
  end
end


text:
<% title "Search" %>

<%= form_tag (params[:option] == 'graphs' ? search_path(anchor: "graphs") : search_path), method: "get" do |f| %>

    <%= label_tag :title %>
    <% if current_user&.can_use_regex_search? %>
      <%= check_box_tag :title_is_regex, 1, params[:title_is_regex] %> regex
      <%= check_box_tag :title_is_inverse_regex, 1, params[:title_is_inverse_regex] %> invert
    <% end %>
    <%= text_field_tag :title, params[:title], placeholder: "*", class: "form-control" %>

    <%= label_tag :body %>
    <%= check_box_tag :body_is_like, 1, params[:body_is_like] %>

      LIKE (?)

    <% if current_user&.can_use_regex_search? %>
      <%= check_box_tag :body_is_regex, 1, params[:body_is_regex] %> regex
      <%= check_box_tag :body_is_inverse_regex, 1, params[:body_is_inverse_regex] %> invert
    <% end %>
    <%= text_field_tag :body, params[:body], placeholder: "*", class: "form-control" %>

    <%= label_tag :username %>
    <% if current_user&.can_use_regex_search? %>
      <%= check_box_tag :username_is_regex, 1, params[:username_is_regex] %> regex
      <%= check_box_tag :username_is_inverse_regex, 1, params[:username_is_inverse_regex] %> invert
    <% end %>
    <%= text_field_tag :username, params[:username], placeholder: "*", class: "form-control" %>

    <%= label_tag :why %>
    <% if current_user&.can_use_regex_search? %>
      <%= check_box_tag :why_is_regex, 1, params[:why_is_regex] %> regex
      <%= check_box_tag :why_is_inverse_regex, 1, params[:why_is_inverse_regex] %> invert
    <% end %>
    <%= text_field_tag :why, params[:why], placeholder: "*", class: "form-control" %>

    <%= label_tag :or_search, "Use OR for text search (uses AND by default)" %>
    <%= check_box_tag :or_search, 1, params[:or_search] %>

    <% h = HTMLEntities.new %>
    <%= label_tag :site %>
    <%= select_tag :site, options_for_select(Site.select(:site_name, :id).all.order(:site_name).map {|s| [h.decode(s.site_name), s.id] }, params[:site]), class: "form-control", include_blank: "All" %>

    <%= label_tag :post_type %>
    <%= check_box_tag :post_type_include_unmatched, 1, params[:post_type_include_unmatched] %> include unmatched
    <%= select_tag :post_type, options_for_select(%i[question answer], params[:post_type]), class: "form-control", include_blank: "All" %>

    <%= label_tag :feedback %>
    <%= check_box_tag :has_no_feedback, 1, params[:has_no_feedback] %> has no feedback?
    <%= select_tag :feedback, options_for_select(["true positive", "false positive", "NAA", "conflicted"], params[:feedback]), class: "form-control", include_blank: "All" %>

    <%= label_tag :autoflagged %>
    <%= select_tag :autoflagged, options_for_select(["Yes", "No"], params[:autoflagged]), class: "form-control", include_blank: "All" %>

    <%= label_tag :reason %>
    <%= select_tag :reason, options_from_collection_for_select(Reason.all.order(:reason_name), "id", "reason_name", params[:reason]), class: "form-control", include_blank: "All" %>

    <%= label_tag :user_reputation %>

        <%= select_tag :user_rep_direction, options_for_select(%w[== <= >=], params[:user_rep_direction]), include_blank: true,
                       class: "form-control" %>

        <%= number_field_tag :user_reputation, params[:user_reputation], class: "form-control" %>

    <%= label_tag :edited, "Edited" %>
    <%= check_box_tag :edited, 1, params[:edited] %>

  <%= hidden_field_tag :option, params[:option] if params[:option] == 'graphs' %>

  <%= submit_tag "Search", class: "btn btn-primary" %>
<% end %>

<% if @results.present? %>

    <li role="presentation" class="nav-tab nav-tab-all-results <%= "active" if params[:option].nil? and (params[:feedback_filter].nil? or params[:feedback_filter] == 'all') %>">
      <%= link_to "Results (#{@total_count})", search_path(request.query_parameters.merge!({option: nil, feedback_filter: nil})) %>

    <li class="nav-tab nav-tab-tp <%= 'active' if params[:feedback_filter] == 'tp' %>">
      <%= link_to "True positives (#{@counts_by_feedback[:is_tp]})", search_path(request.query_parameters.to_h.merge!({option: nil, feedback_filter: "tp"})) %>

    <li class="nav-tab nav-tab-fp <%= 'active' if params[:feedback_filter] == 'fp' %>">
      <%= link_to "False positives (#{@counts_by_feedback[:is_fp]})", search_path(request.query_parameters.to_h.merge!({option: nil, feedback_filter: "fp"})) %>

    <li class="nav-tab nav-tab-naa <%= 'active' if params[:feedback_filter] == 'naa' %>">
      <%= link_to "NAA (#{@counts_by_feedback[:is_naa]})", search_path(request.query_parameters.to_h.merge!({option: nil, feedback_filter: "naa"})) %>

    <li role="presentation" class="nav-tab nav-tab-graphs <%= "active" if params[:option] == "graphs" %>">
      <%= link_to "Graphs", search_path(request.query_parameters.to_h.merge({option: "graphs", anchor: "graphs"})) %>

  <% if params[:option].nil? %>

      <%= render @results, collection: :post %>

      <%= will_paginate @results, renderer: BootstrapPagination::Rails %>

        <%= link_to "JSON (still subject to paging limits)", search_path(params: request.query_parameters, format: :json) %>

  <% else %>

      <% h = HTMLEntities.new %>
      <%= pie_chart @results.group(:site).count.map{ |k,v| {(k.nil? ? "Unknown" : h.decode(k.site_name))=>v} }.reduce(:merge).select{|k,v| k != "Unknown"}.sort_by {|k,v| v}.reverse %>

      <%= line_chart [{name: "True positives", data: @results.where(is_tp: true).group_by_day('`posts`.`created_at`', range: 1.month.ago.to_date..Time.now).count}, {name: "False positives", data: @results.where(is_fp: true).group_by_day('`posts`.`created_at`', range: 1.month.ago.to_date..Time.now).count}], colors: ["green", "red"], library: {"tooltip" => {'shared' => 'true'} } %>

  <% end %>
<% end %>

<% if user_signed_in? && current_user.has_role?(:developer) %>

Developer

<%= link_to "Update site cache", url_for(controller: :developer, action: :update_sites), method: :post,
                data: { confirm: "You're a developer, so I should be assuming you know what you're doing, but: are you sure?" } %>

<% end %>


text:
Please set a username:

<%= form_tag users_username_path, method: :post do %>

    <%= label_tag :username %>

    <%= text_field_tag :username, nil, placeholder: "Your SE username", class: "form-control", autofocus: true %>

  <%= submit_tag "Save", class: "btn btn-success" %>
<% end %>


text:
Confirm your code

Last step: to confirm that you're all correctly set up, enter the code shown in your mobile authenticator below.

<%= form_tag url_for(controller: :users, action: :confirm_enable_code) do %>

    <%= label_tag 'code' %>

    <%= text_field_tag 'code', '', class: 'form-control' %>

    <%= submit_tag 'Confirm', class: 'btn btn-success' %>

<% end %>


text:
Check Your Privilege

The page you're trying to visit requires more privileges. You need to have the <%= @role&.name&.humanize&.titleize || params[:required] %>
  role to view this page.

<% if user_signed_in? %>

You currently have the following roles: <%= current_user.roles.map(&:name).map { |x| x.humanize.titleize }.to_sentence %>.

<% end %>

If you think this is an error, tell an admin.


text:
json.merge! user.as_json
json.username CGI::unescapeHTML(user.username) unless user.username.nil?
json.merge!(moderator_sites: user.moderator_sites.map { |ms| ms.site.as_json })


text:
<%= form_tag migrate_token_path do %>
  <%= hidden_field_tag "state", params[:state] %>
  <%= submit_tag "Confirm that you've granted access", class: 'btn btn-primary' %>
<% end %>


text:
User: <%= @user.username %>

Created: <span title="<%= @user.created_at %>"><%= time_ago_in_words @user.created_at %> ago

SE Auth

          Read access (auth-only): <%= @user.stack_exchange_account_id.present? ? 'Yes' : 'No' %>

          Write access (auth+flags): <%= @user.write_authenticated ? 'Yes' : 'No' %>

          Account ID: <%= link_to @user.stack_exchange_account_id, "https://stackexchange.com/users/#{@user.stack_exchange_account_id}" %>

          SE chat ID: <%= link_to @user.stackexchange_chat_id, "https://chat.stackexchange.com/users/#{@user.stackexchange_chat_id}" %>

          MSE chat ID: <%= link_to @user.meta_stackexchange_chat_id, "https://chat.meta.stackexchange.com/users/#{@user.meta_stackexchange_chat_id}" %>

          SO chat ID: <%= link_to @user.stackoverflow_chat_id, "https://chat.stackoverflow.com/users/#{@user.stackoverflow_chat_id}" %>

          users.yml:<%= @user.stack_exchange_account_id.present? ? @user.stack_exchange_account_id : 'null' %>:
          [<%= @user.stack_exchange_account_id.present? ? @user.stack_exchange_account_id : 'null' %>,
          <%= @user.stackexchange_chat_id.present? ? @user.stackexchange_chat_id : 'null' %>,
          <%= @user.meta_stackexchange_chat_id.present? ? @user.meta_stackexchange_chat_id : 'null' %>,
          <%= @user.stackoverflow_chat_id.present? ? @user.stackoverflow_chat_id : 'null' %>]  # <%= @user.username %>

          rooms.yml:      - <%= @user.stack_exchange_account_id.present? ? @user.stack_exchange_account_id : 'null' %> # <%= @user.username %>

        <% if current_user&.has_role?(:developer) %>

            <%= link_to 'Refresh chat IDs', update_user_chat_ids_path(@user), method: :post, class: 'btn btn-info' %>

        <% end %>

Flagging

          Enabled: <%= @user.flags_enabled? ? 'Yes' : 'No' %>

          <%= link_to 'Flagger Overview', user_overview_path(user: @user.id) %>

Authentication

          Email: <%= @user.email =~ /(metasmoke\.fake|se-oauth\.metasmoke)/ ? 'No' : 'Yes' %>

          SE: <%= @user.stack_exchange_account_id.present? ? 'Yes' : 'No' %>

          2FA: <%= @user.enabled_2fa? ? 'Yes' : 'No' %>

          <%= link_to 'Send password reset email', send_password_reset_path(@user), method: :post, class: 'btn btn-info' %>

Mod sites

        <% if @user.moderator_sites.any? %>

            <% @user.moderator_sites.each do |s| %>

<%= s.site.site_name %>

            <% end %>

        <% else %>

User is not a moderator.

        <% end %>
        <% if current_user&.has_role?(:developer) %>

            <%= link_to 'Refresh mod sites', update_mod_sites_path(@user), method: :post, class: 'btn btn-info' %>

        <% end %>

Misc

          Announcement emails:
          <% if @user.announcement_emails? && @user.email =~ /(metasmoke\.fake|se-oauth\.metasmoke)/ %>

               Enabled with issues

          <% elsif @user.announcement_emails? %>
            Yes
          <% else %>
            No
          <% end %>


text:
Add metasmoke to your authenticator

Your account has been set up with the necessary credentials to use 2FA. You now need to add your metasmoke account to
  your mobile authenticator - we recommend using Google Authenticator.

Using Google Authenticator, tap the floating red plus button in the lower-right corner, and tap "scan a barcode".
  Scan the QR code below, and the account should be added.

<img src="<%= @qr_uri %>" width="300" />

<%= link_to "Done, next step", url_for(controller: :users, action: :enable_code), class: "btn btn-success" %>


text:
Autoflagging Users

The following users have accepted a disclaimer and signed up for their accounts to be used for autoflagging. The minimum
  weight listed is the minimum total reason weight the user has set themselves; it must be more than <%= FlagSetting['min_accuracy'] %>%
  accurate, but the higher it is, the more accurate this user's automatic flags will be.
  (<%= link_to 'What does this mean?', 'https://charcoal-se.org/flagging' %>)

      User
      Min. weight

    <% @users.each do |user| %>

        <%= link_to user.username, "https://stackexchange.com/users/#{user.stack_exchange_account_id}" %>
        <%= user.flag_conditions.first&.min_weight %>
        <%= link_to 'Flags cast as this user', flag_logs_by_user_path(user_id: user.id) %>

    <% end %>

<%= will_paginate @users, renderer: BootstrapPagination::Rails %>


text:
Your Authorized Apps

Below is a list of apps that you have authorized to perform write operations on metasmoke on your behalf via the API.
  If you no longer want an app to be able to do this, click Revoke Access.

      App Name
      Last Authorized
      Active Tokens

    <% @keys.each do |key| %>

        <%= key.app_name %>
        <%= APIToken.where(user: current_user, api_key: key).last.created_at %>
        <%= APIToken.where(user: current_user, api_key: key).count %>
        <%= link_to "Revoke Access", url_for(controller: :users, action: :revoke_app, key_id: key.id), method: :delete,
                data: { confirm: "Are you sure? If you want to use this app again, you will have to re-authenticate." }, class: "text text-danger" %>

    <% end %>

Available Apps

This is a list of all apps that use the metasmoke API. Their authors may have provided a GitHub link for you to find
  out more about the app and decide if you want to use it.

      Name
      Author
      GitHub link

    <% APIKey.all.each do |key| %>

        <%= key.app_name %>

          <% if key.user %>
            <%= key.user.username %>
          <% else %>
            (None recorded)
          <% end %>

          <% if key.github_link.try(:strip).try(:length) || 0 > 0 %>
            <%= link_to "Repo", key.github_link %>
          <% end %>

    <% end %>

<%= link_to "Apps you own/develop", url_for(controller: :api_keys, action: :mine) %>


text:
      SE Username
      Flags Cast
      Accuracy

    <% users.each do |u| %>

        <%= link_to u.username, "https://stackexchange.com/users/#{u.stack_exchange_account_id}" %>
        <%= u.total_flags %>
        <%= u.tp_flags %> / <%= u.total_flags %> (<%= ((u.tp_flags.to_f / u.total_flags)*100).round(2) %>%)

    <% end %>

  <%= will_paginate users, renderer: BootstrapPagination::Rails %>


text:
Confirm your code

Last step: to confirm that it's really you trying to remove security from your account, enter the code shown in your
  authenticator app.

<%= form_tag url_for(controller: :users, action: :confirm_disable_code) do %>

    <%= label_tag 'code' %>

    <%= text_field_tag 'code', '', class: 'form-control' %>

    <%= submit_tag 'Confirm', class: 'btn btn-success' %>

<% end %>


text:
Two-factor authentication

Two-factor authentication adds another layer of security to your account by requiring you to get a code from an app
  on your phone before you can log in. This prevents anyone who doesn't have your phone with them getting into your account.

Two-factor authentication is currently <%= current_user.enabled_2fa ? "on" : "off" %> for your account.

<% if current_user.enabled_2fa %>

<%= link_to "Turn off", url_for(controller: :users, action: :disable_code) %>

<% else %>

<%= link_to "Turn on", url_for(controller: :users, action: :enable_2fa), method: :post %>

<% end %>


text:
<%= link_to contact.name, abuse_contact_path(contact) %>

 <%= contact.email %>

 <%= link_to contact.link, contact.link %>


text:
<%= form_for contact, url: url do |f| %>

    <%= f.label :name do %>
       Name *
    <% end %>

    <%= f.text_field :name, class: 'form-control' %>

    <%= f.label :email do %>
       Email
    <% end %>
    Optional

    <%= f.email_field :email, class: 'form-control' %>

    <%= f.label :link do %>
       Link
    <% end %>
    Optional

    <%= f.url_field :link, class: 'form-control' %>

    <%= f.label :details do %>
       Details
    <% end %>
    Markdown allowed

    <%= f.text_area :details, rows: 5, cols: 100, class: 'form-control' %>

    <%= f.submit 'Save Contact', class: 'btn btn-success' %>

<% end %>


text:
<%= @contact.name %>

 <%= @contact.email %>

 <%= link_to @contact.link, @contact.link %>

<% if current_user&.has_role?(:core) %>

    <%= link_to 'Edit', edit_abuse_contact_path(@contact) %>
    <% if current_user&.has_role?(:admin) %>
      ·
      <%= link_to 'Delete', destroy_abuse_contact_path(@contact), method: :delete, class: 'text-danger', data: { confirm: 'Are you sure?' } %>
    <% end %>

<% end %>

<%= safe_render_markdown(@contact.details.present? ? @contact.details : 'No contact details stored', scrubber: Announcement.scrubber) %>

Reports to this contact

<% @reports.in_groups_of(3).map(&:compact).each do |g| %>

    <% g.each do |r| %>
      <%= render 'abuse_reports/report', report: r %>
    <% end %>

<% end %>


text:
Edit Contact: <%= @contact.name %>

Use the form below to add or modify this contact's details.

<%= render 'form', contact: @contact, url: update_abuse_contact_path(@contact) %>


text:
Abuse Contacts

People or sites to contact to report abuse of their or our services.

<% @contacts.in_groups_of(4).map(&:compact).each do |g| %>

    <% g.each do |c| %>
      <%= render 'contact', contact: c %>
    <% end %>

<% end %>

<%= will_paginate @contacts, renderer: BootstrapPagination::Rails %>


text:
Authenticate with Stack Exchange

Associate your metasmoke account with your Stack Exchange account to provide feedback through the API.

<% if current_user.stack_exchange_account_id.nil? %>
  <%= link_to "Authenticate", identify_auth_url, class: "btn btn-success" %>
<% else %>

You have already authenticated. You do not need to do so again.

However, you can authenticate again (e.g. if you are experiencing problems).

  <% if current_user.has_role? :flagger %>

        Warning: Undo (this one) runs this service.
        Due to how the API operates, Undo could, theoretically, perform any action on your account. He won't, and if there's a known breach
        we'll revoke all our access tokens, but we're human. By clicking below, you explicitly acknowledge that you're signing up for the following:

a service run by dogs and humans,

who are all volunteers,

with a budget of about four dollars a month

      <%= link_to "Authenticate w/ Write Access", write_auth_url, class: "btn btn-danger" %>

  <% end %>
<% end %>


text:
Authentication#redirect_target

Find me in app/views/authentication/redirect_target.html.erb


text:
API Token Invalidation

Warning

    Beware - this tool removes and invalidates Stack Exchange API access tokens for any users you select. If you do this unnecessarily,
    many people will be annoyed, and we'll lose a bunch of flaggers. You have been warned.

<%= form_tag url_for(controller: :authentication, action: :send_invalidations), method: :post do %>

    <%= label_tag 'users' %>

    <%= select_tag 'users', options_for_select(@users.map{|u| [u.username, u.id]}), { multiple: true, class: "form-control selectpicker", 'data-live-search' => true, 'data-actions-box' => true, 'data-dropup-auto' => false, title: "Choose users to invalidate API tokens for" } %>

    <%= submit_tag 'Invalidate', class: 'btn btn-danger' %>

<% end %>


text:
<%= notice %>

Statistics for <%= @smoke_detector.location %>

  <li class="<%= 'active' if params[:range].nil? or params[:range] == 'hour' %>">
    <%= link_to "Hourly", smoke_detector_statistics_path(@smoke_detector, range: "hour") %>

  <li class="<%= 'active' if params[:range] == 'day' %>">
    <%= link_to "Daily", smoke_detector_statistics_path(@smoke_detector, range: "day") %>

<% if params[:range] == 'day' %>
    <%= line_chart [
          {name: 'Posts scanned', data: @smoke_detector.statistics.group_by_day(:created_at, range: 3.weeks.ago.to_date..Time.now).sum(:posts_scanned)},
        ], colors: ['#76ADE1'] %>
  <% else %>
    <%= line_chart [
          {name: 'Posts scanned', data: @smoke_detector.statistics.group_by_hour(:created_at, range: 3.days.ago.to_date..Time.now).sum(:posts_scanned)},
          {name: 'Quota remaining', data:  @smoke_detector.statistics.where(created_at: 3.days.ago.to_date..Time.now).where.not(api_quota: -1).select(:created_at, :api_quota).map {|s| [s.created_at, s.api_quota]}.to_h}
        ], colors: ['#76ADE1', '#e0a976'] %>
<% end %>

      Posts scanned
      Post scan rate
      API quota
      At

    <% @statistics.each do |statistic| %>

        <%= statistic.posts_scanned %>
        <%= statistic.post_scan_rate %>
        <%= statistic.api_quota %>
        <td title="<%= statistic.created_at %>"><%= time_ago_in_words statistic.created_at %> ago

    <% end %>

  <%= will_paginate @statistics, renderer: BootstrapPagination::Rails %>


text:
<%- if controller_name != 'sessions' %>
  <%= link_to "Log in", new_session_path(resource_name) %>

<% end -%>

<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
  <%= link_to "Sign up", new_registration_path(resource_name) %>

<% end -%>

<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
  <%= link_to "Forgot your password?", new_password_path(resource_name) %>

<% end -%>

<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
  <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>

<% end -%>

<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
  <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>

<% end -%>

<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %>

  <% end -%>
<% end -%>


text:
Forgot your password?

<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
  <%= devise_error_messages! %>

    <%= f.label :email %>

    <%= f.email_field :email, autofocus: true %>

    <%= f.submit "Send me reset password instructions" %>

<% end %>

<%= render "devise/shared/links" %>


text:
Change your password

<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
  <%= devise_error_messages! %>
  <%= f.hidden_field :reset_password_token %>

    <%= f.label :password, "New password" %>

    <% if @minimum_password_length %>
      (<%= @minimum_password_length %> characters minimum)

    <% end %>
    <%= f.password_field :password, autofocus: true, autocomplete: "off" %>

    <%= f.label :password_confirmation, "Confirm new password" %>

    <%= f.password_field :password_confirmation, autocomplete: "off" %>

    <%= f.submit "Change my password" %>

<% end %>

<%= render "devise/shared/links" %>


text:
Resend confirmation instructions

<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
  <%= devise_error_messages! %>

    <%= f.label :email %>

    <%= f.email_field :email, autofocus: true, value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>

    <%= f.submit "Resend confirmation instructions" %>

<% end %>

<%= render "devise/shared/links" %>


text:
Hello <%= @resource.email %>!

We're contacting you to notify you that your password has been changed.


text:
Hello <%= @resource.email %>!

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

Click the link below to unlock your account:

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>


text:
Hello <%= @resource.email %>!

Someone has requested a link to change your password. You can do this through the link below.

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

If you didn't request this, please ignore this email.

Your password won't change until you access the link above and create a new one.


text:
Welcome <%= @email %>!

You can confirm your account email through the link below:

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>


text:
      <%= link_to signup_auth_url, class: "btn btn-default se-oauth-button" do %>
        Sign up with
          <%= image_tag "se-icon.svg", class: "se-icon" %>Stack Exchange
      <% end %>

or

      <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
        <%= devise_error_messages! %>

          <%= f.label :email %>

          <%= f.email_field :email, autofocus: true, class: "form-control" %>

          <%= f.label :username %>

          <%= f.text_field :username, class: "form-control" %>

          <%= f.label :password %>
          <% if @minimum_password_length %>
          (<%= @minimum_password_length %> characters minimum)
          <% end %>

          <%= f.password_field :password, autocomplete: "off", class: "form-control" %>

          <%= f.label :password_confirmation %>

          <%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %>

          <%= f.check_box :eu_resident %>
          <%= f.label :eu_resident, 'I am an EU resident' %>

          <%= f.check_box :privacy_accepted %>
          <%= f.label :privacy_accepted do %>
            I have read and accept the <%= link_to 'Privacy Statement', data_privacy_path %>
          <% end %>

          <%= f.submit "Sign up", class: "btn btn-primary" %>

          <%= render "devise/shared/links" %>

      <% end %>


text:
Account Settings

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
  <% if resource.errors.any? %>
<%= devise_error_messages! %>
<% end %>

    <%= f.label :email %>

    <%= f.email_field :email, autofocus: true, class: 'form-control' %>

  <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>

Currently waiting confirmation for: <%= resource.unconfirmed_email %>

  <% end %>

    <%= f.label :password %> (leave blank if you don't want to change it)

    <%= f.password_field :password, autocomplete: "off", class: 'form-control' %>

    <%= f.label :password_confirmation %>

    <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control' %>

    <%= f.label :current_password %> (we need your current password to confirm your changes)

    <%= f.password_field :current_password, autocomplete: "off", class: 'form-control' %>

    <%= f.submit "Update", class: 'btn btn-primary' %>

<% end %>

<% if /\d+@se-oauth\.metasmoke/.match?(current_user.email) %>

No password? No problem!

  <%= form_tag url_for(controller: '/users', action: :update_email) do %>

      <%= label_tag :email %>

      <%= email_field_tag :email, nil, class: 'form-control' %>

      <%= submit_tag 'Save', class: 'btn btn-success' %>

  <% end %>
<% end %>

Two-factor authentication

<%= link_to 'Configure two-factor authentication', url_for(controller: '/users', action: :tf_status) %>

Announcement emails

<%= form_tag url_for(controller: '/users', action: :set_announcement_emails) do %>

    <%= check_box_tag :emails, nil, current_user.announcement_emails %> <%= label_tag :emails, "Send me emails about new announcements" %>

    <%= submit_tag "Save", class: 'btn btn-success' %>

<% end %>

Cancel my account

Unhappy? You can delete your metasmoke account by clicking this button.
  <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger' %>

<%= link_to "Back", :back %>


text:
Resend unlock instructions

<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
  <%= devise_error_messages! %>

    <%= f.label :email %>

    <%= f.email_field :email, autofocus: true %>

    <%= f.submit "Resend unlock instructions" %>

<% end %>

<%= render "devise/shared/links" %>


text:
      <%= link_to login_auth_url, class: "btn btn-default se-oauth-button" do %>
        Login with
          <%= image_tag "se-icon.svg", class: "se-icon" %>Stack Exchange
      <% end %>

or

      <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>

          <%= f.label :email %>

          <%= f.email_field :email, autofocus: true, class: "form-control" %>

          <%= f.label :password %>

          <%= f.password_field :password, autocomplete: "off", class: "form-control" %>

          <%= f.submit "Log in", class: "btn btn-primary" %>

          <%= render "devise/shared/links" %>

      <% end %>


text:
<%= form_for @smoke_detector, url: smoke_detector_create_path do |f| %>

    <%= f.label :name, "" %>

    <%= f.text_field :name, class: "form-control" %>

    <%= f.submit "Save", class: "btn btn-success" %>

<% end %>


text:
SmokeDetectors Audit

    User
    Action
    Location
    At

  <% @audits.each do |audit| %>

        <%= audit.user.try(:username) %>
        <%= audit.action %>
        <%= audit.audited_changes['location'] %>
        <span data-livestamp="<%= audit.created_at.to_i %>" title="<%= audit.created_at %>">

  <% end %>


text:
Create SmokeDetector

<%= render partial: 'form' %>


text:
My SmokeDetectors

      ID
      Location
      Last Ping
      Key
      Actions

    <% @smoke_detectors.each do |sd| %>

        <%= sd.id %>
        <%= sd.location %>
        <%= sd.last_ping %>
        <%= sd.access_token %>

          <%= link_to "New Token", smoke_detector_token_regen_path(sd.id, redirect: request.path), method: :post, data: { confirm: "Are you sure you want to create a new token for this item?" } %>

          <%= link_to "Remove", smoke_detector_delete_path(sd.id, redirect: request.path), method: :delete, data: { confirm: "Are you sure you want to delete this key?" } %>

    <% end %>

<%= link_to "Create a new key", smoke_detector_new_path %>


text:
<% exclude_responses = exclude_responses ? Array(exclude_responses) : [] %>

    <% if (item.completed and ReviewResult.where(item: item).where.not(result: "skip").exists?) or
        ReviewResult.where(user: current_user, item: item).where.not(result: "skip").exists? %>

        <% if item.completed %>
          Review completed.
        <% else %>
          You have already reviewed this item.  It needs more reviews from other users to be completed.
        <% end %>

        <% item.results.each do |result| %>
          <%= result.user.present? ? result.user.username : 'No user recorded' %>
          <span data-livestamp="<%= result.created_at.to_i %>" title="<%= result.created_at.to_s %>">
            <%= time_ago_in_words(result.created_at.to_s) %>
          :
          <%= result.result == 'skip' ? 'Skip' : queue.responses.map(&:reverse).to_h[result.result] %>

        <% end %>

      <%= link_to 'Next', 'javascript:void(0)', class: 'btn btn-primary review-next-link' %>
    <% else %>
      <% (queue.responses - exclude_responses).each do |r| %>
        <%= link_to r[0], submit_review_path(name: queue.name, item_id: item.id, response: r[1]), class: 'btn btn-primary review-submit-link',
                    method: :post, remote: true %>
      <% end %>
      <%= link_to 'Skip', submit_review_path(name: queue.name, item_id: item.id, response: 'skip'), class: 'btn btn-primary review-submit-link',
                  method: :post, remote: true %>
    <% end %>


text:
Review history for <%= @queue.name.titleize %>

<% if current_user&.has_role?(:developer) %>

    <%= link_to 'Recheck items', recheck_queue_items_path(name: @queue.name), class: 'text-danger', method: :post %>

<% end %>

  <li class="<%= 'active' if @all %>"><%= link_to 'All', all: 1 %>

  <li class="<%= 'active' unless @all %>"><%= link_to 'Mine', all: 0 %>

<% if @all %>
  <%= form_tag review_history_path(name: @queue.name), method: :get, class: 'form-inline top-filters' do %>
    <%= hidden_field_tag :all, '1' %>
    <%= label_tag :user %>
    <%= select_tag :user, options_from_collection_for_select(User.all, 'id', 'username', selected: params[:user]),
                   include_blank: true, class: 'form-control input-sm' %>
    <%= label_tag :response %>
    <%= select_tag :response, options_for_select(@queue.responses + [['Skip', 'skip']], selected: params[:response]),
                   include_blank: true, class: 'form-control input-sm' %>
    <%= label_tag :role %>
    <%= select_tag :role, options_for_select(Role.names.map { |r| [r.to_s.humanize, r] }, selected: params[:role]),
                   include_blank: true, class: 'form-control input-sm' %>
    <%= submit_tag 'Filter', class: 'btn btn-primary btn-sm' %>
  <% end %>
<% end %>

    Item ID
    Review ID
    <% if @all %> User	 <% end %>
    Task
    Timestamp
    Result
    <% if current_user&.has_role?(:admin) %>

    <% end %>

  <% @reviews.each do |review| %>

      <%= review.item.id %>
      <%= review.id %>
      <% if @all %> <%= review.user&.username || '(deleted user)' %>	 <% end %>
      <bdi><%= link_to review.item.reviewable&.review_item_name || '(deleted record)', review_item_path(@queue.name, review.item) %><bdi>
      <span title="<%= review.created_at %>"><%= time_ago_in_words(review.created_at) %> ago
      <%= review.result %>
      <% if current_user&.has_role?(:admin) %>
        <%= link_to 'Delete', delete_review_path(name: @queue.name, id: review.id), class: 'text-danger', method: :delete %>
      <% end %>

  <% end %>

  <%= will_paginate @reviews, renderer: BootstrapPagination::Rails %>


text:
  Review <%= @queue.name.titleize %>

    ·
    <%= link_to 'History', review_history_path(name: @queue.name) %>

<%= @queue.description %>

<% if @queue.name == 'posts' %>

    <form class="form-inline">
      <%= label_tag :site_id, 'Site' %>
      <% h = HTMLEntities.new %>
      <% sites_with_counts = Site.left_joins(:posts => [:review_item]).where(posts: { review_items: { completed: [false, nil] } }).group('sites.id').select('sites.*, COUNT(review_items.id) as review_item_count') %>
      <%= select_tag :site_id, options_for_select(sites_with_counts.map { |s| ["#{h.decode(s.site_name)} (#{s.review_item_count})", s.id] }), include_blank: true,
                     class: 'form-control review-filter selectpicker' %>
      <button type="button" class="btn btn-primary" id="filter-button">Filter</button>
    </form>

<% end %>

  <% if @item %>
    <%= render "#{@item.reviewable_type.underscore.pluralize}/review_item.html.erb", queue: @queue, item: @item, layout: nil %>
  <% else %>
    Loading...
  <% end %>


text:
Review Queues

<% @queues.each do |q| %>

        <%= link_to q.name.titleize, review_queue_path(q.name) %>
        <%= q.items.active.count if q.items.active.count > 0 %>

        <% if current_user&.has_role?(q.privileges) %>
          <%= q.description %>
        <% else %>
          You don't have the required permissions to use this queue.
        <% end %>

      <%= link_to 'Review History', review_history_path(q.name) %>

<% end %>


text:
<%= form_for status, url: url do |f| %>

    <%= f.label :name do %>
       Name *
    <% end %>

    <%= f.text_field :name, class: 'form-control' %>

    <%= f.label :icon do %>
       Icon *
    <% end %>
    Valid glyphicon class, without the glyphicon- prefix.

    <%= f.text_field :icon, class: 'form-control' %>

    <%= f.label :color do %>
       Color *
    <% end %>
    Bootstrap contextual color class (i.e. info or success, etc)

    <%= f.text_field :color, class: 'form-control' %>

    <%= f.submit 'Save Status', class: 'btn btn-success' %>

<% end %>


text:
Reports with status <%= render 'status', status: @status, nl: true %>

<% @reports.in_groups_of(3).map(&:compact).each do |g| %>

    <% g.each do |r| %>
      <%= render 'abuse_reports/report', report: r %>
    <% end %>

<% end %>


text:
Edit Status: <%= @status.name %>

<%= render 'form', status: @status, url: update_abuse_status_path(@status) %>


text:
<% nl ||= false %>
<span class="label label-<%= status.color %> report-status <%= 'nl' if nl %>">
  <span class="glyphicon glyphicon-<%= status.icon %>">
  <%= link_to status.name, abuse_status_path(status) %>


text:
Report Statuses

These are the statuses that are shown to users as options when they're updating their abuse reports. You shouldn't need to change
them.

<% @statuses.each do |s| %>

    <%= render 'status', status: s %>
    <%= link_to 'Edit', edit_abuse_status_path(s) %> ·
    <%= link_to 'Delete', destroy_abuse_status_path(s), class: 'text-danger', method: :delete, data: { confirm: 'Are you sure?' } %>

<% end %>

   New report status

        <button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>

New Report Status

        <%= render 'form', status: AbuseReportStatus.new, url: create_abuse_status_path %>


text:
Announcement

This announcement was just posted on metasmoke:

  <%= raw(sanitize(@announcement.text, scrubber: Announcement.scrubber)) %>

Please do not reply to this email. Direct any comments or queries to Charcoal HQ.