mongo-express
Version:
Web-based admin interface for MongoDB
552 lines (526 loc) • 20 kB
HTML
{% extends 'layout.html' %}
{% block title %}{{ collectionName }}{% endblock %}
{% block breadcrumb %}
<li class="nav-item">
<a class="nav-link px-1" href="{{ dbUrl }}">Database:</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle px-1" href="#" role="button" aria-controls="navbarDatabaseDropdown" data-bs-target="#navbarDatabaseDropdown" data-bs-toggle="dropdown" aria-expanded="false">
{{ dbName }}
</a>
<ul id="navbarDatabaseDropdown" class="dropdown-menu scrollable-ul">
{% for db in databases %}
<li><a class="dropdown-item" href="{{ baseHref }}db/{{ db | url_encode }}/">{{ db }}</a></li>
{% endfor %}
</ul>
</li>
<li class="nav-item">
<a class="nav-link px-1" href="{{ collectionUrl }}">Collection:</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle px-1" href="#" role="button" aria-controls="navbarCollectionDropdown" data-bs-target="#navbarCollectionDropdown" data-bs-toggle="dropdown" aria-expanded="false">
{{ collectionName }}
</a>
<ul id="navbarCollectionDropdown" class="dropdown-menu scrollable-ul">
{% for collection in collections %}
<li><a class="dropdown-item" href="{{ dbUrl }}/{{ collection | url_encode }}">{{ collection }}</a></li>
{% endfor %}
</ul>
</li>
{% endblock %}
{% block content %}
{% if !settings.read_only %}
<div class="btn-toolbar my-3" role="toolbar">
<button type="button" data-bs-toggle="modal" data-bs-target="#addDocument" class="btn btn-success btn-large me-1 m-1">
<i class="fas fa-plus"></i>
New Document
</button>
<button type="button" data-bs-toggle="modal" data-bs-target="#addIndex" class="btn btn-success btn-large m-1">
<i class="fas fa-plus"></i>
New Index
</button>
</div>
{% endif %}
<ul id="tabs" class="nav nav-pills nav-fill" role="tablist">
<li class="nav-item">
<a href="#simple" class="nav-link active" data-bs-toggle="tab"><i class="fa fa-tag"></i> Simple</a>
</li>
<li class="nav-item">
<a href="#advanced" class="nav-link" data-bs-toggle="tab"><i class="fa fa-tag"></i> Advanced</a>
</li>
</ul>
<div id="my-tab-content" class="tab-content p-3">
<div class="tab-pane active" id="simple">
<form method="get" action="{{ collectionUrl }}">
{% for column in columns %}
<input type="checkbox" name="sort[{{column}}]" class="d-none sort-{{column}}" {% if sort[column]
%}value="{{sort[column]}}" checked="checked" {% endif %} />
{% endfor %}
<div class="row">
<div class="col-sm-6 col-md-4 py-1">
<input style="width:100%;" class="input-medium form-control" type="text" id="key" name="key" placeholder="Key"
title="Key" value="{{ key }}">
</div>
<div class="col-sm-6 col-md-4 py-1">
<input style="width:100%;" class="input-medium form-control" type="text" id="value" name="value"
placeholder="Value" title="Value" value="{{ value }}">
</div>
<div class="col-sm-6 col-md-2 py-1">
<select name="type" class="form-control">
<option value="S" {% if type=='S' %}selected {% endif %}>String</option>
<option value="R" {% if type=='R' %}selected {% endif %}>Regex</option>
<option value="J" {% if type=='J' %}selected {% endif %}>JSON, bool</option>
<option value="N" {% if type=='N' %}selected {% endif %}>Number</option>
<option value="O" {% if type=='O' %}selected {% endif %}>ObjectId</option>
<option value="U" {% if type=='U' %}selected {% endif %}>UUID (v4)</option>
</select>
</div>
<div class="col-sm-6 col-md-2 d-grid py-1">
<button type="submit" class="btn btn-primary btn-block">
<i class="fa fa-search"></i>
Find
</button>
</div>
</div>
</form>
</div>
<div class="tab-pane" id="advanced">
<form method="get" action="{{ collectionUrl }}">
{% for column in columns %}
<input type="checkbox" name="sort[{{column}}]" class="d-none sort-{{column}}" {% if sort[column]
%}value="{{sort[column]}}" checked="checked" {% endif %} />
{% endfor %}
<div class="row">
<div class="col-sm-6 col-md-5 py-1">
<textarea class="input-medium form-control" style="width: 100%; height: 150px" id="query" name="query"
placeholder="Query" title="Query">{{ query }}</textarea>
</div>
<div class="col-sm-6 col-md-5 py-1">
<textarea class="input-medium form-control" style="width: 100%; height: 150px" id="projection"
name="projection" placeholder="Projection" title="Projection">{{ projection }}</textarea>
</div>
<div class="col-md-2 d-grid py-1">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="runAggregate" name="runAggregate" title="Run Aggregate"
{% if runAggregate %} checked="checked" {% endif %}>
<label class="form-check-label" for="runAggregate">Aggregate query</label>
</div>
<button type="submit" class="btn btn-primary btn-block">
<i class="fa fa-search"></i>
Find
</button>
</div>
</div>
</form>
</div>
</div>
{% if !settings.read_only && !settings.no_delete && count > 0 %}
<form id="deleteListForm" method="POST" class="my-3"
action="{{ collectionUrl }}?key={{ key }}&value={{ value }}&type={{ type }}&query={{ query|default('{}') }}&projection={{ projection }}&runAggregate={{ runAggregate }}">
{# Router is smart enough to transform method=POST + _method=delete into actual HTTP DELETE, which is what we want
#}
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
<input type="hidden" name="_method" value="delete">
<button type="button" data-bs-toggle="modal" data-bs-target="#deleteListModal" class="btn btn-danger btn-large btn-block">
<i class="fa fa-trash"></i>
Delete all {{count}} documents retrieved
</button>
</form>
{% endif %}
<br />
<!-- Add Document Modal -->
<div class="modal fade" id="addDocument" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form id="addDocumentForm" method="post" action="{{ collectionUrl }}">
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
<div class="modal-header">
<h2 class="modal-title">Add Document</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="document-modal-body">
<textarea id="document" name="document">{
"_id": ObjectId()
}</textarea>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success" onclick="return checkValidJSON({{ csrfToken }})">
<i class="fa fa-save"></i>
Save
</button>
</div>
</form>
</div>
</div>
</div>
<!-- End Add Document Modal -->
<!-- Add Index Modal -->
<div class="modal fade" id="addIndex" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form id="addIndexForm" method="post" action="{{ dbUrl }}/addIndex/{{ collectionName | url_encode }}">
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
<div class="modal-header">
<h2 class="modal-title">Add Indexes</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="index-modal-body">
<p>
A document that contains the field and value pairs where the field
is the index key. 1 for an ascending and -1 for a descending index.
</p>
<textarea id="index" name="index">{
"key": 1
}</textarea>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success" onclick="return checkValidIndexJSON()">
<i class="fa fa-save"></i>
Save
</button>
</div>
</form>
</div>
</div>
</div>
<!-- End Add Index Modal -->
{% if documents.length == 0 %}
<p class="card">
No documents found.
</p>
{% else %}
{% if pagination %}
<nav class="navbar" aria-label="Page navigation">
<div class="nav navbar-nav mx-auto">
<div id="paginator-top"></div>
</div>
</nav>
{% endif %}
<div style="position: relative;">
<div class="fadeToWhite" id="fadeToWhiteID"></div>
<div class="table-responsive tableWrapper">
<table class="table table-striped tableHeaderFooterBars">
<thead>
{% for column in columns %}
<th class="sorting-button" data-column="{{column}}" data-direction="{{sort[column]}}" title="Sort by {{column}}">
{{column}}
{% if sort[column] == 1 %}
<i class="fas fa-sort-up"></i>
{% elseif sort[column] == -1 %}
<i class="fas fa-sort-down"></i>
{% endif %}
</th>
{% endfor %}
</thead>
{% for index, document in docs %}
{% if document._id._bsontype == 'Binary' %}
<tr id="doc-{{ index }}"
onclick="loadDocument('{{ collectionUrl }}/{{ document._id | json | safe | url_encode }}?subtype={{ document._id.sub_type }}&skip={{skip}}')">
{% elseif document._id._bsontype == 'Long' %}
<tr id="doc-{{ index }}"
onclick="loadDocument('{{ collectionUrl }}/{{ document._id | longToString | safe | url_encode }}?skip={{skip}}')">
{% else %}
<tr id="doc-{{ index }}"
onclick="loadDocument('{{ collectionUrl }}/{{ document._id | json | safe | url_encode }}?skip={{skip}}')">
{% endif %}
{% for column in columns %}
<td>
<div class="tableContent">
{% if !settings.read_only && !settings.no_delete && column === '_id' && collectionName !== 'system.indexes'
%}
<button class="btn btn-info"
onclick="loadDocument('{{ collectionUrl }}/{{ document._id | json | safe | url_encode }}')">
<i class="fa fa-link"></i>
</button>
<form class="deleteButtonDocument" method="POST" style="display:inline;"
action="{{ collectionUrl }}/{{ document._id | json | safe | url_encode }}?subtype={{ document._id.sub_type }}&skip={{ skip }}&key={{ key }}&value={{ value }}&type={{ type }}&query={{ query }}&projection= {{ projection }}">
<input type="hidden" name="_method" value="delete">
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
<button type="submit" class="btn btn-danger">
<i class="fa fa-trash"></i>
</button>
</form>
{% endif %}
{% if document[column] | is_URL %}
<a href="{{ document[column] }}" target="_blank" rel="noopener noreferrer">{{ document[column] }}</a>
{% else %}
{{ document[column] | stringDocIDs | to_display | safe }}
{% endif %}
</div>
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</div>
</div>
{% if pagination %}
<nav class="navbar" aria-label="Page navigation">
<div class="nav navbar-nav mx-auto">
<div id="paginator-bottom"></div>
</div>
</nav>
{% endif %}
{% endif %}
{% if !settings.read_only %}
<div class="card mb-3">
<h2 class="card-header">Rename Collection</h2>
<div class="card-body">
<form method="POST" action="{{ collectionUrl }}" class="form-inline">
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
<input type="hidden" name="_method" value="put">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">{{ dbName }} . </span>
</div>
<input type="text" class="form-control" id="collection" name="collection" placeholder="{{ collectionName }}">
<div class="input-group-append">
<button class="btn btn-primary" type="button">
<i class="fa fa-pen"></i>
Rename
</button>
</div>
</div>
</form>
</div>
</div>
{% endif %}
<div class="card mb-3">
<h2 class="card-header">Tools</h2>
<div class="card-body">
<div class="row">
{% if !settings.me_no_export %}
<div class="col-sm-4 p-2 d-grid ">
<a href="{{ dbUrl }}/export/{{ collectionName | url_encode }}?key={{ key }}&value={{ value }}&type={{ type }}&query={{ query | url_encode }}&projection= {{ projection | url_encode }}{% for k, v in sort %}&sort[{{ k }}]={{ v }}{% endfor %}"
class="btn btn-warning btn-block text-white">
<i class="fa fa-download d-block"></i>Export Standard
</a>
</div>
<div class="col-sm-4 p-2 d-grid ">
<a href="{{ dbUrl }}/expArr/{{ collectionName | url_encode }}?key={{ key }}&value={{ value }}&type={{ type }}&query={{ query | url_encode }}&projection= {{ projection | url_encode }}{% for k, v in sort %}&sort[{{ k }}]={{ v }}{% endfor %}"
class="btn btn-warning btn-block text-white">
<i class="fa fa-download d-block"></i>Export --jsonArray
</a>
</div>
<div class="col-sm-4 p-2 d-grid ">
<a href="{{ dbUrl }}/expCsv/{{ collectionName | url_encode }}?key={{ key }}&value={{ value }}&type={{ type }}&query={{ query | url_encode }}&projection= {{ projection | url_encode }}{% for k, v in sort %}&sort[{{ k }}]={{ v }}{% endfor %}"
class="btn btn-warning btn-block text-white">
<i class="fa fa-download d-block"></i>Export --csv
</a>
</div>
{% endif %}
<div class="col-sm-4 p-2 d-grid ">
<a href="{{ dbUrl }}/reIndex/{{ collectionName | url_encode }}" class="btn btn-warning btn-block text-white">
<i class="fa fa-sync d-block"></i>Reindex
</a>
</div>
<div class="col-sm-4 p-2 d-grid ">
<a class="btn btn-warning btn-block text-white import-file-link">
<i class="fa fa-upload d-block"></i>Import --mongoexport json
</a>
<input class="d-none import-input-file" type="file" name="import-file"
collection-name="{{ collectionName | url_encode }}" />
</div>
{% if !settings.read_only %}
<div class="col-sm-4 p-2 d-grid ">
<a href="{{ dbUrl }}/compact/{{ collectionName | url_encode }}" class="btn btn-danger btn-block">
<i class="fa fa-compress d-block"></i>Compact
</a>
</div>
{% if !settings.no_delete %}
<div class="col-sm-4 p-2 d-grid ">
<form method="POST" class="d-grid" action="{{ baseHref }}db/{{ dbName }}/{{ collectionName | url_encode }}"
id="db-{{ dbName }}-{{ collectionName }}">
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
<input type="hidden" name="_method" value="delete">
<button type="submit" class="btn btn-danger d-grid deleteButtonCollection btn-block"
data-collection-name="{{ collectionName }}">
<i class="fa fa-trash d-block"></i>Delete
</button>
</form>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
{% if !settings.no_delete %}
<div class="modal fade" id="confirm-deletion-document" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
Are you sure?
</div>
<div class="modal-footer">
<button type="button" data-bs-dismiss="modal" class="btn btn-danger" id="delete">Delete</button>
<button type="button" data-bs-dismiss="modal" class="btn">Cancel</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="deleteListModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
Are you sure you want to delete all {{count}} documents?
</div>
<div class="modal-footer">
<button type="button" data-bs-dismiss="modal" class="btn btn btn-danger"
id="deleteListConfirmButton">Delete</button>
<button type="button" data-bs-dismiss="modal" class="btn">Cancel</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="confirm-deletion-collection" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Delete collection</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>
Be careful! You are about to delete whole <strong><span id="modal-collection-name"></span></strong>
collection.
</p>
<p>
<label for="confirmation-input">Type the database name to proceed:</label>
<input type="text" id="confirmation-input" name="confirmation-input" shouldbe="" value="" />
</p>
</div>
<div class="modal-footer">
<button type="button" data-bs-dismiss="modal" class="btn btn btn-danger" id="deleteCollectionConfirmation">Delete</button>
<button type="button" data-bs-dismiss="modal" class="btn">Cancel</button>
</div>
</div>
</div>
</div>
{% endif %}
{% if stats != false %}
<div class="card mb-3">
<h2 class="card-header">Collection stats</h2>
<table class="table table-bordered table-striped">
<tr>
<td>
<strong>Documents</strong>
</td>
<td>
{{ stats.count }}
</td>
</tr>
<tr>
<td>
<strong>Total doc size</strong>
</td>
<td>
{{ stats.size|convertBytes }}
</td>
</tr>
<tr>
<td>
<strong>Average doc size</strong>
</td>
<td>
{{ stats.avgObjSize|convertBytes }}
</td>
</tr>
<tr>
<td>
<strong>Pre-allocated size</strong>
</td>
<td>
{{ stats.storageSize|convertBytes }}
</td>
</tr>
<tr>
<td>
<strong>Indexes</strong>
</td>
<td>
{{ stats.nindexes }}
</td>
</tr>
<tr>
<td>
<strong>Total index size</strong>
</td>
<td>
{{ stats.totalIndexSize|convertBytes }}
</td>
</tr>
<tr>
<td>
<strong>Padding factor</strong>
</td>
<td>
{{ stats.paddingFactor }}
</td>
</tr>
<tr>
<td>
<strong>Extents</strong>
</td>
<td>
{{ stats.numExtents }}
</td>
</tr>
</table>
</div>
<div class="card mb-3">
<h2 class="card-header">Indexes</h2>
<table class="table table-bordered table-striped">
<tr>
<th>Name</th>
<th>Columns</th>
<th>Size</th>
<th>Attributes</th>
<th>Actions</th>
</tr>
{% for index in indexes %}
<tr>
<td>
{{ index.name }}
</td>
<td>
{% for sort in index.key %}
<div>{{ loop.key }} {% if sort == 1 %} ASC {% else %} DSC {% endif %}</div>
{% endfor %}
</td>
<td>
{{ index.size|convertBytes }}
</td>
<td>
{% for k,v in index %}
<div>
{% if k != 'key' && k != 'v' && k != 'name' && k != 'ns' && k != 'size' %}
{{ k }}:
{% if k == 'partialFilterExpression' %}
<pre>{{ v | json | safe }}</pre>
{% else %}
{{ v }}
{% endif %}
{% endif %}
</div>
{% endfor %}
</td>
{% if !settings.read_only && !settings.no_delete %}
<td>
<a class="btn btn-danger" href="{{ dbUrl }}/dropIndex/{{ collectionName | url_encode }}?name={{ index.name }}">
<i class="fa fa-trash"></i> Delete
</a>
</td>
{% else %}
<td>
</td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% endblock %}
{% block scripts %}
<script src="{{ baseHref }}{{assets.codemirror.js}}"></script>
<script src="{{ baseHref }}{{assets.collection.js}}"></script>
{% endblock %}