Skip to content

Commit fc08b8a

Browse files
committed
feat: Add first part of Elasticsearch impl
1 parent e008496 commit fc08b8a

18 files changed

Lines changed: 344 additions & 27 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace Frosh\Tools\Components\Elasticsearch;
4+
5+
use Elasticsearch\Client;
6+
7+
class ElasticsearchManager
8+
{
9+
private Client $client;
10+
private bool $enabled;
11+
12+
public function __construct(Client $client, bool $enabled)
13+
{
14+
$this->client = $client;
15+
$this->enabled = $enabled;
16+
}
17+
18+
public function isEnabled(): bool
19+
{
20+
return $this->enabled;
21+
}
22+
23+
public function info(): array
24+
{
25+
return [
26+
'info' => $this->client->info(),
27+
'health' => $this->client->cluster()->health()
28+
];
29+
}
30+
31+
public function indices(): array
32+
{
33+
$indices = $this->client->indices()->get(['index' => '*']);
34+
$stats = $this->client->indices()->stats(['index' => '*']);
35+
36+
$list = [];
37+
38+
foreach ($indices as $indexName => $config) {
39+
$statCfg = $stats['indices'][$indexName];
40+
41+
$list[] = [
42+
'name' => $indexName,
43+
'aliases' => array_keys($config['aliases']),
44+
'indexSize' => $statCfg['total']['store']['size_in_bytes'],
45+
'docs' => $statCfg['primaries']['docs']['count'],
46+
];
47+
}
48+
49+
return $list;
50+
}
51+
52+
public function deleteIndex(string $name): array
53+
{
54+
return $this->client->indices()->delete(['index' => $name]);
55+
}
56+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace Frosh\Tools\Controller;
4+
5+
use Frosh\Tools\Components\Elasticsearch\ElasticsearchManager;
6+
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
7+
use Symfony\Component\HttpFoundation\JsonResponse;
8+
use Symfony\Component\HttpFoundation\Response;
9+
use Symfony\Component\Routing\Annotation\Route;
10+
11+
/**
12+
* @RouteScope(scopes={"api"})
13+
* @Route(path="/api/_action/frosh-tools/elasticsearch")
14+
*/
15+
class ElasticsearchController
16+
{
17+
private ElasticsearchManager $manager;
18+
19+
public function __construct(ElasticsearchManager $manager)
20+
{
21+
$this->manager = $manager;
22+
}
23+
24+
/**
25+
* @Route(path="/status", methods={"GET"}, name="api.frosh.tools.elasticsearch.status")
26+
*/
27+
public function status(): Response
28+
{
29+
if (!$this->manager->isEnabled()) {
30+
return new Response('', Response::HTTP_PRECONDITION_FAILED);
31+
}
32+
33+
return new JsonResponse($this->manager->info());
34+
}
35+
36+
/**
37+
* @Route(path="/indices", methods={"GET"}, name="api.frosh.tools.elasticsearch.indices")
38+
*/
39+
public function indices(): Response
40+
{
41+
if (!$this->manager->isEnabled()) {
42+
return new Response('', Response::HTTP_PRECONDITION_FAILED);
43+
}
44+
45+
return new JsonResponse($this->manager->indices());
46+
}
47+
48+
/**
49+
* @Route(path="/index/{indexName}", methods={"DELETE"}, name="api.frosh.tools.elasticsearch.delete_index")
50+
*/
51+
public function deleteIndex(string $indexName): Response
52+
{
53+
if (!$this->manager->isEnabled()) {
54+
return new Response('', Response::HTTP_PRECONDITION_FAILED);
55+
}
56+
57+
return new JsonResponse($this->manager->deleteIndex($indexName));
58+
}
59+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const { ApiService } = Shopware.Classes;
2+
3+
class Elasticsearch extends ApiService {
4+
constructor(httpClient, loginService, apiEndpoint = '_action/frosh-tools/elasticsearch') {
5+
super(httpClient, loginService, apiEndpoint);
6+
}
7+
8+
status() {
9+
const apiRoute = `${this.getApiBasePath()}/status`;
10+
return this.httpClient.get(
11+
apiRoute,
12+
{
13+
headers: this.getBasicHeaders()
14+
}
15+
).then((response) => {
16+
return ApiService.handleResponse(response);
17+
});
18+
}
19+
20+
indices() {
21+
const apiRoute = `${this.getApiBasePath()}/indices`;
22+
return this.httpClient.get(
23+
apiRoute,
24+
{
25+
headers: this.getBasicHeaders()
26+
}
27+
).then((response) => {
28+
return ApiService.handleResponse(response);
29+
});
30+
}
31+
32+
deleteIndex(indexName) {
33+
const apiRoute = `${this.getApiBasePath()}/index/` + indexName;
34+
return this.httpClient.delete(
35+
apiRoute,
36+
{
37+
headers: this.getBasicHeaders()
38+
}
39+
).then((response) => {
40+
return ApiService.handleResponse(response);
41+
});
42+
}
43+
}
44+
45+
export default Elasticsearch;
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
const { Application } = Shopware;
2-
31
import FroshToolsService from "./frosh-tools";
2+
import Elasticsearch from "./elasticsearch";
3+
4+
const { Application } = Shopware;
45

5-
Application.addServiceProvider('FroshToolsService', (container) => {
6+
Application.addServiceProvider('froshToolsService', (container) => {
67
const initContainer = Application.getContainer('init');
78

89
return new FroshToolsService(initContainer.httpClient, container.loginService);
910
});
11+
12+
Application.addServiceProvider('froshElasticSearch', (container) => {
13+
const initContainer = Application.getContainer('init');
14+
15+
return new Elasticsearch(initContainer.httpClient, container.loginService);
16+
});

src/Resources/app/administration/src/module/frosh-tools/component/frosh-tools-tab-cache/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const { Criteria } = Shopware.Data;
77
Component.register('frosh-tools-tab-cache', {
88
template,
99

10-
inject: ['FroshToolsService', 'repositoryFactory', 'themeService'],
10+
inject: ['froshToolsService', 'repositoryFactory', 'themeService'],
1111
mixins: [
1212
Mixin.getByName('notification')
1313
],
@@ -59,7 +59,7 @@ Component.register('frosh-tools-tab-cache', {
5959
methods: {
6060
async createdComponent() {
6161
this.isLoading = true;
62-
this.cacheInfo = await this.FroshToolsService.getCacheInfo();
62+
this.cacheInfo = await this.froshToolsService.getCacheInfo();
6363
this.isLoading = false;
6464
},
6565

@@ -85,7 +85,7 @@ Component.register('frosh-tools-tab-cache', {
8585

8686
async clearCache(item) {
8787
this.isLoading = true;
88-
await this.FroshToolsService.clearCache(item.name);
88+
await this.froshToolsService.clearCache(item.name);
8989
await this.createdComponent();
9090
},
9191

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,92 @@
11
import template from './template.twig';
22

3-
const { Component, Mixin } = Shopware;
4-
const { Criteria } = Shopware.Data;
3+
const { Component } = Shopware;
54

65
Component.register('frosh-tools-tab-elasticsearch', {
7-
template
6+
template,
7+
8+
inject: ['froshElasticSearch'],
9+
10+
data() {
11+
return {
12+
isLoading: true,
13+
isActive: true,
14+
statusInfo: {},
15+
indices: [],
16+
consoleInput: 'GET /_cat/indices'
17+
};
18+
},
19+
20+
computed: {
21+
columns() {
22+
return [
23+
{
24+
property: 'name',
25+
label: 'frosh-tools.name',
26+
rawData: true,
27+
primary: true
28+
},
29+
{
30+
property: 'indexSize',
31+
label: 'frosh-tools.size',
32+
rawData: true,
33+
primary: true
34+
},
35+
{
36+
property: 'docs',
37+
label: 'frosh-tools.docs',
38+
rawData: true,
39+
primary: true
40+
}
41+
];
42+
}
43+
},
44+
45+
async created() {
46+
this.createdComponent();
47+
},
48+
49+
methods: {
50+
async createdComponent() {
51+
this.isLoading = true;
52+
53+
try {
54+
this.statusInfo = await this.froshElasticSearch.status();
55+
} catch (err) {
56+
this.isActive = false;
57+
this.isLoading = false;
58+
59+
return;
60+
} finally {
61+
this.isLoading = false;
62+
}
63+
64+
this.indices = await this.froshElasticSearch.indices();
65+
},
66+
67+
formatSize(bytes) {
68+
const thresh = 1024;
69+
const dp = 1;
70+
71+
if (Math.abs(bytes) < thresh) {
72+
return bytes + ' B';
73+
}
74+
75+
const units = ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
76+
let u = -1;
77+
const r = 10**dp;
78+
79+
do {
80+
bytes /= thresh;
81+
++u;
82+
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
83+
84+
return bytes.toFixed(dp) + ' ' + units[u];
85+
},
86+
87+
async deleteIndex(indexName) {
88+
await this.froshElasticSearch.deleteIndex(indexName);
89+
await this.createdComponent();
90+
}
91+
}
892
})
Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,51 @@
11
<sw-card-view>
2-
<sw-card :title="$tc('frosh-tools.tabs.elasticsearch.title')" :large="true"></sw-card>
2+
<sw-card :title="$tc('frosh-tools.tabs.elasticsearch.title')" :large="true" :isLoading="isLoading">
3+
<sw-alert variant="error" v-if="!isLoading && !isActive">Elasticsearch is not enabled</sw-alert>
4+
5+
<div v-if="!isLoading && isActive">
6+
<div><strong>Elasticsearch version: </strong> {{ statusInfo.info.version.number }}</div>
7+
<div><strong>Nodes: </strong> {{ statusInfo.health.number_of_nodes }}</div>
8+
<div><strong>Cluster status: </strong> {{ statusInfo.health.status }}</div>
9+
</div>
10+
</sw-card>
11+
12+
<sw-card title="Indices" v-if="!isLoading && isActive" :large="true">
13+
<sw-data-grid
14+
v-if="indices"
15+
:showSelection="false"
16+
:dataSource="indices"
17+
:columns="columns">
18+
19+
<template #column-name="{ item }">
20+
<sw-label variant="primary" appearance="pill" v-if="item.aliases.length">
21+
{{ $tc('frosh-tools.active') }}
22+
</sw-label>
23+
24+
{{ item.name }}<br>
25+
</template>
26+
27+
<template #column-indexSize="{ item }">
28+
{{ formatSize(item.indexSize) }}<br>
29+
</template>
30+
31+
<template #actions="{ item }">
32+
<sw-context-menu-item variant="danger" @click="deleteIndex(item.name)">
33+
{{ $tc('frosh-tools.delete') }}
34+
</sw-context-menu-item>
35+
</template>
36+
</sw-data-grid>
37+
</sw-card>
38+
39+
<sw-card title="Actions" v-if="!isLoading && isActive" :large="true">
40+
<sw-button variant="primary">Reindex</sw-button>
41+
<sw-button>Trigger alias switching</sw-button>
42+
</sw-card>
43+
44+
<sw-card title="Elasticsearch Console" v-if="!isLoading && isActive" :large="true">
45+
<sw-code-editor completionMode="text" mode="twig" :softWraps="true" :setFocus="false" :disabled="false" :sanitizeInput="false" v-model="consoleInput"></sw-code-editor>
46+
47+
<div><strong>Output:</strong></div>
48+
49+
<pre></pre>
50+
</sw-card>
351
</sw-card-view>

src/Resources/app/administration/src/module/frosh-tools/component/frosh-tools-tab-files/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const { Component, Mixin } = Shopware;
55

66
Component.register('frosh-tools-tab-files', {
77
template,
8-
inject: ['repositoryFactory', 'FroshToolsService'],
8+
inject: ['repositoryFactory', 'froshToolsService'],
99
mixins: [
1010
Mixin.getByName('notification')
1111
],
@@ -42,7 +42,7 @@ Component.register('frosh-tools-tab-files', {
4242

4343
methods: {
4444
async createdComponent() {
45-
this.items = (await this.FroshToolsService.getShopwareFiles()).data;
45+
this.items = (await this.froshToolsService.getShopwareFiles()).data;
4646
this.isLoading = false;
4747
}
4848
}

src/Resources/app/administration/src/module/frosh-tools/component/frosh-tools-tab-index/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import './style.scss';
44
const { Component } = Shopware;
55

66
Component.register('frosh-tools-tab-index', {
7-
inject: ['FroshToolsService'],
7+
inject: ['froshToolsService'],
88
template,
99

1010
data() {
@@ -42,7 +42,7 @@ Component.register('frosh-tools-tab-index', {
4242
},
4343

4444
async createdComponent() {
45-
this.health = await this.FroshToolsService.healthStatus();
45+
this.health = await this.froshToolsService.healthStatus();
4646
this.isLoading = false;
4747
},
4848
}

0 commit comments

Comments
 (0)