mirror of
https://github.com/simon987/sist2.git
synced 2025-12-12 15:08:53 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca845d80e8 | |||
| e2025df2c0 | |||
| 7eb064162e | |||
| 7bc4b73e43 | |||
| ca2e308d89 | |||
| c03c148273 | |||
| 5522bcfa9b | |||
| f0fd708082 | |||
| 6bf2b4c74d | |||
| d907576406 | |||
| 7659b481fa | |||
|
|
e81e5ee457 | ||
| d4820d2fad |
@@ -37,4 +37,5 @@ state.db
|
||||
build/
|
||||
__pycache__/
|
||||
sist2-vue/dist
|
||||
sist2-admin/frontend/dist
|
||||
sist2-admin/frontend/dist
|
||||
*.fts
|
||||
@@ -68,7 +68,7 @@
|
||||
},
|
||||
"mtime": {
|
||||
"type": "date",
|
||||
"format": "epoch_millis"
|
||||
"format": "epoch_second"
|
||||
},
|
||||
"size": {
|
||||
"type": "long"
|
||||
|
||||
0
scripts/sqlite_extension_compile.sh
Normal file → Executable file
0
scripts/sqlite_extension_compile.sh
Normal file → Executable file
7
scripts/test_in_docker.sh
Normal file
7
scripts/test_in_docker.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
docker build . -t tmp
|
||||
|
||||
docker run --rm -it\
|
||||
-v $(pwd):/host \
|
||||
tmp \
|
||||
scan --ocr-lang eng --ocr-ebooks -t6 --incremental --very-verbose \
|
||||
-o /host/docker.sist2 /host/third-party/libscan/libscan-test-files/test_files/
|
||||
12
sist2-admin/frontend/package-lock.json
generated
12
sist2-admin/frontend/package-lock.json
generated
@@ -9528,9 +9528,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz",
|
||||
"integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.3.tgz",
|
||||
"integrity": "sha512-JMafRntWVO2DCJimKsRTh/wnqVvO4hrfwOqtO7f+uzwsQMuxO6VwImtYxaQ+ieoyshWOTJyV0fA21lccEXRPpQ==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
@@ -17996,9 +17996,9 @@
|
||||
}
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz",
|
||||
"integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.3.tgz",
|
||||
"integrity": "sha512-JMafRntWVO2DCJimKsRTh/wnqVvO4hrfwOqtO7f+uzwsQMuxO6VwImtYxaQ+ieoyshWOTJyV0fA21lccEXRPpQ==",
|
||||
"requires": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
|
||||
@@ -33,9 +33,26 @@ class Sist2AdminApi {
|
||||
return axios.get(`${this.baseUrl}/api/job/${name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
getSearchBackend(name) {
|
||||
return axios.get(`${this.baseUrl}/api/search_backend/${name}`);
|
||||
}
|
||||
|
||||
updateSearchBackend(name, data) {
|
||||
return axios.put(`${this.baseUrl}/api/search_backend/${name}`, data);
|
||||
}
|
||||
|
||||
getSearchBackends() {
|
||||
return axios.get(`${this.baseUrl}/api/search_backend/`);
|
||||
}
|
||||
|
||||
deleteBackend(name) {
|
||||
return axios.delete(`${this.baseUrl}/api/search_backend/${name}`)
|
||||
}
|
||||
|
||||
createBackend(name) {
|
||||
return axios.post(`${this.baseUrl}/api/search_backend/${name}`);
|
||||
}
|
||||
|
||||
getFrontend(name) {
|
||||
return axios.get(`${this.baseUrl}/api/frontend/${name}`);
|
||||
}
|
||||
@@ -112,6 +129,16 @@ class Sist2AdminApi {
|
||||
getSist2AdminInfo() {
|
||||
return axios.get(`${this.baseUrl}/api/`);
|
||||
}
|
||||
|
||||
getLogsToDelete(jobName, n) {
|
||||
return axios.get(`${this.baseUrl}/api/job/${jobName}/logs_to_delete`, {
|
||||
params: {n: n}
|
||||
});
|
||||
}
|
||||
|
||||
deleteTaskLogs(taskId) {
|
||||
return axios.post(`${this.baseUrl}/api/task/${taskId}/delete_logs`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new Sist2AdminApi()
|
||||
@@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<label>{{ $t("indexOptions.threads") }}</label>
|
||||
<b-form-input v-model="options.threads" type="number" min="1" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("webOptions.esUrl") }}</label>
|
||||
<b-alert :variant="esTestOk ? 'success' : 'danger'" :show="showEsTestAlert" class="mt-1">
|
||||
{{ esTestMessage }}
|
||||
</b-alert>
|
||||
<b-input-group>
|
||||
<b-form-input v-model="options.es_url" @change="update()"></b-form-input>
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="testEs()">{{ $t("test") }}</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
|
||||
<label>{{ $t("indexOptions.esIndex") }}</label>
|
||||
<b-form-input v-model="options.es_index" @change="update()"></b-form-input>
|
||||
|
||||
<br>
|
||||
<b-form-checkbox v-model="options.es_insecure_ssl" :disabled="!options.es_url.startsWith('https')" @change="update()">
|
||||
{{ $t("webOptions.esInsecure") }}
|
||||
</b-form-checkbox>
|
||||
|
||||
<label>{{ $t("indexOptions.batchSize") }}</label>
|
||||
<b-form-input v-model="options.batch_size" type="number" min="1" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("indexOptions.script") }}</label>
|
||||
<b-form-textarea v-model="options.script" rows="6" @change="update()"></b-form-textarea>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import sist2AdminApi from "@/Sist2AdminApi";
|
||||
|
||||
export default {
|
||||
name: "IndexOptions",
|
||||
props: ["options"],
|
||||
data() {
|
||||
return {
|
||||
showEsTestAlert: false,
|
||||
esTestOk: false,
|
||||
esTestMessage: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update() {
|
||||
this.$emit("change", this.options);
|
||||
},
|
||||
testEs() {
|
||||
sist2AdminApi.pingEs(this.options.es_url, this.options.es_insecure_ssl).then((resp) => {
|
||||
this.showEsTestAlert = true;
|
||||
this.esTestOk = resp.data.ok;
|
||||
this.esTestMessage = resp.data.message;
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -44,8 +44,7 @@ export default {
|
||||
return "";
|
||||
}
|
||||
|
||||
const date = Date.parse(dateString);
|
||||
return moment(date).fromNow();
|
||||
return moment.utc(dateString).local().fromNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,94 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-form-checkbox :checked="desktopNotificationsEnabled" @change="updateNotifications($event)">
|
||||
{{ $t("jobOptions.desktopNotifications") }}
|
||||
</b-form-checkbox>
|
||||
<div>
|
||||
<b-form-checkbox :checked="desktopNotificationsEnabled" @change="updateNotifications($event)">
|
||||
{{ $t("jobOptions.desktopNotifications") }}
|
||||
</b-form-checkbox>
|
||||
|
||||
<b-form-checkbox v-model="job.schedule_enabled" @change="update()">
|
||||
{{ $t("jobOptions.scheduleEnabled") }}
|
||||
</b-form-checkbox>
|
||||
<b-form-checkbox v-model="job.schedule_enabled" @change="update()">
|
||||
{{ $t("jobOptions.scheduleEnabled") }}
|
||||
</b-form-checkbox>
|
||||
|
||||
<label>{{ $t("jobOptions.cron") }}</label>
|
||||
<b-form-input class="text-monospace" :state="cronValid" v-model="job.cron_expression" :disabled="!job.schedule_enabled" @change="update()"></b-form-input>
|
||||
</div>
|
||||
<label>{{ $t("jobOptions.cron") }}</label>
|
||||
<b-form-input class="text-monospace" :state="cronValid" v-model="job.cron_expression"
|
||||
:disabled="!job.schedule_enabled" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("jobOptions.keepNLogs") }}</label>
|
||||
<b-input-group>
|
||||
<b-form-input type="number" v-model="job.keep_last_n_logs" @change="update()"></b-form-input>
|
||||
<b-input-group-append>
|
||||
<b-button variant="danger" @click="onDeleteNowClick()">{{ $t("jobOptions.deleteNow") }}</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Sist2AdminApi from "@/Sist2AdminApi";
|
||||
|
||||
export default {
|
||||
name: "JobOptions",
|
||||
props: ["job"],
|
||||
data() {
|
||||
return {
|
||||
cronValid: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
desktopNotificationsEnabled() {
|
||||
return this.$store.state.jobDesktopNotificationMap[this.job.name];
|
||||
}
|
||||
},
|
||||
name: "JobOptions",
|
||||
props: ["job"],
|
||||
data() {
|
||||
return {
|
||||
cronValid: undefined,
|
||||
logsToDelete: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
desktopNotificationsEnabled() {
|
||||
return this.$store.state.jobDesktopNotificationMap[this.job.name];
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.cronValid = this.checkCron(this.job.cron_expression)
|
||||
this.cronValid = this.checkCron(this.job.cron_expression)
|
||||
},
|
||||
methods: {
|
||||
checkCron(expression) {
|
||||
return /((((\d+,)+\d+|(\d+([/-])\d+)|\d+|\*) ?){5,7})/.test(expression);
|
||||
},
|
||||
updateNotifications(value) {
|
||||
this.$store.dispatch("setJobDesktopNotification", {
|
||||
job: this.job.name,
|
||||
enabled: value
|
||||
});
|
||||
},
|
||||
update() {
|
||||
if (this.job.schedule_enabled) {
|
||||
this.cronValid = this.checkCron(this.job.cron_expression);
|
||||
} else {
|
||||
this.cronValid = undefined;
|
||||
}
|
||||
checkCron(expression) {
|
||||
return /((((\d+,)+\d+|(\d+([/-])\d+)|\d+|\*) ?){5,7})/.test(expression);
|
||||
},
|
||||
updateNotifications(value) {
|
||||
this.$store.dispatch("setJobDesktopNotification", {
|
||||
job: this.job.name,
|
||||
enabled: value
|
||||
});
|
||||
},
|
||||
update() {
|
||||
if (this.job.schedule_enabled) {
|
||||
this.cronValid = this.checkCron(this.job.cron_expression);
|
||||
} else {
|
||||
this.cronValid = undefined;
|
||||
}
|
||||
|
||||
if (this.cronValid !== false) {
|
||||
this.$emit("change", this.job);
|
||||
}
|
||||
if (this.cronValid !== false) {
|
||||
this.$emit("change", this.job);
|
||||
}
|
||||
},
|
||||
onDeleteNowClick() {
|
||||
Sist2AdminApi.getLogsToDelete(this.job.name, this.job.keep_last_n_logs).then(resp => {
|
||||
const toDelete = resp.data;
|
||||
const message = `Delete ${toDelete.length} log files?`;
|
||||
|
||||
this.$bvModal.msgBoxConfirm(message, {
|
||||
title: this.$t("confirmation"),
|
||||
size: "sm",
|
||||
buttonSize: "sm",
|
||||
okVariant: "danger",
|
||||
okTitle: this.$t("delete"),
|
||||
cancelTitle: this.$t("cancel"),
|
||||
footerClass: "p-2",
|
||||
hideHeaderClose: false,
|
||||
centered: true
|
||||
}).then(value => {
|
||||
if (value) {
|
||||
toDelete.forEach(row => {
|
||||
Sist2AdminApi.deleteTaskLogs(row["id"]);
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<b-list-group-item action :to="`/searchBackend/${backend.name}`">
|
||||
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">
|
||||
{{ backend.name }}
|
||||
</h5>
|
||||
|
||||
<div>
|
||||
<b-badge v-if="backend.backend_type === 'sqlite'" variant="info">SQLite</b-badge>
|
||||
<b-badge v-else variant="info">Elasticsearch</b-badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</b-list-group-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "SearchBackendListItem",
|
||||
props: ["backend"],
|
||||
}
|
||||
</script>
|
||||
37
sist2-admin/frontend/src/components/SearchBackendSelect.vue
Normal file
37
sist2-admin/frontend/src/components/SearchBackendSelect.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<b-progress v-if="loading" striped animated value="100"></b-progress>
|
||||
<div v-else>
|
||||
<label>{{$t("backendOptions.searchBackend")}}</label>
|
||||
<b-select :options="options" :value="value" @change="$emit('change', $event)"></b-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sist2AdminApi from "@/Sist2AdminApi";
|
||||
|
||||
export default {
|
||||
name: "SearchBackendSelect",
|
||||
props: ["value"],
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
backends: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
options() {
|
||||
return this.backends.map(backend => backend.name)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
Sist2AdminApi.getSearchBackends().then(resp => {
|
||||
this.loading = false;
|
||||
this.backends = resp.data
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,56 +1,35 @@
|
||||
<template>
|
||||
<div>
|
||||
<label>{{ $t("webOptions.esUrl") }}</label>
|
||||
<b-alert :variant="esTestOk ? 'success' : 'danger'" :show="showEsTestAlert" class="mt-1">
|
||||
{{ esTestMessage }}
|
||||
</b-alert>
|
||||
<div>
|
||||
|
||||
<b-input-group>
|
||||
<b-form-input v-model="options.es_url" @change="update()"></b-form-input>
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="testEs()">{{ $t("test") }}</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
<label>{{ $t("webOptions.lang") }}</label>
|
||||
<b-form-select v-model="options.lang" :options="['en', 'fr', 'zh-CN']" @change="update()"></b-form-select>
|
||||
|
||||
<b-form-checkbox v-model="options.es_insecure_ssl" :disabled="!this.options.es_url.startsWith('https')" @change="update()">
|
||||
{{ $t("webOptions.esInsecure") }}
|
||||
</b-form-checkbox>
|
||||
<label>{{ $t("webOptions.bind") }}</label>
|
||||
<b-form-input v-model="options.bind" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("webOptions.esIndex") }}</label>
|
||||
<b-form-input v-model="options.es_index" @change="update()"></b-form-input>
|
||||
<label>{{ $t("webOptions.tagline") }}</label>
|
||||
<b-form-textarea v-model="options.tagline" @change="update()"></b-form-textarea>
|
||||
|
||||
<label>{{ $t("webOptions.lang") }}</label>
|
||||
<b-form-select v-model="options.lang" :options="['en', 'fr', 'zh-CN']" @change="update()"></b-form-select>
|
||||
<label>{{ $t("webOptions.auth") }}</label>
|
||||
<b-form-input v-model="options.auth" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("webOptions.bind") }}</label>
|
||||
<b-form-input v-model="options.bind" @change="update()"></b-form-input>
|
||||
<label>{{ $t("webOptions.tagAuth") }}</label>
|
||||
<b-form-input v-model="options.tag_auth" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("webOptions.tagline") }}</label>
|
||||
<b-form-textarea v-model="options.tagline" @change="update()"></b-form-textarea>
|
||||
<br>
|
||||
<h5>Auth0 options</h5>
|
||||
<label>{{ $t("webOptions.auth0Audience") }}</label>
|
||||
<b-form-input v-model="options.auth0_audience" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("webOptions.auth") }}</label>
|
||||
<b-form-input v-model="options.auth" @change="update()"></b-form-input>
|
||||
<label>{{ $t("webOptions.auth0Domain") }}</label>
|
||||
<b-form-input v-model="options.auth0_domain" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("webOptions.tagAuth") }}</label>
|
||||
<b-form-input v-model="options.tag_auth" @change="update()"></b-form-input>
|
||||
<label>{{ $t("webOptions.auth0ClientId") }}</label>
|
||||
<b-form-input v-model="options.auth0_client_id" @change="update()"></b-form-input>
|
||||
|
||||
<br>
|
||||
<h5>Auth0 options</h5>
|
||||
<label>{{ $t("webOptions.auth0Audience") }}</label>
|
||||
<b-form-input v-model="options.auth0_audience" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("webOptions.auth0Domain") }}</label>
|
||||
<b-form-input v-model="options.auth0_domain" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("webOptions.auth0ClientId") }}</label>
|
||||
<b-form-input v-model="options.auth0_client_id" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("webOptions.auth0PublicKey") }}</label>
|
||||
<b-textarea rows="10" v-model="options.auth0_public_key" @change="update()"></b-textarea>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<label>{{ $t("webOptions.auth0PublicKey") }}</label>
|
||||
<b-textarea rows="10" v-model="options.auth0_public_key" @change="update()"></b-textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -58,31 +37,24 @@
|
||||
import sist2AdminApi from "@/Sist2AdminApi";
|
||||
|
||||
export default {
|
||||
name: "WebOptions",
|
||||
props: ["options", "frontendName"],
|
||||
data() {
|
||||
return {
|
||||
showEsTestAlert: false,
|
||||
esTestOk: false,
|
||||
esTestMessage: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update() {
|
||||
if (!this.options.es_url.startsWith("https")) {
|
||||
this.options.es_insecure_ssl = false;
|
||||
}
|
||||
|
||||
this.$emit("change", this.options);
|
||||
name: "WebOptions",
|
||||
props: ["options", "frontendName"],
|
||||
data() {
|
||||
return {
|
||||
showEsTestAlert: false,
|
||||
esTestOk: false,
|
||||
esTestMessage: "",
|
||||
}
|
||||
},
|
||||
testEs() {
|
||||
sist2AdminApi.pingEs(this.options.es_url, this.options.es_insecure_ssl).then((resp) => {
|
||||
this.showEsTestAlert = true;
|
||||
this.esTestOk = resp.data.ok;
|
||||
this.esTestMessage = resp.data.message;
|
||||
});
|
||||
methods: {
|
||||
update() {
|
||||
if (!this.options.es_url.startsWith("https")) {
|
||||
this.options.es_insecure_ssl = false;
|
||||
}
|
||||
|
||||
this.$emit("change", this.options);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -5,10 +5,13 @@ export default {
|
||||
go: "Go",
|
||||
online: "online",
|
||||
offline: "offline",
|
||||
view: "View",
|
||||
delete: "Delete",
|
||||
runNow: "Index now",
|
||||
create: "Create",
|
||||
cancel: "Cancel",
|
||||
test: "Test",
|
||||
confirmation: "Confirmation",
|
||||
|
||||
jobTitle: "job configuration",
|
||||
tasks: "Tasks",
|
||||
@@ -45,12 +48,13 @@ export default {
|
||||
extraQueryArgs: "Extra query arguments when launching from sist2-admin",
|
||||
customUrl: "Custom URL when launching from sist2-admin",
|
||||
|
||||
searchBackends: "Search backends",
|
||||
searchBackendTitle: "search backend configuration",
|
||||
newBackendName: "New search backend name",
|
||||
|
||||
selectJobs: "Select jobs",
|
||||
webOptions: {
|
||||
title: "Web options",
|
||||
esUrl: "Elasticsearch URL",
|
||||
esIndex: "Elasticsearch index name",
|
||||
esInsecure: "Do not verify SSL connections to Elasticsearch.",
|
||||
lang: "UI Language",
|
||||
bind: "Listen address",
|
||||
tagline: "Tagline in navbar",
|
||||
@@ -61,6 +65,18 @@ export default {
|
||||
auth0ClientId: "Auth0 client ID",
|
||||
auth0PublicKey: "Auth0 public key",
|
||||
},
|
||||
backendOptions: {
|
||||
title: "Search backend options",
|
||||
searchBackend: "Search backend",
|
||||
type: "Search backend type",
|
||||
esUrl: "Elasticsearch URL",
|
||||
esIndex: "Elasticsearch index name",
|
||||
esInsecure: "Do not verify SSL connections to Elasticsearch.",
|
||||
threads: "Number of threads",
|
||||
batchSize: "Index batch size",
|
||||
script: "User script",
|
||||
searchIndex: "Search index file location"
|
||||
},
|
||||
scanOptions: {
|
||||
title: "Scanning options",
|
||||
path: "Path",
|
||||
@@ -87,20 +103,14 @@ export default {
|
||||
treemapThreshold: "Relative size threshold for treemap",
|
||||
optimizeIndex: "Defragment index file after scan to reduce its file size."
|
||||
},
|
||||
indexOptions: {
|
||||
title: "Indexing options",
|
||||
threads: "Number of threads",
|
||||
esUrl: "Elasticsearch URL",
|
||||
esIndex: "Elasticsearch index name",
|
||||
esInsecure: "Do not verify SSL connections to Elasticsearch.",
|
||||
batchSize: "Index batch size",
|
||||
script: "User script"
|
||||
},
|
||||
jobOptions: {
|
||||
title: "Job options",
|
||||
cron: "Job schedule",
|
||||
keepNLogs: "Keep last N log files. Set to -1 to keep all logs.",
|
||||
deleteNow: "Delete now",
|
||||
scheduleEnabled: "Enable scheduled re-scan",
|
||||
noJobAvailable: "No jobs available.",
|
||||
noBackendError: "You must select a search backend to run this job",
|
||||
desktopNotifications: "Desktop notifications"
|
||||
},
|
||||
frontendOptions: {
|
||||
|
||||
@@ -5,6 +5,7 @@ import Job from "@/views/Job";
|
||||
import Tasks from "@/views/Tasks";
|
||||
import Frontend from "@/views/Frontend";
|
||||
import Tail from "@/views/Tail";
|
||||
import SearchBackend from "@/views/SearchBackend.vue";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
@@ -29,6 +30,11 @@ const routes = [
|
||||
name: "Frontend",
|
||||
component: Frontend
|
||||
},
|
||||
{
|
||||
path: "/searchBackend/:name",
|
||||
name: "SearchBackend",
|
||||
component: SearchBackend
|
||||
},
|
||||
{
|
||||
path: "/log/:taskId",
|
||||
name: "Tail",
|
||||
|
||||
@@ -1,60 +1,70 @@
|
||||
<template>
|
||||
<b-card>
|
||||
<b-card-title>
|
||||
{{ name }}
|
||||
<small style="vertical-align: top">
|
||||
<b-badge v-if="!loading && frontend.running" variant="success">{{ $t("online") }}</b-badge>
|
||||
<b-badge v-else-if="!loading" variant="secondary">{{ $t("offline") }}</b-badge>
|
||||
</small>
|
||||
</b-card-title>
|
||||
<b-card>
|
||||
<b-card-title>
|
||||
{{ name }}
|
||||
<small style="vertical-align: top">
|
||||
<b-badge v-if="!loading && frontend.running" variant="success">{{ $t("online") }}</b-badge>
|
||||
<b-badge v-else-if="!loading" variant="secondary">{{ $t("offline") }}</b-badge>
|
||||
</small>
|
||||
</b-card-title>
|
||||
|
||||
<div class="mb-3" v-if="!loading">
|
||||
<b-button class="mr-1" :disabled="frontend.running || !valid" variant="success" @click="start()">{{
|
||||
$t("start")
|
||||
}}
|
||||
</b-button>
|
||||
<b-button class="mr-1" :disabled="!frontend.running" variant="danger" @click="stop()">{{
|
||||
$t("stop")
|
||||
}}
|
||||
</b-button>
|
||||
<b-button class="mr-1" :disabled="!frontend.running" variant="primary" :href="frontendUrl" target="_blank">
|
||||
{{ $t("go") }}
|
||||
</b-button>
|
||||
<b-button variant="danger" @click="deleteFrontend()">{{ $t("delete") }}</b-button>
|
||||
</div>
|
||||
<div class="mb-3" v-if="!loading">
|
||||
<b-button class="mr-1" :disabled="frontend.running || !valid" variant="success" @click="start()">{{
|
||||
$t("start")
|
||||
}}
|
||||
</b-button>
|
||||
<b-button class="mr-1" :disabled="!frontend.running" variant="danger" @click="stop()">{{
|
||||
$t("stop")
|
||||
}}
|
||||
</b-button>
|
||||
<b-button class="mr-1" :disabled="!frontend.running" variant="primary" :href="frontendUrl" target="_blank">
|
||||
{{ $t("go") }}
|
||||
</b-button>
|
||||
<b-button variant="danger" @click="deleteFrontend()">{{ $t("delete") }}</b-button>
|
||||
</div>
|
||||
|
||||
|
||||
<b-progress v-if="loading" striped animated value="100"></b-progress>
|
||||
<b-card-body v-else>
|
||||
<b-progress v-if="loading" striped animated value="100"></b-progress>
|
||||
<b-card-body v-else>
|
||||
|
||||
<h4>{{ $t("frontendOptions.title") }}</h4>
|
||||
<b-card>
|
||||
<b-form-checkbox v-model="frontend.auto_start" @change="update()">
|
||||
{{ $t("autoStart") }}
|
||||
</b-form-checkbox>
|
||||
<h4>{{ $t("frontendOptions.title") }}</h4>
|
||||
<b-card>
|
||||
<b-form-checkbox v-model="frontend.auto_start" @change="update()">
|
||||
{{ $t("autoStart") }}
|
||||
</b-form-checkbox>
|
||||
|
||||
<label>{{ $t("extraQueryArgs") }}</label>
|
||||
<b-form-input v-model="frontend.extra_query_args" @change="update()"></b-form-input>
|
||||
<label>{{ $t("extraQueryArgs") }}</label>
|
||||
<b-form-input v-model="frontend.extra_query_args" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("customUrl") }}</label>
|
||||
<b-form-input v-model="frontend.custom_url" @change="update()" placeholder="http://"></b-form-input>
|
||||
<label>{{ $t("customUrl") }}</label>
|
||||
<b-form-input v-model="frontend.custom_url" @change="update()" placeholder="http://"></b-form-input>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<b-alert v-if="!valid" variant="warning" show>{{ $t("frontendOptions.noJobSelectedWarning") }}</b-alert>
|
||||
<b-alert v-if="!valid" variant="warning" show>{{ $t("frontendOptions.noJobSelectedWarning") }}</b-alert>
|
||||
|
||||
<JobCheckboxGroup :frontend="frontend" @input="update()"></JobCheckboxGroup>
|
||||
</b-card>
|
||||
<JobCheckboxGroup :frontend="frontend" @input="update()"></JobCheckboxGroup>
|
||||
</b-card>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h4>{{ $t("webOptions.title") }}</h4>
|
||||
<b-card>
|
||||
<WebOptions :options="frontend.web_options" :frontend-name="$route.params.name" @change="update()"></WebOptions>
|
||||
</b-card>
|
||||
</b-card-body>
|
||||
<h4>{{ $t("webOptions.title") }}</h4>
|
||||
<b-card>
|
||||
<WebOptions :options="frontend.web_options" :frontend-name="$route.params.name"
|
||||
@change="update()"></WebOptions>
|
||||
</b-card>
|
||||
|
||||
</b-card>
|
||||
<br>
|
||||
|
||||
<h4>{{ $t("backendOptions.title") }}</h4>
|
||||
<b-card>
|
||||
<SearchBackendSelect :value="frontend.web_options.search_backend"
|
||||
@change="onBackendSelect($event)"></SearchBackendSelect>
|
||||
</b-card>
|
||||
</b-card-body>
|
||||
|
||||
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -62,68 +72,73 @@
|
||||
import Sist2AdminApi from "@/Sist2AdminApi";
|
||||
import JobCheckboxGroup from "@/components/JobCheckboxGroup";
|
||||
import WebOptions from "@/components/WebOptions";
|
||||
import SearchBackendSelect from "@/components/SearchBackendSelect.vue";
|
||||
|
||||
export default {
|
||||
name: 'Frontend',
|
||||
components: {JobCheckboxGroup, WebOptions},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
frontend: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
valid() {
|
||||
return !this.loading && this.frontend.jobs.length > 0;
|
||||
name: 'Frontend',
|
||||
components: {SearchBackendSelect, JobCheckboxGroup, WebOptions},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
frontend: null,
|
||||
}
|
||||
},
|
||||
frontendUrl() {
|
||||
if (this.frontend.custom_url) {
|
||||
return this.frontend.custom_url + this.args;
|
||||
}
|
||||
computed: {
|
||||
valid() {
|
||||
return !this.loading && this.frontend.jobs.length > 0;
|
||||
},
|
||||
frontendUrl() {
|
||||
if (this.frontend.custom_url) {
|
||||
return this.frontend.custom_url + this.args;
|
||||
}
|
||||
|
||||
if (this.frontend.web_options.bind.startsWith("0.0.0.0")) {
|
||||
return window.location.protocol + "//" + window.location.hostname + ":" + this.port + this.args;
|
||||
}
|
||||
if (this.frontend.web_options.bind.startsWith("0.0.0.0")) {
|
||||
return window.location.protocol + "//" + window.location.hostname + ":" + this.port + this.args;
|
||||
}
|
||||
|
||||
return window.location.protocol + "//" + this.frontend.web_options.bind + this.args;
|
||||
return window.location.protocol + "//" + this.frontend.web_options.bind + this.args;
|
||||
},
|
||||
name() {
|
||||
return this.$route.params.name;
|
||||
},
|
||||
port() {
|
||||
return this.frontend.web_options.bind.split(":")[1]
|
||||
},
|
||||
args() {
|
||||
const args = this.frontend.extra_query_args;
|
||||
if (args !== "") {
|
||||
return "#" + (args.startsWith("?") ? (args) : ("?" + args));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
},
|
||||
name() {
|
||||
return this.$route.params.name;
|
||||
mounted() {
|
||||
Sist2AdminApi.getFrontend(this.name).then(resp => {
|
||||
this.frontend = resp.data;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
port() {
|
||||
return this.frontend.web_options.bind.split(":")[1]
|
||||
},
|
||||
args() {
|
||||
const args = this.frontend.extra_query_args;
|
||||
if (args !== "") {
|
||||
return "#" + (args.startsWith("?") ? (args) : ("?" + args));
|
||||
}
|
||||
return "";
|
||||
methods: {
|
||||
start() {
|
||||
this.frontend.running = true;
|
||||
Sist2AdminApi.startFrontend(this.name)
|
||||
},
|
||||
stop() {
|
||||
this.frontend.running = false;
|
||||
Sist2AdminApi.stopFrontend(this.name)
|
||||
},
|
||||
deleteFrontend() {
|
||||
Sist2AdminApi.deleteFrontend(this.name).then(() => {
|
||||
this.$router.push("/");
|
||||
});
|
||||
},
|
||||
update() {
|
||||
Sist2AdminApi.updateFrontend(this.name, this.frontend);
|
||||
},
|
||||
onBackendSelect(backend) {
|
||||
this.frontend.web_options.search_backend = backend;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
Sist2AdminApi.getFrontend(this.name).then(resp => {
|
||||
this.frontend = resp.data;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
start() {
|
||||
this.frontend.running = true;
|
||||
Sist2AdminApi.startFrontend(this.name)
|
||||
},
|
||||
stop() {
|
||||
this.frontend.running = false;
|
||||
Sist2AdminApi.stopFrontend(this.name)
|
||||
},
|
||||
deleteFrontend() {
|
||||
Sist2AdminApi.deleteFrontend(this.name).then(() => {
|
||||
this.$router.push("/frontends");
|
||||
});
|
||||
},
|
||||
update() {
|
||||
Sist2AdminApi.updateFrontend(this.name, this.frontend);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,60 +1,89 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-card>
|
||||
<b-card-title>{{ $t("jobs") }}</b-card-title>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-input id="new-job" v-model="newJobName" :placeholder="$t('newJobName')"></b-input>
|
||||
<b-popover
|
||||
:show.sync="showHelp"
|
||||
target="new-job"
|
||||
placement="top"
|
||||
triggers="manual"
|
||||
variant="primary"
|
||||
:content="$t('newJobHelp')"
|
||||
></b-popover>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-button variant="primary" @click="createJob()" :disabled="!jobNameValid(newJobName)">{{ $t("create") }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<div>
|
||||
<b-card>
|
||||
<b-card-title>{{ $t("jobs") }}</b-card-title>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-input id="new-job" v-model="newJobName" :placeholder="$t('newJobName')"></b-input>
|
||||
<b-popover
|
||||
:show.sync="showHelp"
|
||||
target="new-job"
|
||||
placement="top"
|
||||
triggers="manual"
|
||||
variant="primary"
|
||||
:content="$t('newJobHelp')"
|
||||
></b-popover>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-button variant="primary" @click="createJob()" :disabled="!jobNameValid(newJobName)">
|
||||
{{ $t("create") }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<hr/>
|
||||
<hr/>
|
||||
|
||||
<b-progress v-if="jobsLoading" striped animated value="100"></b-progress>
|
||||
<b-list-group v-else>
|
||||
<JobListItem v-for="job in jobs" :key="job.name" :job="job"></JobListItem>
|
||||
</b-list-group>
|
||||
</b-card>
|
||||
<b-progress v-if="jobsLoading" striped animated value="100"></b-progress>
|
||||
<b-list-group v-else>
|
||||
<JobListItem v-for="job in jobs" :key="job.name" :job="job"></JobListItem>
|
||||
</b-list-group>
|
||||
</b-card>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<b-card>
|
||||
<b-card>
|
||||
|
||||
<b-card-title>{{ $t("frontends") }}</b-card-title>
|
||||
<b-card-title>{{ $t("frontends") }}</b-card-title>
|
||||
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-input v-model="newFrontendName" :placeholder="$t('newFrontendName')"></b-input>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-button variant="primary" @click="createFrontend()" :disabled="!frontendNameValid(newFrontendName)">
|
||||
{{ $t("create") }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-input v-model="newFrontendName" :placeholder="$t('newFrontendName')"></b-input>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-button variant="primary" @click="createFrontend()"
|
||||
:disabled="!frontendNameValid(newFrontendName)">
|
||||
{{ $t("create") }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<hr/>
|
||||
<hr/>
|
||||
|
||||
<b-progress v-if="frontendsLoading" striped animated value="100"></b-progress>
|
||||
<b-list-group v-else>
|
||||
<FrontendListItem v-for="frontend in frontends"
|
||||
:key="frontend.name" :frontend="frontend"></FrontendListItem>
|
||||
</b-list-group>
|
||||
<b-progress v-if="frontendsLoading" striped animated value="100"></b-progress>
|
||||
<b-list-group v-else>
|
||||
<FrontendListItem v-for="frontend in frontends"
|
||||
:key="frontend.name" :frontend="frontend"></FrontendListItem>
|
||||
</b-list-group>
|
||||
|
||||
</b-card>
|
||||
</div>
|
||||
</b-card>
|
||||
|
||||
<br/>
|
||||
|
||||
<b-card>
|
||||
<b-card-title>{{ $t("searchBackends") }}</b-card-title>
|
||||
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-input v-model="newBackendName" :placeholder="$t('newBackendName')"></b-input>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-button variant="primary" @click="createBackend()"
|
||||
:disabled="!backendNameValid(newBackendName)">
|
||||
{{ $t("create") }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<hr/>
|
||||
|
||||
<b-progress v-if="backendsLoading" striped animated value="100"></b-progress>
|
||||
<b-list-group v-else>
|
||||
<SearchBackendListItem v-for="backend in backends"
|
||||
:key="backend.name" :backend="backend"></SearchBackendListItem>
|
||||
</b-list-group>
|
||||
|
||||
</b-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -62,61 +91,80 @@ import JobListItem from "@/components/JobListItem";
|
||||
import {formatBindAddress} from "@/util";
|
||||
import Sist2AdminApi from "@/Sist2AdminApi";
|
||||
import FrontendListItem from "@/components/FrontendListItem";
|
||||
import SearchBackendListItem from "@/components/SearchBackendListItem.vue";
|
||||
|
||||
export default {
|
||||
name: "Jobs",
|
||||
components: {JobListItem, FrontendListItem},
|
||||
data() {
|
||||
return {
|
||||
jobsLoading: true,
|
||||
newJobName: "",
|
||||
jobs: [],
|
||||
name: "Jobs",
|
||||
components: {SearchBackendListItem, JobListItem, FrontendListItem},
|
||||
data() {
|
||||
return {
|
||||
jobsLoading: true,
|
||||
newJobName: "",
|
||||
jobs: [],
|
||||
|
||||
frontendsLoading: true,
|
||||
frontends: [],
|
||||
formatBindAddress,
|
||||
newFrontendName: "",
|
||||
frontendsLoading: true,
|
||||
frontends: [],
|
||||
formatBindAddress,
|
||||
newFrontendName: "",
|
||||
|
||||
showHelp: false
|
||||
backends: [],
|
||||
backendsLoading: true,
|
||||
newBackendName: "",
|
||||
|
||||
showHelp: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loading = true;
|
||||
this.reload();
|
||||
},
|
||||
methods: {
|
||||
jobNameValid(name) {
|
||||
if (this.jobs.some(job => job.name === name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return /^[a-zA-Z0-9-_,.; ]+$/.test(name);
|
||||
},
|
||||
frontendNameValid(name) {
|
||||
if (this.frontends.some(frontend => frontend.name === name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return /^[a-zA-Z0-9-_,.; ]+$/.test(name);
|
||||
},
|
||||
backendNameValid(name) {
|
||||
if (this.backends.some(backend => backend.name === name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return /^[a-zA-Z0-9-_,.; ]+$/.test(name);
|
||||
},
|
||||
reload() {
|
||||
Sist2AdminApi.getJobs().then(resp => {
|
||||
this.jobs = resp.data;
|
||||
this.jobsLoading = false;
|
||||
|
||||
this.showHelp = this.jobs.length === 0;
|
||||
});
|
||||
Sist2AdminApi.getFrontends().then(resp => {
|
||||
this.frontends = resp.data;
|
||||
this.frontendsLoading = false;
|
||||
});
|
||||
Sist2AdminApi.getSearchBackends().then(resp => {
|
||||
this.backends = resp.data;
|
||||
this.backendsLoading = false;
|
||||
})
|
||||
},
|
||||
createJob() {
|
||||
Sist2AdminApi.createJob(this.newJobName).then(this.reload);
|
||||
},
|
||||
createFrontend() {
|
||||
Sist2AdminApi.createFrontend(this.newFrontendName).then(this.reload)
|
||||
},
|
||||
createBackend() {
|
||||
Sist2AdminApi.createBackend(this.newBackendName).then(this.reload);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loading = true;
|
||||
this.reload();
|
||||
},
|
||||
methods: {
|
||||
jobNameValid(name) {
|
||||
if (this.jobs.some(job => job.name === name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return /^[a-zA-Z0-9-_,.; ]+$/.test(name);
|
||||
},
|
||||
frontendNameValid(name) {
|
||||
if (this.frontends.some(job => job.name === name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return /^[a-zA-Z0-9-_,.; ]+$/.test(name);
|
||||
},
|
||||
reload() {
|
||||
Sist2AdminApi.getJobs().then(resp => {
|
||||
this.jobs = resp.data;
|
||||
this.jobsLoading = false;
|
||||
|
||||
this.showHelp = this.jobs.length === 0;
|
||||
});
|
||||
Sist2AdminApi.getFrontends().then(resp => {
|
||||
this.frontends = resp.data;
|
||||
this.frontendsLoading = false;
|
||||
});
|
||||
},
|
||||
createJob() {
|
||||
Sist2AdminApi.createJob(this.newJobName).then(this.reload);
|
||||
},
|
||||
createFrontend() {
|
||||
Sist2AdminApi.createFrontend(this.newFrontendName).then(this.reload)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,92 +1,112 @@
|
||||
<template>
|
||||
<b-card>
|
||||
<b-card-title>
|
||||
[{{ getName() }}]
|
||||
{{ $t("jobTitle") }}
|
||||
</b-card-title>
|
||||
<b-card>
|
||||
<b-card-title>
|
||||
[{{ getName() }}]
|
||||
{{ $t("jobTitle") }}
|
||||
</b-card-title>
|
||||
|
||||
<div class="mb-3">
|
||||
<b-button class="mr-1" variant="primary" @click="runJob()">{{ $t("runNow") }}</b-button>
|
||||
<b-button variant="danger" @click="deleteJob()">{{ $t("delete") }}</b-button>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<b-button class="mr-1" variant="primary" @click="runJob()" :disabled="!valid">{{ $t("runNow") }}</b-button>
|
||||
<b-button variant="danger" @click="deleteJob()">{{ $t("delete") }}</b-button>
|
||||
</div>
|
||||
|
||||
<div v-if="job">
|
||||
{{ $t("status") }}: <code>{{ job.status }}</code>
|
||||
</div>
|
||||
<div v-if="job">
|
||||
{{ $t("status") }}: <code>{{ job.status }}</code>
|
||||
</div>
|
||||
|
||||
<b-progress v-if="loading" striped animated value="100"></b-progress>
|
||||
<b-card-body v-else>
|
||||
<b-progress v-if="loading" striped animated value="100"></b-progress>
|
||||
<b-card-body v-else>
|
||||
|
||||
<h4>{{ $t("jobOptions.title") }}</h4>
|
||||
<b-card>
|
||||
<JobOptions :job="job" @change="update"></JobOptions>
|
||||
</b-card>
|
||||
<h4>{{ $t("jobOptions.title") }}</h4>
|
||||
<b-card>
|
||||
<JobOptions :job="job" @change="update"></JobOptions>
|
||||
</b-card>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h4>{{ $t("scanOptions.title") }}</h4>
|
||||
<b-card>
|
||||
<ScanOptions :options="job.scan_options" @change="update()"></ScanOptions>
|
||||
</b-card>
|
||||
<h4>{{ $t("scanOptions.title") }}</h4>
|
||||
<b-card>
|
||||
<ScanOptions :options="job.scan_options" @change="update()"></ScanOptions>
|
||||
</b-card>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h4>{{ $t("indexOptions.title") }}</h4>
|
||||
<b-card>
|
||||
<IndexOptions :options="job.index_options" @change="update()"></IndexOptions>
|
||||
</b-card>
|
||||
<h4>{{ $t("backendOptions.title") }}</h4>
|
||||
<b-card>
|
||||
<b-alert v-if="!valid" variant="warning" show>{{ $t("jobOptions.noBackendError") }}</b-alert>
|
||||
<SearchBackendSelect :value="job.index_options.search_backend"
|
||||
@change="onBackendSelect($event)"></SearchBackendSelect>
|
||||
</b-card>
|
||||
|
||||
</b-card-body>
|
||||
</b-card-body>
|
||||
|
||||
</b-card>
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ScanOptions from "@/components/ScanOptions";
|
||||
import Sist2AdminApi from "@/Sist2AdminApi";
|
||||
import IndexOptions from "@/components/IndexOptions";
|
||||
import JobOptions from "@/components/JobOptions";
|
||||
import SearchBackendSelect from "@/components/SearchBackendSelect.vue";
|
||||
|
||||
export default {
|
||||
name: "Job",
|
||||
components: {
|
||||
IndexOptions,
|
||||
ScanOptions,
|
||||
JobOptions
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
job: null
|
||||
name: "Job",
|
||||
components: {
|
||||
SearchBackendSelect,
|
||||
ScanOptions,
|
||||
JobOptions
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
job: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getName() {
|
||||
return this.$route.params.name;
|
||||
},
|
||||
update() {
|
||||
Sist2AdminApi.updateJob(this.getName(), this.job);
|
||||
},
|
||||
runJob() {
|
||||
Sist2AdminApi.runJob(this.getName()).then(() => {
|
||||
this.$bvToast.toast(this.$t("runJobConfirmation"), {
|
||||
title: this.$t("runJobConfirmationTitle"),
|
||||
variant: "success",
|
||||
toaster: "b-toaster-bottom-right"
|
||||
});
|
||||
});
|
||||
},
|
||||
deleteJob() {
|
||||
Sist2AdminApi.deleteJob(this.getName())
|
||||
.then(() => {
|
||||
this.$router.push("/");
|
||||
})
|
||||
.catch(err => {
|
||||
this.$bvToast.toast("Cannot delete job " +
|
||||
"because it is referenced by a frontend", {
|
||||
title: "Error",
|
||||
variant: "danger",
|
||||
toaster: "b-toaster-bottom-right"
|
||||
});
|
||||
})
|
||||
},
|
||||
onBackendSelect(backend) {
|
||||
this.job.index_options.search_backend = backend;
|
||||
this.update();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
Sist2AdminApi.getJob(this.getName()).then(resp => {
|
||||
this.loading = false;
|
||||
this.job = resp.data;
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
valid() {
|
||||
return this.job?.index_options.search_backend != null;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getName() {
|
||||
return this.$route.params.name;
|
||||
},
|
||||
update() {
|
||||
Sist2AdminApi.updateJob(this.getName(), this.job);
|
||||
},
|
||||
runJob() {
|
||||
Sist2AdminApi.runJob(this.getName()).then(() => {
|
||||
this.$bvToast.toast(this.$t("runJobConfirmation"), {
|
||||
title: this.$t("runJobConfirmationTitle"),
|
||||
variant: "success",
|
||||
toaster: "b-toaster-bottom-right"
|
||||
});
|
||||
});
|
||||
},
|
||||
deleteJob() {
|
||||
Sist2AdminApi.deleteJob(this.getName()).then(() => {
|
||||
this.$router.push("/");
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
Sist2AdminApi.getJob(this.getName()).then(resp => {
|
||||
this.loading = false;
|
||||
this.job = resp.data;
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
126
sist2-admin/frontend/src/views/SearchBackend.vue
Normal file
126
sist2-admin/frontend/src/views/SearchBackend.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
|
||||
<b-card>
|
||||
<b-card-title>
|
||||
<span class="text-monospace">{{ getName() }}</span>
|
||||
{{ $t("searchBackendTitle") }}
|
||||
</b-card-title>
|
||||
|
||||
<div class="mb-3">
|
||||
<b-button variant="danger" @click="deleteBackend()">{{ $t("delete") }}</b-button>
|
||||
</div>
|
||||
|
||||
<b-progress v-if="loading" striped animated value="100"></b-progress>
|
||||
<b-card-body v-else>
|
||||
|
||||
<label>{{ $t("backendOptions.type") }}</label>
|
||||
<b-select :options="backendTypeOptions" v-model="backend.backend_type" @change="update()"></b-select>
|
||||
|
||||
<hr/>
|
||||
|
||||
<template v-if="backend.backend_type === 'elasticsearch'">
|
||||
<b-alert :variant="esTestOk ? 'success' : 'danger'" :show="showEsTestAlert" class="mt-1">
|
||||
{{ esTestMessage }}
|
||||
</b-alert>
|
||||
|
||||
<label>{{ $t("backendOptions.esUrl") }}</label>
|
||||
<b-input-group>
|
||||
<b-form-input v-model="backend.es_url" @change="update()"></b-form-input>
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="testEs()">{{ $t("test") }}</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
|
||||
<b-form-checkbox v-model="backend.es_insecure_ssl" :disabled="!this.backend.es_url.startsWith('https')"
|
||||
@change="update()">
|
||||
{{ $t("backendOptions.esInsecure") }}
|
||||
</b-form-checkbox>
|
||||
|
||||
<label>{{ $t("backendOptions.esIndex") }}</label>
|
||||
<b-form-input v-model="backend.es_index" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("backendOptions.threads") }}</label>
|
||||
<b-form-input v-model="backend.threads" type="number" min="1" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("backendOptions.batchSize") }}</label>
|
||||
<b-form-input v-model="backend.batch_size" type="number" min="1" @change="update()"></b-form-input>
|
||||
|
||||
<label>{{ $t("backendOptions.script") }}</label>
|
||||
<b-form-textarea v-model="backend.script" rows="6" @change="update()"></b-form-textarea>
|
||||
</template>
|
||||
<template v-else>
|
||||
<label>{{ $t("backendOptions.searchIndex") }}</label>
|
||||
<b-form-input v-model="backend.search_index" disabled></b-form-input>
|
||||
</template>
|
||||
</b-card-body>
|
||||
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import sist2AdminApi from "@/Sist2AdminApi";
|
||||
import Sist2AdminApi from "@/Sist2AdminApi";
|
||||
|
||||
export default {
|
||||
name: "SearchBackend",
|
||||
data() {
|
||||
return {
|
||||
showEsTestAlert: false,
|
||||
esTestOk: false,
|
||||
esTestMessage: "",
|
||||
loading: true,
|
||||
backend: null,
|
||||
backendTypeOptions: [
|
||||
{
|
||||
text: "Elasticsearch",
|
||||
value: "elasticsearch"
|
||||
},
|
||||
{
|
||||
text: "SQLite",
|
||||
value: "sqlite"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
Sist2AdminApi.getSearchBackend(this.getName()).then(resp => {
|
||||
this.backend = resp.data;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
getName() {
|
||||
return this.$route.params.name;
|
||||
},
|
||||
testEs() {
|
||||
sist2AdminApi.pingEs(this.backend.es_url, this.backend.es_insecure_ssl)
|
||||
.then((resp) => {
|
||||
this.showEsTestAlert = true;
|
||||
this.esTestOk = resp.data.ok;
|
||||
this.esTestMessage = resp.data.message;
|
||||
});
|
||||
},
|
||||
update() {
|
||||
Sist2AdminApi.updateSearchBackend(this.getName(), this.backend);
|
||||
},
|
||||
deleteBackend() {
|
||||
Sist2AdminApi.deleteBackend(this.getName())
|
||||
.then(() => {
|
||||
this.$router.push("/");
|
||||
})
|
||||
.catch(err => {
|
||||
this.$bvToast.toast("Cannot delete search backend " +
|
||||
"because it is referenced by a job or frontend", {
|
||||
title: "Error",
|
||||
variant: "danger",
|
||||
toaster: "b-toaster-bottom-right"
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,38 +1,49 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
|
||||
<b-card v-if="tasks.length > 0">
|
||||
<h2>{{ $t("runningTasks") }}</h2>
|
||||
<b-list-group>
|
||||
<TaskListItem v-for="task in tasks" :key="task.id" :task="task"></TaskListItem>
|
||||
</b-list-group>
|
||||
</b-card>
|
||||
<b-card v-if="tasks.length > 0">
|
||||
<h2>{{ $t("runningTasks") }}</h2>
|
||||
<b-list-group>
|
||||
<TaskListItem v-for="task in tasks" :key="task.id" :task="task"></TaskListItem>
|
||||
</b-list-group>
|
||||
</b-card>
|
||||
|
||||
<b-card class="mt-4">
|
||||
<b-card class="mt-4">
|
||||
|
||||
<b-card-title>{{ $t("taskHistory") }}</b-card-title>
|
||||
<b-card-title>{{ $t("taskHistory") }}</b-card-title>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<b-table
|
||||
id="task-history"
|
||||
:items="historyItems"
|
||||
:fields="historyFields"
|
||||
:current-page="historyCurrentPage"
|
||||
:tbody-tr-class="rowClass"
|
||||
:per-page="10"
|
||||
>
|
||||
<template #cell(logs)="data">
|
||||
<router-link :to="`/log/${data.item.logs}`">{{ $t("logs") }}</router-link>
|
||||
</template>
|
||||
<b-table
|
||||
id="task-history"
|
||||
:items="historyItems"
|
||||
:fields="historyFields"
|
||||
:current-page="historyCurrentPage"
|
||||
:tbody-tr-class="rowClass"
|
||||
:per-page="10"
|
||||
>
|
||||
<template #cell(logs)="data">
|
||||
<template v-if="data.item._row.has_logs">
|
||||
<b-button variant="link" size="sm" :to="`/log/${data.item.id}`">
|
||||
{{ $t("view") }}
|
||||
</b-button>
|
||||
/
|
||||
<b-button variant="link" size="sm" @click="deleteLogs(data.item.id)">
|
||||
{{ $t("delete") }}
|
||||
</b-button>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
</b-table>
|
||||
<template #cell(delete)="data">
|
||||
</template>
|
||||
|
||||
<b-pagination limit="20" v-model="historyCurrentPage" :total-rows="historyItems.length"
|
||||
:per-page="10"></b-pagination>
|
||||
</b-table>
|
||||
|
||||
</b-card>
|
||||
</div>
|
||||
<b-pagination limit="20" v-model="historyCurrentPage" :total-rows="historyItems.length"
|
||||
:per-page="10"></b-pagination>
|
||||
|
||||
</b-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -45,106 +56,116 @@ const HOUR = 3600;
|
||||
const MINUTE = 60;
|
||||
|
||||
function humanDuration(sec_num) {
|
||||
sec_num = sec_num / 1000;
|
||||
const days = Math.floor(sec_num / DAY);
|
||||
sec_num -= days * DAY;
|
||||
const hours = Math.floor(sec_num / HOUR);
|
||||
sec_num -= hours * HOUR;
|
||||
const minutes = Math.floor(sec_num / MINUTE);
|
||||
sec_num -= minutes * MINUTE;
|
||||
const seconds = Math.floor(sec_num);
|
||||
sec_num = sec_num / 1000;
|
||||
const days = Math.floor(sec_num / DAY);
|
||||
sec_num -= days * DAY;
|
||||
const hours = Math.floor(sec_num / HOUR);
|
||||
sec_num -= hours * HOUR;
|
||||
const minutes = Math.floor(sec_num / MINUTE);
|
||||
sec_num -= minutes * MINUTE;
|
||||
const seconds = Math.floor(sec_num);
|
||||
|
||||
if (days > 0) {
|
||||
return `${days} days ${hours}h ${minutes}m ${seconds}s`;
|
||||
}
|
||||
if (days > 0) {
|
||||
return `${days} days ${hours}h ${minutes}m ${seconds}s`;
|
||||
}
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}h ${minutes}m ${seconds}s`;
|
||||
}
|
||||
if (hours > 0) {
|
||||
return `${hours}h ${minutes}m ${seconds}s`;
|
||||
}
|
||||
|
||||
if (minutes > 0) {
|
||||
return `${minutes}m ${seconds}s`;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return `${minutes}m ${seconds}s`;
|
||||
}
|
||||
|
||||
if (seconds > 0) {
|
||||
return `${seconds}s`;
|
||||
}
|
||||
if (seconds > 0) {
|
||||
return `${seconds}s`;
|
||||
}
|
||||
|
||||
return "<0s";
|
||||
return "<0s";
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'Tasks',
|
||||
components: {TaskListItem},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
tasks: [],
|
||||
taskHistory: [],
|
||||
timerId: null,
|
||||
historyFields: [
|
||||
{key: "name", label: this.$t("taskName")},
|
||||
{key: "time", label: this.$t("taskStarted")},
|
||||
{key: "duration", label: this.$t("taskDuration")},
|
||||
{key: "status", label: this.$t("taskStatus")},
|
||||
{key: "logs", label: this.$t("logs")},
|
||||
],
|
||||
historyCurrentPage: 1,
|
||||
historyItems: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
msg: String
|
||||
},
|
||||
mounted() {
|
||||
this.loading = true;
|
||||
this.update().then(() => this.loading = false);
|
||||
name: 'Tasks',
|
||||
components: {TaskListItem},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
tasks: [],
|
||||
taskHistory: [],
|
||||
timerId: null,
|
||||
historyFields: [
|
||||
{key: "name", label: this.$t("taskName")},
|
||||
{key: "time", label: this.$t("taskStarted")},
|
||||
{key: "duration", label: this.$t("taskDuration")},
|
||||
{key: "status", label: this.$t("taskStatus")},
|
||||
{key: "logs", label: this.$t("logs")},
|
||||
],
|
||||
historyCurrentPage: 1,
|
||||
historyItems: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
msg: String
|
||||
},
|
||||
mounted() {
|
||||
this.loading = true;
|
||||
this.update().then(() => this.loading = false);
|
||||
|
||||
this.timerId = window.setInterval(this.update, 1000);
|
||||
this.updateHistory();
|
||||
},
|
||||
destroyed() {
|
||||
if (this.timerId) {
|
||||
window.clearInterval(this.timerId);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
rowClass(row) {
|
||||
if (row.status === "failed") {
|
||||
return "table-danger";
|
||||
}
|
||||
return null;
|
||||
this.timerId = window.setInterval(this.update, 1000);
|
||||
this.updateHistory();
|
||||
},
|
||||
updateHistory() {
|
||||
Sist2AdminApi.getTaskHistory().then(resp => {
|
||||
this.historyItems = resp.data.map(row => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
duration: this.taskDuration(row),
|
||||
time: moment(row.started).format("dd, MMM Do YYYY, HH:mm:ss"),
|
||||
logs: row.id,
|
||||
status: row.return_code === 0 ? "ok" : "failed"
|
||||
}));
|
||||
});
|
||||
destroyed() {
|
||||
if (this.timerId) {
|
||||
window.clearInterval(this.timerId);
|
||||
}
|
||||
},
|
||||
update() {
|
||||
return Sist2AdminApi.getTasks().then(resp => {
|
||||
this.tasks = resp.data;
|
||||
})
|
||||
},
|
||||
taskDuration(task) {
|
||||
const start = moment.utc(task.started);
|
||||
const end = moment.utc(task.ended);
|
||||
methods: {
|
||||
rowClass(row) {
|
||||
if (row.status === "failed") {
|
||||
return "table-danger";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
updateHistory() {
|
||||
Sist2AdminApi.getTaskHistory().then(resp => {
|
||||
this.historyItems = resp.data.map(row => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
duration: this.taskDuration(row),
|
||||
time: moment.utc(row.started).local().format("dd, MMM Do YYYY, HH:mm:ss"),
|
||||
logs: null,
|
||||
status: [0,1].includes(row.return_code) ? "ok" : "failed",
|
||||
_row: row
|
||||
}));
|
||||
});
|
||||
},
|
||||
update() {
|
||||
return Sist2AdminApi.getTasks().then(resp => {
|
||||
this.tasks = resp.data;
|
||||
})
|
||||
},
|
||||
taskDuration(task) {
|
||||
const start = moment.utc(task.started);
|
||||
const end = moment.utc(task.ended);
|
||||
|
||||
return humanDuration(end.diff(start))
|
||||
return humanDuration(end.diff(start))
|
||||
},
|
||||
deleteLogs(taskId) {
|
||||
Sist2AdminApi.deleteTaskLogs(taskId).then(() => {
|
||||
this.updateHistory();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#task-history {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4913,9 +4913,9 @@ socket.io-client@^4.5.1:
|
||||
socket.io-parser "~4.2.0"
|
||||
|
||||
socket.io-parser@~4.2.0:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz"
|
||||
integrity sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.3.tgz#926bcc6658e2ae0883dc9dee69acbdc76e4e3667"
|
||||
integrity sha512-JMafRntWVO2DCJimKsRTh/wnqVvO4hrfwOqtO7f+uzwsQMuxO6VwImtYxaQ+ieoyshWOTJyV0fA21lccEXRPpQ==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
|
||||
@@ -20,8 +20,9 @@ import cron
|
||||
from config import LOG_FOLDER, logger, WEBSERVER_PORT, DATA_FOLDER, SIST2_BINARY
|
||||
from jobs import Sist2Job, Sist2ScanTask, TaskQueue, Sist2IndexTask, JobStatus
|
||||
from notifications import Subscribe, Notifications
|
||||
from sist2 import Sist2
|
||||
from state import migrate_v1_to_v2, RUNNING_FRONTENDS, TESSERACT_LANGS, DB_SCHEMA_VERSION
|
||||
from sist2 import Sist2, Sist2SearchBackend
|
||||
from state import migrate_v1_to_v2, RUNNING_FRONTENDS, TESSERACT_LANGS, DB_SCHEMA_VERSION, migrate_v3_to_v4, \
|
||||
get_log_files_to_remove, delete_log_file, create_default_search_backends
|
||||
from web import Sist2Frontend
|
||||
|
||||
sist2 = Sist2(SIST2_BINARY, DATA_FOLDER)
|
||||
@@ -80,9 +81,7 @@ async def get_jobs():
|
||||
|
||||
@app.put("/api/job/{name:str}")
|
||||
async def update_job(name: str, new_job: Sist2Job):
|
||||
# TODO: Check etag
|
||||
|
||||
new_job.last_modified = datetime.now()
|
||||
new_job.last_modified = datetime.utcnow()
|
||||
job = db["jobs"][name]
|
||||
if not job:
|
||||
raise HTTPException(status_code=404)
|
||||
@@ -134,8 +133,18 @@ async def kill_job(task_id: str):
|
||||
return task_queue.kill_task(task_id)
|
||||
|
||||
|
||||
@app.post("/api/task/{task_id:str}/delete_logs")
|
||||
async def delete_task_logs(task_id: str):
|
||||
if not db["task_done"][task_id]:
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
delete_log_file(db, task_id)
|
||||
|
||||
return "ok"
|
||||
|
||||
|
||||
def _run_job(job: Sist2Job):
|
||||
job.last_modified = datetime.now()
|
||||
job.last_modified = datetime.utcnow()
|
||||
if job.status == JobStatus("created"):
|
||||
job.status = JobStatus("started")
|
||||
db["jobs"][job.name] = job
|
||||
@@ -158,14 +167,29 @@ async def run_job(name: str):
|
||||
return "ok"
|
||||
|
||||
|
||||
@app.get("/api/job/{name:str}/logs_to_delete")
|
||||
async def task_history(n: int, name: str):
|
||||
return get_log_files_to_remove(db, name, n)
|
||||
|
||||
|
||||
@app.delete("/api/job/{name:str}")
|
||||
async def delete_job(name: str):
|
||||
job = db["jobs"][name]
|
||||
if job:
|
||||
del db["jobs"][name]
|
||||
else:
|
||||
job: Sist2Job = db["jobs"][name]
|
||||
if not job:
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
if any(name in frontend.jobs for frontend in db["frontends"]):
|
||||
raise HTTPException(status_code=400, detail="in use (frontend)")
|
||||
|
||||
try:
|
||||
os.remove(job.previous_index)
|
||||
except:
|
||||
pass
|
||||
|
||||
del db["jobs"][name]
|
||||
|
||||
return "ok"
|
||||
|
||||
|
||||
@app.delete("/api/frontend/{name:str}")
|
||||
async def delete_frontend(name: str):
|
||||
@@ -251,9 +275,21 @@ def check_es_version(es_url: str, insecure: bool):
|
||||
|
||||
|
||||
def start_frontend_(frontend: Sist2Frontend):
|
||||
frontend.web_options.indices = list(map(lambda j: db["jobs"][j].index_path, frontend.jobs))
|
||||
frontend.web_options.indices = [
|
||||
os.path.join(DATA_FOLDER, db["jobs"][j].index_path)
|
||||
for j in frontend.jobs
|
||||
]
|
||||
|
||||
pid = sist2.web(frontend.web_options, frontend.name)
|
||||
backend_name = frontend.web_options.search_backend
|
||||
search_backend = db["search_backends"][backend_name]
|
||||
if search_backend is None:
|
||||
logger.error(
|
||||
f"Error while running task: search backend not found: {backend_name}")
|
||||
return -1
|
||||
|
||||
logger.debug(f"Fetched search backend options for {backend_name}")
|
||||
|
||||
pid = sist2.web(frontend.web_options, search_backend, frontend.name)
|
||||
RUNNING_FRONTENDS[frontend.name] = pid
|
||||
|
||||
|
||||
@@ -283,6 +319,62 @@ async def get_frontends():
|
||||
return res
|
||||
|
||||
|
||||
@app.get("/api/search_backend/")
|
||||
async def get_search_backends():
|
||||
return list(db["search_backends"])
|
||||
|
||||
|
||||
@app.put("/api/search_backend/{name:str}")
|
||||
async def update_search_backend(name: str, backend: Sist2SearchBackend):
|
||||
if not db["search_backends"][name]:
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
db["search_backends"][name] = backend
|
||||
return "ok"
|
||||
|
||||
|
||||
@app.get("/api/search_backend/{name:str}")
|
||||
def get_search_backend(name: str):
|
||||
backend = db["search_backends"][name]
|
||||
if not backend:
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
return backend
|
||||
|
||||
|
||||
@app.delete("/api/search_backend/{name:str}")
|
||||
def delete_search_backend(name: str):
|
||||
backend: Sist2SearchBackend = db["search_backends"][name]
|
||||
if not backend:
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
if any(frontend.web_options.search_backend == name for frontend in db["frontends"]):
|
||||
raise HTTPException(status_code=400, detail="in use (frontend)")
|
||||
|
||||
if any(job.index_options.search_backend == name for job in db["jobs"]):
|
||||
raise HTTPException(status_code=400, detail="in use (job)")
|
||||
|
||||
del db["search_backends"][name]
|
||||
|
||||
try:
|
||||
os.remove(os.path.join(DATA_FOLDER, backend.search_index))
|
||||
except:
|
||||
pass
|
||||
|
||||
return "ok"
|
||||
|
||||
|
||||
@app.post("/api/search_backend/{name:str}")
|
||||
def create_search_backend(name: str):
|
||||
if db["search_backends"][name] is not None:
|
||||
return HTTPException(status_code=400, detail="already exists")
|
||||
|
||||
backend = Sist2SearchBackend.create_default(name)
|
||||
db["search_backends"][name] = backend
|
||||
|
||||
return backend
|
||||
|
||||
|
||||
def tail(filepath: str, n: int):
|
||||
with open(filepath) as file:
|
||||
|
||||
@@ -321,7 +413,6 @@ async def ws_tail_log(websocket: WebSocket):
|
||||
async with Subscribe(notifications) as ob:
|
||||
async for notification in ob.notifications():
|
||||
await websocket.send_json(notification)
|
||||
print(notification)
|
||||
|
||||
except ConnectionClosed:
|
||||
return
|
||||
@@ -352,7 +443,7 @@ async def ws_tail_log(websocket: WebSocket, task_id: str, n: int):
|
||||
|
||||
|
||||
def main():
|
||||
uvicorn.run(app, port=WEBSERVER_PORT, host="0.0.0.0")
|
||||
uvicorn.run(app, port=WEBSERVER_PORT, host="0.0.0.0", timeout_graceful_shutdown=0)
|
||||
|
||||
|
||||
def initialize_db():
|
||||
@@ -361,6 +452,8 @@ def initialize_db():
|
||||
frontend = Sist2Frontend.create_default("default")
|
||||
db["frontends"]["default"] = frontend
|
||||
|
||||
create_default_search_backends(db)
|
||||
|
||||
logger.info("Initialized database.")
|
||||
|
||||
|
||||
@@ -381,6 +474,12 @@ if __name__ == '__main__':
|
||||
if db["sist2_admin"]["info"]["version"] == "2":
|
||||
logger.error("Cannot migrate database from v2 to v3. Delete state.db to proceed.")
|
||||
exit(-1)
|
||||
if db["sist2_admin"]["info"]["version"] == "3":
|
||||
logger.info("Migrating to v4 database schema")
|
||||
migrate_v3_to_v4(db)
|
||||
|
||||
if db["sist2_admin"]["info"]["version"] != DB_SCHEMA_VERSION:
|
||||
raise Exception(f"Incompatible database version for {db.dbfile}")
|
||||
|
||||
start_frontends()
|
||||
cron.initialize(db, _run_job)
|
||||
|
||||
@@ -10,7 +10,9 @@ from jobs import Sist2Job
|
||||
|
||||
|
||||
def _check_schedule(db: PersistentState, run_job):
|
||||
for job in db["jobs"]:
|
||||
jobs = list(db["jobs"])
|
||||
|
||||
for job in jobs:
|
||||
job: Sist2Job
|
||||
|
||||
if job.schedule_enabled:
|
||||
|
||||
@@ -13,10 +13,10 @@ from uuid import uuid4, UUID
|
||||
from hexlib.db import PersistentState
|
||||
from pydantic import BaseModel
|
||||
|
||||
from config import logger, LOG_FOLDER
|
||||
from config import logger, LOG_FOLDER, DATA_FOLDER
|
||||
from notifications import Notifications
|
||||
from sist2 import ScanOptions, IndexOptions, Sist2
|
||||
from state import RUNNING_FRONTENDS
|
||||
from state import RUNNING_FRONTENDS, get_log_files_to_remove, delete_log_file
|
||||
from web import Sist2Frontend
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ class Sist2Job(BaseModel):
|
||||
cron_expression: str
|
||||
schedule_enabled: bool = False
|
||||
|
||||
keep_last_n_logs: int = -1
|
||||
|
||||
previous_index: str = None
|
||||
index_path: str = None
|
||||
previous_index_path: str = None
|
||||
@@ -53,15 +55,10 @@ class Sist2Job(BaseModel):
|
||||
name=name,
|
||||
scan_options=ScanOptions(path="/"),
|
||||
index_options=IndexOptions(),
|
||||
last_modified=datetime.now(),
|
||||
last_modified=datetime.utcnow(),
|
||||
cron_expression="0 0 * * *"
|
||||
)
|
||||
|
||||
# @validator("etag", always=True)
|
||||
# def validate_etag(cls, value, values):
|
||||
# s = values["name"] + values["scan_options"].json() + values["index_options"].json() + values["cron_expression"]
|
||||
# return md5(s.encode()).hexdigest()
|
||||
|
||||
|
||||
class Sist2TaskProgress:
|
||||
|
||||
@@ -111,7 +108,7 @@ class Sist2Task:
|
||||
self._logger.info(json.dumps(log_json))
|
||||
|
||||
def run(self, sist2: Sist2, db: PersistentState):
|
||||
self.started = datetime.now()
|
||||
self.started = datetime.utcnow()
|
||||
|
||||
logger.info(f"Started task {self.display_name}")
|
||||
|
||||
@@ -132,14 +129,16 @@ class Sist2ScanTask(Sist2Task):
|
||||
self.pid = pid
|
||||
|
||||
return_code = sist2.scan(self.job.scan_options, logs_cb=self.log_callback, set_pid_cb=set_pid)
|
||||
self.ended = datetime.now()
|
||||
self.ended = datetime.utcnow()
|
||||
|
||||
if return_code != 0:
|
||||
is_ok = return_code in (0, 1)
|
||||
|
||||
if not is_ok:
|
||||
self._logger.error(json.dumps({"sist2-admin": f"Process returned non-zero exit code ({return_code})"}))
|
||||
logger.info(f"Task {self.display_name} failed ({return_code})")
|
||||
else:
|
||||
self.job.index_path = self.job.scan_options.output
|
||||
self.job.last_index_date = datetime.now()
|
||||
self.job.last_index_date = datetime.utcnow()
|
||||
self.job.do_full_scan = False
|
||||
db["jobs"][self.job.name] = self.job
|
||||
self._logger.info(json.dumps({"sist2-admin": f"Save last_index_date={self.job.last_index_date}"}))
|
||||
@@ -147,7 +146,7 @@ class Sist2ScanTask(Sist2Task):
|
||||
logger.info(f"Completed {self.display_name} ({return_code=})")
|
||||
|
||||
# Remove old index
|
||||
if return_code == 0:
|
||||
if is_ok:
|
||||
if self.job.previous_index_path is not None and self.job.previous_index_path != self.job.index_path:
|
||||
self._logger.info(json.dumps({"sist2-admin": f"Remove {self.job.previous_index_path=}"}))
|
||||
try:
|
||||
@@ -171,8 +170,15 @@ class Sist2IndexTask(Sist2Task):
|
||||
|
||||
self.job.index_options.path = self.job.scan_options.output
|
||||
|
||||
return_code = sist2.index(self.job.index_options, logs_cb=self.log_callback)
|
||||
self.ended = datetime.now()
|
||||
search_backend = db["search_backends"][self.job.index_options.search_backend]
|
||||
if search_backend is None:
|
||||
logger.error(f"Error while running task: search backend not found: {self.job.index_options.search_backend}")
|
||||
return -1
|
||||
|
||||
logger.debug(f"Fetched search backend options for {self.job.index_options.search_backend}")
|
||||
|
||||
return_code = sist2.index(self.job.index_options, search_backend, logs_cb=self.log_callback)
|
||||
self.ended = datetime.utcnow()
|
||||
|
||||
duration = self.ended - self.started
|
||||
|
||||
@@ -206,9 +212,20 @@ class Sist2IndexTask(Sist2Task):
|
||||
except ChildProcessError:
|
||||
pass
|
||||
|
||||
frontend.web_options.indices = map(lambda j: db["jobs"][j].index_path, frontend.jobs)
|
||||
backend_name = frontend.web_options.search_backend
|
||||
search_backend = db["search_backends"][backend_name]
|
||||
if search_backend is None:
|
||||
logger.error(f"Error while running task: search backend not found: {backend_name}")
|
||||
return -1
|
||||
|
||||
pid = sist2.web(frontend.web_options, frontend.name)
|
||||
logger.debug(f"Fetched search backend options for {backend_name}")
|
||||
|
||||
frontend.web_options.indices = [
|
||||
os.path.join(DATA_FOLDER, db["jobs"][j].index_path)
|
||||
for j in frontend.jobs
|
||||
]
|
||||
|
||||
pid = sist2.web(frontend.web_options, search_backend, frontend.name)
|
||||
RUNNING_FRONTENDS[frontend_name] = pid
|
||||
|
||||
self._logger.info(json.dumps({"sist2-admin": f"Restart frontend {pid=} {frontend_name=}"}))
|
||||
@@ -232,7 +249,7 @@ class TaskQueue:
|
||||
def _tasks_failed(self):
|
||||
done = set()
|
||||
|
||||
for row in self._db["task_done"].sql("WHERE return_code != 0"):
|
||||
for row in self._db["task_done"].sql("WHERE return_code NOT IN (0,1)"):
|
||||
done.add(uuid.UUID(row["id"]))
|
||||
|
||||
return done
|
||||
@@ -301,8 +318,14 @@ class TaskQueue:
|
||||
"ended": task.ended,
|
||||
"started": task.started,
|
||||
"name": task.display_name,
|
||||
"return_code": task_result
|
||||
"return_code": task_result,
|
||||
"has_logs": 1
|
||||
}
|
||||
|
||||
logs_to_delete = get_log_files_to_remove(self._db, task.job.name, task.job.keep_last_n_logs)
|
||||
for row in logs_to_delete:
|
||||
delete_log_file(self._db, row["id"])
|
||||
|
||||
if isinstance(task, Sist2IndexTask):
|
||||
self._notifications.notify({
|
||||
"message": "notifications.indexCompleted",
|
||||
|
||||
@@ -3,6 +3,7 @@ import json
|
||||
import logging
|
||||
import os.path
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from io import TextIOWrapper
|
||||
from logging import FileHandler
|
||||
from subprocess import Popen, PIPE
|
||||
@@ -12,7 +13,7 @@ from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from config import logger, LOG_FOLDER
|
||||
from config import logger, LOG_FOLDER, DATA_FOLDER
|
||||
|
||||
|
||||
class Sist2Version:
|
||||
@@ -25,77 +26,60 @@ class Sist2Version:
|
||||
return f"{self.major}.{self.minor}.{self.patch}"
|
||||
|
||||
|
||||
class WebOptions(BaseModel):
|
||||
indices: List[str] = []
|
||||
class SearchBackendType(Enum):
|
||||
SQLITE = "sqlite"
|
||||
ELASTICSEARCH = "elasticsearch"
|
||||
|
||||
|
||||
class Sist2SearchBackend(BaseModel):
|
||||
backend_type: SearchBackendType = SearchBackendType("elasticsearch")
|
||||
name: str
|
||||
|
||||
search_index: str = ""
|
||||
|
||||
es_url: str = "http://elasticsearch:9200"
|
||||
es_insecure_ssl: bool = False
|
||||
es_index: str = "sist2"
|
||||
bind: str = "0.0.0.0:4090"
|
||||
auth: str = None
|
||||
tag_auth: str = None
|
||||
tagline: str = "Lightning-fast file system indexer and search tool"
|
||||
dev: bool = False
|
||||
lang: str = "en"
|
||||
auth0_audience: str = None
|
||||
auth0_domain: str = None
|
||||
auth0_client_id: str = None
|
||||
auth0_public_key: str = None
|
||||
auth0_public_key_file: str = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def args(self):
|
||||
args = ["web", f"--es-url={self.es_url}", f"--es-index={self.es_index}", f"--bind={self.bind}",
|
||||
f"--tagline={self.tagline}", f"--lang={self.lang}"]
|
||||
|
||||
if self.auth0_audience:
|
||||
args.append(f"--auth0-audience={self.auth0_audience}")
|
||||
if self.auth0_domain:
|
||||
args.append(f"--auth0-domain={self.auth0_domain}")
|
||||
if self.auth0_client_id:
|
||||
args.append(f"--auth0-client-id={self.auth0_client_id}")
|
||||
if self.auth0_public_key_file:
|
||||
args.append(f"--auth0-public-key-file={self.auth0_public_key_file}")
|
||||
if self.es_insecure_ssl:
|
||||
args.append(f"--es-insecure-ssl")
|
||||
if self.auth:
|
||||
args.append(f"--auth={self.auth}")
|
||||
if self.tag_auth:
|
||||
args.append(f"--tag-auth={self.tag_auth}")
|
||||
if self.dev:
|
||||
args.append(f"--dev")
|
||||
|
||||
args.extend(self.indices)
|
||||
|
||||
return args
|
||||
|
||||
|
||||
class IndexOptions(BaseModel):
|
||||
path: str = None
|
||||
threads: int = 1
|
||||
es_url: str = "http://elasticsearch:9200"
|
||||
es_insecure_ssl: bool = False
|
||||
es_index: str = "sist2"
|
||||
incremental_index: bool = True
|
||||
script: str = ""
|
||||
script_file: str = None
|
||||
batch_size: int = 70
|
||||
|
||||
@staticmethod
|
||||
def create_default(name: str, backend_type: SearchBackendType = SearchBackendType("elasticsearch")):
|
||||
return Sist2SearchBackend(
|
||||
name=name,
|
||||
search_index=f"search-index-{name.replace('/', '_')}.sist2",
|
||||
backend_type=backend_type
|
||||
)
|
||||
|
||||
|
||||
class IndexOptions(BaseModel):
|
||||
path: str = None
|
||||
incremental_index: bool = True
|
||||
search_backend: str = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def args(self):
|
||||
def args(self, search_backend):
|
||||
absolute_path = os.path.join(DATA_FOLDER, self.path)
|
||||
|
||||
args = ["index", self.path, f"--threads={self.threads}", f"--es-url={self.es_url}",
|
||||
f"--es-index={self.es_index}", f"--batch-size={self.batch_size}"]
|
||||
if search_backend.backend_type == SearchBackendType("sqlite"):
|
||||
search_index_absolute = os.path.join(DATA_FOLDER, search_backend.search_index)
|
||||
args = ["sqlite-index", absolute_path, "--search-index", search_index_absolute]
|
||||
else:
|
||||
args = ["index", absolute_path, f"--threads={search_backend.threads}",
|
||||
f"--es-url={search_backend.es_url}",
|
||||
f"--es-index={search_backend.es_index}",
|
||||
f"--batch-size={search_backend.batch_size}"]
|
||||
|
||||
if self.script_file:
|
||||
args.append(f"--script-file={self.script_file}")
|
||||
if self.es_insecure_ssl:
|
||||
args.append(f"--es-insecure-ssl")
|
||||
if self.incremental_index:
|
||||
args.append(f"--incremental-index")
|
||||
if search_backend.script_file:
|
||||
args.append(f"--script-file={search_backend.script_file}")
|
||||
if search_backend.es_insecure_ssl:
|
||||
args.append(f"--es-insecure-ssl")
|
||||
if self.incremental_index:
|
||||
args.append(f"--incremental-index")
|
||||
|
||||
return args
|
||||
|
||||
@@ -137,9 +121,12 @@ class ScanOptions(BaseModel):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def args(self):
|
||||
|
||||
output_path = os.path.join(DATA_FOLDER, self.output)
|
||||
|
||||
args = ["scan", self.path, f"--threads={self.threads}", f"--thumbnail-quality={self.thumbnail_quality}",
|
||||
f"--thumbnail-count={self.thumbnail_count}", f"--thumbnail-size={self.thumbnail_size}",
|
||||
f"--content-size={self.content_size}", f"--output={self.output}", f"--depth={self.depth}",
|
||||
f"--content-size={self.content_size}", f"--output={output_path}", f"--depth={self.depth}",
|
||||
f"--archive={self.archive}", f"--mem-buffer={self.mem_buffer}"]
|
||||
|
||||
if self.incremental:
|
||||
@@ -200,28 +187,80 @@ class Sist2Index:
|
||||
def name(self) -> str:
|
||||
return self._descriptor["name"]
|
||||
|
||||
class WebOptions(BaseModel):
|
||||
indices: List[str] = []
|
||||
|
||||
search_backend: str = "elasticsearch"
|
||||
|
||||
bind: str = "0.0.0.0:4090"
|
||||
auth: str = None
|
||||
tag_auth: str = None
|
||||
tagline: str = "Lightning-fast file system indexer and search tool"
|
||||
dev: bool = False
|
||||
lang: str = "en"
|
||||
auth0_audience: str = None
|
||||
auth0_domain: str = None
|
||||
auth0_client_id: str = None
|
||||
auth0_public_key: str = None
|
||||
auth0_public_key_file: str = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def args(self, search_backend: Sist2SearchBackend):
|
||||
args = ["web", f"--bind={self.bind}", f"--tagline={self.tagline}",
|
||||
f"--lang={self.lang}"]
|
||||
|
||||
if search_backend.backend_type == SearchBackendType("sqlite"):
|
||||
args.append(f"--search-index={search_backend.search_index}")
|
||||
else:
|
||||
args.append(f"--es-url={search_backend.es_url}")
|
||||
args.append(f"--es-index={search_backend.es_index}")
|
||||
if search_backend.es_insecure_ssl:
|
||||
args.append(f"--es-insecure-ssl")
|
||||
|
||||
if self.auth0_audience:
|
||||
args.append(f"--auth0-audience={self.auth0_audience}")
|
||||
if self.auth0_domain:
|
||||
args.append(f"--auth0-domain={self.auth0_domain}")
|
||||
if self.auth0_client_id:
|
||||
args.append(f"--auth0-client-id={self.auth0_client_id}")
|
||||
if self.auth0_public_key_file:
|
||||
args.append(f"--auth0-public-key-file={self.auth0_public_key_file}")
|
||||
if self.auth:
|
||||
args.append(f"--auth={self.auth}")
|
||||
if self.tag_auth:
|
||||
args.append(f"--tag-auth={self.tag_auth}")
|
||||
if self.dev:
|
||||
args.append(f"--dev")
|
||||
|
||||
args.extend(self.indices)
|
||||
|
||||
return args
|
||||
|
||||
class Sist2:
|
||||
|
||||
def __init__(self, bin_path: str, data_directory: str):
|
||||
self._bin_path = bin_path
|
||||
self.bin_path = bin_path
|
||||
self._data_dir = data_directory
|
||||
|
||||
def index(self, options: IndexOptions, logs_cb):
|
||||
def index(self, options: IndexOptions, search_backend: Sist2SearchBackend, logs_cb):
|
||||
|
||||
if options.script:
|
||||
if search_backend.script and search_backend.backend_type == SearchBackendType("elasticsearch"):
|
||||
with NamedTemporaryFile("w", prefix="sist2-admin", suffix=".painless", delete=False) as f:
|
||||
f.write(options.script)
|
||||
options.script_file = f.name
|
||||
f.write(search_backend.script)
|
||||
search_backend.script_file = f.name
|
||||
else:
|
||||
options.script_file = None
|
||||
search_backend.script_file = None
|
||||
|
||||
args = [
|
||||
self._bin_path,
|
||||
*options.args(),
|
||||
self.bin_path,
|
||||
*options.args(search_backend),
|
||||
"--json-logs",
|
||||
"--very-verbose"
|
||||
]
|
||||
|
||||
logs_cb({"sist2-admin": f"Starting sist2 command with args {args}"})
|
||||
proc = Popen(args, stdout=PIPE, stderr=PIPE)
|
||||
|
||||
t_stderr = Thread(target=self._consume_logs_stderr, args=(logs_cb, proc))
|
||||
@@ -236,13 +275,10 @@ class Sist2:
|
||||
def scan(self, options: ScanOptions, logs_cb, set_pid_cb):
|
||||
|
||||
if options.output is None:
|
||||
options.output = os.path.join(
|
||||
self._data_dir,
|
||||
f"scan-{options.name.replace('/', '_')}-{datetime.now()}.sist2"
|
||||
)
|
||||
options.output = f"scan-{options.name.replace('/', '_')}-{datetime.utcnow()}.sist2"
|
||||
|
||||
args = [
|
||||
self._bin_path,
|
||||
self.bin_path,
|
||||
*options.args(),
|
||||
"--json-logs",
|
||||
"--very-verbose"
|
||||
@@ -290,7 +326,7 @@ class Sist2:
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
def web(self, options: WebOptions, name: str):
|
||||
def web(self, options: WebOptions, search_backend: Sist2SearchBackend, name: str):
|
||||
|
||||
if options.auth0_public_key:
|
||||
with NamedTemporaryFile("w", prefix="sist2-admin", suffix=".txt", delete=False) as f:
|
||||
@@ -300,8 +336,8 @@ class Sist2:
|
||||
options.auth0_public_key_file = None
|
||||
|
||||
args = [
|
||||
self._bin_path,
|
||||
*options.args()
|
||||
self.bin_path,
|
||||
*options.args(search_backend)
|
||||
]
|
||||
|
||||
web_logger = logging.Logger(name=f"sist2-frontend-{name}")
|
||||
@@ -321,3 +357,5 @@ class Sist2:
|
||||
t_stdout.start()
|
||||
|
||||
return proc.pid
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
from typing import Dict
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from hexlib.db import Table, PersistentState
|
||||
import pickle
|
||||
|
||||
from tesseract import get_tesseract_langs
|
||||
import sqlite3
|
||||
from config import LOG_FOLDER, logger
|
||||
from sist2 import SearchBackendType, Sist2SearchBackend
|
||||
|
||||
RUNNING_FRONTENDS: Dict[str, int] = {}
|
||||
|
||||
TESSERACT_LANGS = get_tesseract_langs()
|
||||
|
||||
DB_SCHEMA_VERSION = "3"
|
||||
DB_SCHEMA_VERSION = "4"
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -50,8 +54,35 @@ class PickleTable(Table):
|
||||
yield dict((k, _deserialize(v)) for k, v in row.items())
|
||||
|
||||
|
||||
def migrate_v1_to_v2(db: PersistentState):
|
||||
def get_log_files_to_remove(db: PersistentState, job_name: str, n: int):
|
||||
if n < 0:
|
||||
return []
|
||||
|
||||
counter = 0
|
||||
to_remove = []
|
||||
|
||||
for row in db["task_done"].sql("WHERE has_logs=1 ORDER BY started DESC"):
|
||||
if row["name"].endswith(f"[{job_name}]"):
|
||||
counter += 1
|
||||
|
||||
if counter > n:
|
||||
to_remove.append(row)
|
||||
|
||||
return to_remove
|
||||
|
||||
|
||||
def delete_log_file(db: PersistentState, task_id: str):
|
||||
db["task_done"][task_id] = {
|
||||
"has_logs": 0
|
||||
}
|
||||
|
||||
try:
|
||||
os.remove(os.path.join(LOG_FOLDER, f"sist2-{task_id}.log"))
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def migrate_v1_to_v2(db: PersistentState):
|
||||
shutil.copy(db.dbfile, db.dbfile + "-before-migrate-v2.bak")
|
||||
|
||||
# Frontends
|
||||
@@ -77,3 +108,29 @@ def migrate_v1_to_v2(db: PersistentState):
|
||||
db["sist2_admin"]["info"] = {
|
||||
"version": "2"
|
||||
}
|
||||
|
||||
|
||||
def create_default_search_backends(db: PersistentState):
|
||||
es_backend = Sist2SearchBackend.create_default(name="elasticsearch",
|
||||
backend_type=SearchBackendType("elasticsearch"))
|
||||
db["search_backends"]["elasticsearch"] = es_backend
|
||||
sqlite_backend = Sist2SearchBackend.create_default(name="sqlite", backend_type=SearchBackendType("sqlite"))
|
||||
db["search_backends"]["sqlite"] = sqlite_backend
|
||||
|
||||
|
||||
def migrate_v3_to_v4(db: PersistentState):
|
||||
shutil.copy(db.dbfile, db.dbfile + "-before-migrate-v4.bak")
|
||||
|
||||
create_default_search_backends(db)
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(db.dbfile)
|
||||
conn.execute("ALTER TABLE task_done ADD COLUMN has_logs INTEGER DEFAULT 1")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
db["sist2_admin"]["info"] = {
|
||||
"version": "4"
|
||||
}
|
||||
|
||||
17
src/cli.c
17
src/cli.c
@@ -93,9 +93,12 @@ int scan_args_validate(scan_args_t *args, int argc, const char **argv) {
|
||||
if (abs_path == NULL) {
|
||||
LOG_FATALF("cli.c", "Invalid PATH argument. File not found: %s", argv[1]);
|
||||
} else {
|
||||
abs_path = realloc(abs_path, strlen(abs_path) + 2);
|
||||
strcat(abs_path, "/");
|
||||
args->path = abs_path;
|
||||
char *new_abs_path = realloc(abs_path, strlen(abs_path) + 2);
|
||||
if (new_abs_path == NULL) {
|
||||
LOG_FATALF("cli.c", "FIXME: realloc() failed for argv[1]=%s, abs_path=%s", argv[1], abs_path);
|
||||
}
|
||||
strcat(new_abs_path, "/");
|
||||
args->path = new_abs_path;
|
||||
}
|
||||
|
||||
if (args->tn_quality == OPTION_VALUE_UNSPECIFIED) {
|
||||
@@ -142,10 +145,14 @@ int scan_args_validate(scan_args_t *args, int argc, const char **argv) {
|
||||
|
||||
char *abs_output = abspath(args->output);
|
||||
if (args->incremental && abs_output == NULL) {
|
||||
LOG_WARNINGF("main.c", "Could not open original index for incremental scan: %s. Will not perform incremental scan.", args->output);
|
||||
LOG_WARNINGF("main.c",
|
||||
"Could not open original index for incremental scan: %s. Will not perform incremental scan.",
|
||||
args->output);
|
||||
args->incremental = FALSE;
|
||||
} else if (!args->incremental && abs_output != NULL) {
|
||||
LOG_FATALF("main.c", "Index already exists: %s. If you wish to perform incremental scan, you must specify --incremental", abs_output);
|
||||
LOG_FATALF("main.c",
|
||||
"Index already exists: %s. If you wish to perform incremental scan, you must specify --incremental",
|
||||
abs_output);
|
||||
}
|
||||
free(abs_output);
|
||||
|
||||
|
||||
@@ -466,8 +466,8 @@ cJSON *database_fts_search(database_t *db, const char *query, const char *path,
|
||||
const char *path_where = path_where_clause(path);
|
||||
const char *size_where = size_where_clause(size_min, size_max);
|
||||
const char *date_where = date_where_clause(date_min, date_max);
|
||||
const char *index_id_where = index_ids_where_clause(index_ids);
|
||||
const char *mime_where = mime_types_where_clause(mime_types);
|
||||
char *index_id_where = index_ids_where_clause(index_ids);
|
||||
char *mime_where = mime_types_where_clause(mime_types);
|
||||
const char *query_where = match_where(query);
|
||||
const char *after_where = get_after_where(after, sort);
|
||||
const char *tags_where = tags_where_clause(tags);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#define LOG_MAX_LENGTH 8192
|
||||
|
||||
#define LOG_SIST_DEBUG 0
|
||||
@@ -33,11 +34,12 @@
|
||||
|
||||
#define LOG_FATALF(filepath, fmt, ...)\
|
||||
sist_logf(filepath, LOG_SIST_FATAL, fmt, __VA_ARGS__);\
|
||||
raise(SIGUSR1)
|
||||
raise(SIGUSR1); \
|
||||
exit(-1)
|
||||
#define LOG_FATAL(filepath, str) \
|
||||
sist_log(filepath, LOG_SIST_FATAL, str);\
|
||||
exit(SIGUSR1)
|
||||
|
||||
raise(SIGUSR1); \
|
||||
exit(-1)
|
||||
#define LOG_FATALF_NO_EXIT(filepath, fmt, ...) \
|
||||
sist_logf(filepath, LOG_SIST_FATAL, fmt, __VA_ARGS__)
|
||||
#define LOG_FATAL_NO_EXIT(filepath, str) \
|
||||
@@ -46,6 +48,7 @@
|
||||
#include "sist.h"
|
||||
|
||||
void sist_logf(const char *filepath, int level, char *format, ...);
|
||||
|
||||
void vsist_logf(const char *filepath, int level, char *format, va_list ap);
|
||||
|
||||
void sist_log(const char *filepath, int level, char *str);
|
||||
|
||||
@@ -51,11 +51,11 @@
|
||||
#include <ctype.h>
|
||||
#include "git_hash.h"
|
||||
|
||||
#define VERSION "3.1.0"
|
||||
#define VERSION "3.1.2"
|
||||
static const char *const Version = VERSION;
|
||||
static const int VersionMajor = 3;
|
||||
static const int VersionMinor = 1;
|
||||
static const int VersionPatch = 0;
|
||||
static const int VersionPatch = 2;
|
||||
|
||||
#ifndef SIST_PLATFORM
|
||||
#define SIST_PLATFORM unknown
|
||||
|
||||
@@ -170,7 +170,7 @@ fts_search_req_t *get_search_req(struct mg_http_message *hm) {
|
||||
cJSON_Delete(json);
|
||||
return NULL;
|
||||
}
|
||||
if (req_path.val && (strstr(req_path.val->valuestring, "*") || strlen(req_path.val) >= PATH_MAX)) {
|
||||
if (req_path.val && (strstr(req_path.val->valuestring, "*") || strlen(req_path.val->valuestring) >= PATH_MAX)) {
|
||||
cJSON_Delete(json);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user