Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.4-custom] - 2025-08
### Fixed
- fixed server removal issues

### Improved
- Redesigned Refresh button as dropdown + icon combination

### Added
- Delete Index functionality with checkbox selection
- filtering for index selector in Structured Query tab
36 changes: 34 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
# Multi Elasticsearch Head
# Multi Elasticsearch Head - Custom

This is a fork of [Multi Elasticsearch Head](https://github.com/vorapoap/elasticsearch-head-chrome) with additional features for improved usability in multi-tenant Elasticsearch environments.

## 🚀 New Features (Custom - 2025-08)

### Index Filtering
- **Enhanced Index Selector**: Added filtering capability to the index selector in the Structured Query tab
- **Multi-tenant Support**: Improved usability for environments with tenant-prefixed index names

### Local Setting

* you can use python.
```
cd /Users/~~/Documents/elasticsearch-head-chrome && python -m http.server 8000
```

* If you need local chrome extension, then run build.py
```
python build.py
```

-----
## Original Project

This is a major improvement over Elasticsearch Head extension.
* Add dropdown for multiple Elasticsearch Head end-points
Expand All @@ -21,7 +44,7 @@ This was created because ElasticSearch 5 removed the ability to run ElasticSearc

## Installation

Head over to [Multi Elasticsearch Head](https://chrome.google.com/webstore/detail/multi-elasticsearch-head/cpmmilfkofbeimbmgiclohpodggeheim) page on the Chrome Web Store.
Head over to [Multi Elasticsearch Head](https://chrome.google.com/webstore/detail/multi-elasticsearch-head/cpmmilfkofbeimbmgiclohpodggeheim) page on the Chrome Web Store.

## Usage

Expand All @@ -40,4 +63,13 @@ To make this more convenient to use (ie: without having to enter in the remote E
`ssh -N -L 29200:myeshost:9200 myusername@jumphost`
* then use `chrome-extension://ffmkiejjmecolpfloofpjologoblkegm/elasticsearch-head/index.html?base_uri=http://localhost:29200` to get to the remote ES cluster.

## License

This project is licensed under the MIT License - see the original project for details.

## Acknowledgments

- Original project: [Multi Elasticsearch Head](https://github.com/lyfeyaj/elasticsearch-head-chrome)
- Based on: [Elasticsearch Head](https://github.com/mobz/elasticsearch-head)


78 changes: 78 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
import os
import shutil
import zipfile
from pathlib import Path

print('Building Elasticsearch Head Chrome Extension(Custom)...')

build_dir = 'build'
if os.path.exists(build_dir):
shutil.rmtree(build_dir)
os.makedirs(build_dir)

files_to_copy = [
'manifest.json',
'src/background.js',
'elasticsearch-head/index.html',
'elasticsearch-head/app.js',
'elasticsearch-head/app.css',
'elasticsearch-head/vendor.js',
'elasticsearch-head/vendor.css',
'elasticsearch-head/i18n.js',
'elasticsearch-head/onload.js',
'elasticsearch-head/base/favicon.png',
'elasticsearch-head/base/jquery.min.js',
'elasticsearch-head/base/loading.gif',
'elasticsearch-head/base/purecss.css',
'elasticsearch-head/base/reset.css',
'elasticsearch-head/fonts/fontawesome-webfont.eot',
'elasticsearch-head/fonts/fontawesome-webfont.svg',
'elasticsearch-head/fonts/fontawesome-webfont.ttf',
'elasticsearch-head/fonts/fontawesome-webfont.woff',
'elasticsearch-head/fonts/FontAwesome.otf',
'elasticsearch-head/lang/en_strings.js',
'elasticsearch-head/lang/fr_strings.js',
'elasticsearch-head/lang/ja_strings.js',
'elasticsearch-head/lang/pt_strings.js',
'elasticsearch-head/lang/tr_strings.js',
'elasticsearch-head/lang/zh_strings.js',
'elasticsearch-head/lang/zh-TW_strings.js',
'icons/esmhead_16x16.png',
'icons/esmhead_32x32.png',
'icons/esmhead_48x48.png',
'icons/esmhead_128x128.png'
]

for file in files_to_copy:
source_path = file
dest_path = os.path.join(build_dir, file)

if os.path.exists(source_path):
dest_dir = os.path.dirname(dest_path)
if not os.path.exists(dest_dir):
os.makedirs(dest_dir, exist_ok=True)

shutil.copy2(source_path, dest_path)
print(f"✓ Copied: {file}")
else:
print(f"✗ Missing: {file}")

print('\nBuild completed successfully!')
print('Extension files are ready in the "build" directory.')
print('You can now load the extension in Chrome from the build directory.')

create_zip = input('\nDo you want to create a ZIP file for distribution? (y/n): ').lower().strip()
if create_zip == 'y':
zip_filename = 'elasticsearch-head-custom-v0.4.4.zip'

with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(build_dir):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, build_dir)
zipf.write(file_path, arcname)
print(f"✓ Added to ZIP: {arcname}")

print(f'\nZIP file created: {zip_filename}')
print('This ZIP file can be uploaded to the Chrome Web Store or distributed manually.')
Binary file added doc/esmhead_custom_sc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/esmhead_custom_sc0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
176 changes: 154 additions & 22 deletions elasticsearch-head/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -114,38 +114,66 @@ TABLE.table H3 {
text-align: left;
}

.uiSplitButton {
white-space: nowrap;
.uiRefreshButton {
display: inline-flex;
align-items: center;
gap: 8px;
background: #96c6eb;
border: 1px solid #668dc6;
border-radius: 4px;
padding: 4px;
color: white;
}

.uiSplitButton .uiButton:first-child {
margin-right: 0;
display: inline-block;
.uiRefreshButton-select {
background: transparent;
border: none;
color: white;
font-size: 13px;
padding: 4px 8px;
cursor: pointer;
outline: none;
}

.uiSplitButton .uiButton:first-child .uiButton-content {
border-right-width: 1;
border-right-color: #5296c7;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
.uiRefreshButton-select option {
background: #96c6eb;
color: white;
}

.uiRefreshButton-icon {
background: transparent;
border: none;
color: white;
cursor: pointer;
padding: 4px 6px;
border-radius: 3px;
transition: background-color 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}

.uiSplitButton .uiMenuButton {
margin-left: 0;
.uiRefreshButton-icon:hover {
background: rgba(255, 255, 255, 0.2);
}

.uiSplitButton .uiButton:last-child .uiButton-content {
border-radius: 2px;
border-left-width: 1;
border-left-color: #96c6eb;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
height: 15px;
.uiRefreshButton-icon.disabled {
opacity: 0.5;
cursor: not-allowed;
}

.uiRefreshButton-icon.disabled:hover {
background: transparent;
}

.refresh-icon {
font-size: 14px;
font-weight: bold;
}

.uiSplitButton .uiButton:last-child .uiButton-label {
padding: 2px 17px 2px 6px;
margin-left: -8px;
.uiRefreshButton.auto-refresh {
background: #2575b7;
border-color: #1e5a8a;
}

.uiToolbar {
Expand Down Expand Up @@ -258,11 +286,14 @@ TABLE.table H3 {
position: absolute;
background: #96c6eb;
color: white;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

.uiMenuPanel LI {
list-style: none;
border-bottom: 1px solid #668dc6;
padding: 0;
}

.uiMenuPanel LI:hover {
Expand All @@ -273,7 +304,13 @@ TABLE.table H3 {
border-bottom: 0;
}

.uiMenuPanel-item {
cursor: pointer;
}

.uiMenuPanel-label {
font-size: 13px;
line-height: 1.4;
white-space: nowrap;
padding: 2px 10px 2px 10px;
cursor: pointer;
Expand Down Expand Up @@ -764,6 +801,12 @@ SPAN.uiJsonPretty-boolean {
top: 1px;
}

.uiFilterBrowser-row BUTTON {
height: 22px;
position: relative;
top: 1px;
}

.uiHeader {
padding: 3px 10px;
}
Expand Down Expand Up @@ -827,6 +870,95 @@ SPAN.uiJsonPretty-boolean {
margin-right: 5px;
}

.uiIndexSelector {
display: inline;
}

.uiIndexSelector-customSelect {
position: relative;
display: inline-block;
}

.uiIndexSelector-selectInput {
padding: 4px 8px;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 100%;
width: auto;
min-width: 200px;
background: #fff;
box-sizing: border-box;
}

.uiIndexSelector-selectInput:focus {
outline: none;
border-color: #397fca;
box-shadow: 0 0 3px rgba(57, 127, 202, 0.3);
}

.uiIndexSelector-selectInput::placeholder {
color: #999;
font-style: italic;
}

.uiIndexSelector-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ccc;
border-top: none;
border-radius: 0 0 4px 4px;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
display: none;
}

.uiIndexSelector-option {
padding: 8px 10px;
cursor: pointer;
border-bottom: 1px solid #eee;
}

.uiIndexSelector-option:hover {
background-color: #f5f5f5;
}

.uiIndexSelector-option:last-child {
border-bottom: none;
}

.uiIndexOverview-deleteButton {
margin-left: 10px;
background-color: #dc3545;
border-color: #dc3545;
color: white;
}

.uiIndexOverview-deleteButton:hover {
background-color: #c82333;
border-color: #bd2130;
}

.uiIndexOverview-deleteButton:disabled {
background-color: #6c757d;
border-color: #6c757d;
cursor: not-allowed;
}

.uiIndexOverview-checkbox {
margin: 0;
cursor: pointer;
}

.uiIndexOverview-selectedCount {
margin-left: 10px;
color: #6c757d;
font-size: 0.9em;
}

.modalDialog {
display: none;
position: fixed;
Expand Down
Loading