mirror of https://github.com/MISP/misp-modules
chg: [website] history add input attr
parent
443f1f5e9c
commit
ad6397b294
|
@ -7,6 +7,7 @@ class Module(db.Model):
|
|||
description = db.Column(db.String)
|
||||
is_active = db.Column(db.Boolean, default=True)
|
||||
request_on_query = db.Column(db.Boolean, default=False)
|
||||
input_attr = db.Column(db.String)
|
||||
|
||||
def to_json(self):
|
||||
json_dict = {
|
||||
|
@ -14,7 +15,8 @@ class Module(db.Model):
|
|||
"name": self.name,
|
||||
"description": self.description,
|
||||
"is_active": self.is_active,
|
||||
"request_on_query": self.request_on_query
|
||||
"request_on_query": self.request_on_query,
|
||||
"input_attr": self.input_attr
|
||||
}
|
||||
return json_dict
|
||||
|
||||
|
|
|
@ -79,6 +79,8 @@ def get_modules_config():
|
|||
modules_list = []
|
||||
for module in modules:
|
||||
loc_module = module.to_json()
|
||||
if loc_module["input_attr"]:
|
||||
loc_module["input_attr"] = json.loads(loc_module["input_attr"])
|
||||
loc_module["config"] = []
|
||||
mcs = Module_Config.query.filter_by(module_id=module.id).all()
|
||||
for mc in mcs:
|
||||
|
|
|
@ -1,634 +0,0 @@
|
|||
import {display_toast} from '../toaster.js'
|
||||
const { ref, nextTick, computed } = Vue
|
||||
export default {
|
||||
delimiters: ['[[', ']]'],
|
||||
props: {
|
||||
cases_info: Object,
|
||||
status_info: Object,
|
||||
users_in_case: Object,
|
||||
edit_mode: Boolean,
|
||||
task: Object,
|
||||
key_loop: Number
|
||||
},
|
||||
emits: ['edit_mode', 'task'],
|
||||
setup(props, {emit}) {
|
||||
Vue.onMounted(async () => {
|
||||
select2_change(props.task.id)
|
||||
|
||||
const targetElement = document.getElementById('editor_' + props.task.id)
|
||||
editor = new Editor.EditorView({
|
||||
doc: "\n\n",
|
||||
extensions: [Editor.basicSetup, Editor.markdown(),Editor.EditorView.updateListener.of((v) => {
|
||||
if (v.docChanged) {
|
||||
note_editor_render.value = editor.state.doc.toString()
|
||||
}
|
||||
})],
|
||||
parent: targetElement
|
||||
})
|
||||
|
||||
const allCollapses = document.getElementById('collapse' + props.task.id)
|
||||
allCollapses.addEventListener('shown.bs.collapse', event => {
|
||||
md.mermaid.init()
|
||||
})
|
||||
is_mounted.value = true
|
||||
})
|
||||
Vue.onUpdated(async () => {
|
||||
select2_change(props.task.id)
|
||||
// do not initialize mermaid before the page is mounted
|
||||
if(is_mounted)
|
||||
md.mermaid.init()
|
||||
})
|
||||
|
||||
const is_mounted = ref(false)
|
||||
const is_exporting = ref(false)
|
||||
|
||||
const notes = ref(props.task.notes)
|
||||
const note_editor_render = ref("")
|
||||
let editor
|
||||
const md = window.markdownit()
|
||||
md.use(mermaidMarkdown.default)
|
||||
|
||||
if(props.task.notes)
|
||||
note_editor_render.value = props.task.notes
|
||||
|
||||
|
||||
async function change_status(status, task){
|
||||
const res = await fetch(
|
||||
'/case/' + task.case_id + '/change_task_status/'+task.id,{
|
||||
headers: { "X-CSRFToken": $("#csrf_token").val(), "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
body: JSON.stringify({"status": status})
|
||||
}
|
||||
)
|
||||
if(await res.status==200){
|
||||
task.last_modif = Date.now()
|
||||
task.status_id=status
|
||||
|
||||
if(props.status_info.status[status-1].name == 'Finished'){
|
||||
task.last_modif = Date.now()
|
||||
task.completed = true
|
||||
fetch('/case/complete_task/'+task.id)
|
||||
}else{
|
||||
task.completed = false
|
||||
}
|
||||
}
|
||||
await display_toast(res)
|
||||
}
|
||||
|
||||
async function take_task(task, current_user){
|
||||
const res = await fetch('/case/' + task.case_id + '/take_task/' + task.id)
|
||||
|
||||
if( await res.status == 200){
|
||||
task.last_modif = Date.now()
|
||||
task.is_current_user_assigned = true
|
||||
task.users.push(current_user)
|
||||
}
|
||||
await display_toast(res)
|
||||
}
|
||||
|
||||
async function remove_assign_task(task, current_user){
|
||||
const res = await fetch('/case/' + task.case_id + '/remove_assignment/' + task.id)
|
||||
|
||||
if( await res.status == 200){
|
||||
task.last_modif = Date.now()
|
||||
task.is_current_user_assigned = false
|
||||
|
||||
let index = -1
|
||||
|
||||
for(let i=0;i<task.users.length;i++){
|
||||
if (task.users[i].id==current_user.id)
|
||||
index = i
|
||||
}
|
||||
|
||||
if(index > -1)
|
||||
task.users.splice(index, 1)
|
||||
}
|
||||
await display_toast(res)
|
||||
}
|
||||
|
||||
|
||||
async function assign_user_task(){
|
||||
let users_select = $('#selectUser'+props.task.id).val()
|
||||
if(users_select.length){
|
||||
const res_msg = await fetch(
|
||||
'/case/' + props.task.case_id + '/assign_users/' + props.task.id,{
|
||||
headers: { "X-CSRFToken": $("#csrf_token").val(), "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
body: JSON.stringify({"users_id": users_select})
|
||||
}
|
||||
)
|
||||
if( await res_msg.status == 200){
|
||||
if(users_select.includes(props.cases_info.current_user.id.toString())){
|
||||
props.task.is_current_user_assigned = true
|
||||
}
|
||||
const res = await fetch('/case/' + props.task.case_id + '/get_assigned_users/' +props.task.id)
|
||||
if(await res.status == 404){
|
||||
display_toast(res)
|
||||
}else{
|
||||
let loc = await res.json()
|
||||
props.task.users = loc
|
||||
props.task.last_modif = Date.now()
|
||||
emit('task', props.task)
|
||||
}
|
||||
}
|
||||
await display_toast(res_msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function remove_assigned_user(user_id){
|
||||
const res = await fetch(
|
||||
'/case/' + props.task.case_id + '/remove_assigned_user/' + props.task.id,{
|
||||
headers: { "X-CSRFToken": $("#csrf_token").val(), "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
body: JSON.stringify({"user_id": user_id})
|
||||
}
|
||||
)
|
||||
|
||||
if( await res.status == 200){
|
||||
props.task.last_modif = Date.now()
|
||||
|
||||
let index = -1
|
||||
for(let i=0;i<props.task.users.length;i++){
|
||||
if (props.task.users[i].id==user_id){
|
||||
if(user_id == props.cases_info.current_user.id.toString()){
|
||||
props.task.is_current_user_assigned = true
|
||||
}
|
||||
props.task.is_current_user_assigned = false
|
||||
index = i
|
||||
}
|
||||
}
|
||||
|
||||
if(index > -1)
|
||||
props.task.users.splice(index, 1)
|
||||
}
|
||||
await display_toast(res)
|
||||
}
|
||||
|
||||
|
||||
async function delete_task(task, task_array){
|
||||
const res = await fetch('/case/' + task.case_id + '/delete_task/' + task.id)
|
||||
|
||||
if( await res.status == 200){
|
||||
let index = task_array.indexOf(task)
|
||||
if(index > -1)
|
||||
task_array.splice(index, 1)
|
||||
}
|
||||
await display_toast(res)
|
||||
}
|
||||
|
||||
async function edit_note(task){
|
||||
task.last_modif = Date.now()
|
||||
emit('edit_mode', true)
|
||||
|
||||
const res = await fetch('/case/' + task.case_id + '/get_note/' + task.id)
|
||||
let loc = await res.json()
|
||||
task.notes = loc["note"]
|
||||
|
||||
const targetElement = document.getElementById('editor1_' + props.task.id)
|
||||
editor = new Editor.EditorView({
|
||||
doc: task.notes,
|
||||
extensions: [Editor.basicSetup, Editor.markdown(),Editor.EditorView.updateListener.of((v) => {
|
||||
if (v.docChanged) {
|
||||
note_editor_render.value = editor.state.doc.toString()
|
||||
}
|
||||
})],
|
||||
parent: targetElement
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
async function modif_note(task){
|
||||
let notes_loc = editor.state.doc.toString()
|
||||
if(notes_loc.trim().length == 0){
|
||||
notes_loc = notes_loc.trim()
|
||||
}
|
||||
const res_msg = await fetch(
|
||||
'/case/' + task.case_id + '/modif_note/' + task.id,{
|
||||
headers: { "X-CSRFToken": $("#csrf_token").val(), "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
body: JSON.stringify({"task_id": task.id.toString(), "notes": notes_loc})
|
||||
}
|
||||
)
|
||||
|
||||
if(await res_msg.status == 200){
|
||||
emit('edit_mode', false)
|
||||
task.last_modif = Date.now()
|
||||
task.notes = notes_loc
|
||||
notes.value = notes_loc
|
||||
await nextTick()
|
||||
|
||||
if(!notes_loc){
|
||||
const targetElement = document.getElementById('editor_' + props.task.id)
|
||||
if(targetElement.innerHTML === ""){
|
||||
editor = new Editor.EditorView({
|
||||
doc: "\n\n",
|
||||
extensions: [Editor.basicSetup, Editor.markdown(),Editor.EditorView.updateListener.of((v) => {
|
||||
if (v.docChanged) {
|
||||
note_editor_render.value = editor.state.doc.toString()
|
||||
}
|
||||
})],
|
||||
parent: targetElement
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
await display_toast(res_msg)
|
||||
}
|
||||
|
||||
|
||||
async function add_file(task){
|
||||
let files = document.getElementById('formFileMultiple'+task.id).files
|
||||
|
||||
let formData = new FormData();
|
||||
for(let i=0;i<files.length;i++){
|
||||
formData.append("files"+i, files[i]);
|
||||
}
|
||||
|
||||
const res = await fetch(
|
||||
'/case/' + task.case_id + '/add_files/' + task.id,{
|
||||
headers: { "X-CSRFToken": $("#csrf_token").val() },
|
||||
method: "POST",
|
||||
files: files,
|
||||
body: formData
|
||||
}
|
||||
)
|
||||
if(await res.status == 200){
|
||||
const res_files = await fetch('/case/' + task.case_id + '/get_files/'+task.id)
|
||||
|
||||
if(await res_files.status == 200){
|
||||
task.last_modif = Date.now()
|
||||
let loc = await res_files.json()
|
||||
task.files = []
|
||||
for(let file in loc['files']){
|
||||
task.files.push(loc['files'][file])
|
||||
}
|
||||
}else{
|
||||
await display_toast(res_files)
|
||||
}
|
||||
}
|
||||
|
||||
await display_toast(res)
|
||||
}
|
||||
|
||||
async function delete_file(file, task){
|
||||
const res = await fetch('/case/task/' + task.id + '/delete_file/' + file.id)
|
||||
if(await res.status == 200){
|
||||
task.last_modif = Date.now()
|
||||
|
||||
let index = task.files.indexOf(file)
|
||||
if(index > -1)
|
||||
task.files.splice(index, 1)
|
||||
}
|
||||
await display_toast(res)
|
||||
}
|
||||
|
||||
async function complete_task(task){
|
||||
const res = await fetch('/case/complete_task/'+task.id)
|
||||
if (await res.status == 200){
|
||||
task.last_modif = Date.now()
|
||||
task.completed = !task.completed
|
||||
let status = task.status_id
|
||||
if(props.status_info.status[task.status_id -1].name == 'Finished'){
|
||||
for(let i in props.status_info.status){
|
||||
if(props.status_info.status[i].name == 'Created')
|
||||
task.status_id = props.status_info.status[i].id
|
||||
}
|
||||
if(task.status_id == status)
|
||||
task.status_id = 1
|
||||
|
||||
}else{
|
||||
for(let i in props.status_info.status){
|
||||
if(props.status_info.status[i].name == 'Finished'){
|
||||
task.status_id = props.status_info.status[i].id
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
let index = props.cases_info.tasks.indexOf(task)
|
||||
if(index > -1)
|
||||
props.cases_info.tasks.splice(index, 1)
|
||||
}
|
||||
await display_toast(res)
|
||||
}
|
||||
|
||||
async function notify_user(user_id){
|
||||
const res = await fetch(
|
||||
'/case/' + props.task.case_id + '/task/' + props.task.id + '/notify_user',{
|
||||
headers: { "X-CSRFToken": $("#csrf_token").val(), "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
body: JSON.stringify({"task_id": props.task.id, "user_id": user_id})
|
||||
}
|
||||
)
|
||||
await display_toast(res)
|
||||
}
|
||||
|
||||
function formatNow(dt) {
|
||||
return moment.utc(dt).from(moment.utc())
|
||||
}
|
||||
|
||||
function endOf(dt){
|
||||
return moment.utc(dt).endOf().from(moment.utc())
|
||||
}
|
||||
|
||||
|
||||
function present_user_in_task(task_user_list, user){
|
||||
let index = -1
|
||||
|
||||
for(let i=0;i<task_user_list.length;i++){
|
||||
if (task_user_list[i].id==user.id)
|
||||
index = i
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
async function export_notes(task, type){
|
||||
is_exporting.value = true
|
||||
let filename = ""
|
||||
await fetch('/case/'+task.case_id+'/task/'+task.id+'/export_notes?type=' + type)
|
||||
.then(res =>{
|
||||
filename = res.headers.get("content-disposition").split("=")
|
||||
filename = filename[filename.length - 1]
|
||||
return res.blob()
|
||||
})
|
||||
.then(data =>{
|
||||
var a = document.createElement("a")
|
||||
a.href = window.URL.createObjectURL(data);
|
||||
a.download = filename;
|
||||
a.click();
|
||||
})
|
||||
is_exporting.value = false
|
||||
}
|
||||
|
||||
function select2_change(tid){
|
||||
$('.select2-selectUser'+tid).select2({width: 'element'})
|
||||
$('.select2-container').css("min-width", "200px")
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
notes,
|
||||
note_editor_render,
|
||||
md,
|
||||
is_exporting,
|
||||
getTextColor,
|
||||
mapIcon,
|
||||
change_status,
|
||||
take_task,
|
||||
remove_assign_task,
|
||||
assign_user_task,
|
||||
remove_assigned_user,
|
||||
delete_task,
|
||||
edit_note,
|
||||
modif_note,
|
||||
add_file,
|
||||
delete_file,
|
||||
complete_task,
|
||||
notify_user,
|
||||
formatNow,
|
||||
endOf,
|
||||
export_notes,
|
||||
present_user_in_task
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div style="display: flex;">
|
||||
<a :href="'#collapse'+task.id" class="list-group-item list-group-item-action" data-bs-toggle="collapse" role="button" aria-expanded="false" :aria-controls="'collapse'+task.id">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">[[ key_loop ]]- [[task.title]]</h5>
|
||||
<small><i>Changed [[ formatNow(task.last_modif) ]] </i></small>
|
||||
</div>
|
||||
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<p v-if="task.description" class="card-text">[[ task.description ]]</p>
|
||||
<p v-else class="card-text"><i style="font-size: 12px;">No description</i></p>
|
||||
|
||||
<small v-if="status_info">
|
||||
<span :class="'badge rounded-pill text-bg-'+status_info.status[task.status_id -1].bootstrap_style">
|
||||
[[ status_info.status[task.status_id -1].name ]]
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<div style="display: flex;" v-if="task.tags">
|
||||
<template v-for="tag in task.tags">
|
||||
<div class="tag" :title="tag.description" :style="{'background-color': tag.color, 'color': getTextColor(tag.color)}">
|
||||
<i class="fa-solid fa-tag" style="margin-right: 3px; margin-left: 3px;"></i>
|
||||
[[tag.name]]
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<div v-if="task.users.length">
|
||||
Users:
|
||||
<template v-for="user in task.users">
|
||||
[[user.first_name]] [[user.last_name]],
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<i>No user assigned</i>
|
||||
</div>
|
||||
<small v-if="task.deadline" :title="task.deadline"><i>Deadline [[endOf(task.deadline)]]</i></small>
|
||||
<small v-else><i>No deadline</i></small>
|
||||
</div>
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<div style="display: flex;" v-if="task.clusters">
|
||||
<template v-for="cluster in task.clusters">
|
||||
<div :title="'Description:\\n' + cluster.description + '\\n\\nMetadata:\\n' + JSON.stringify(JSON.parse(cluster.meta), null, 4)">
|
||||
<span v-html="mapIcon(cluster.icon)"></span>
|
||||
[[cluster.tag]]
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
</div>
|
||||
</a>
|
||||
<div v-if="!cases_info.permission.read_only && cases_info.present_in_case || cases_info.permission.admin" style="display: grid;">
|
||||
<button v-if="task.completed" class="btn btn-secondary btn-sm" @click="complete_task(task)" title="Revive the task">
|
||||
<i class="fa-solid fa-backward"></i>
|
||||
</button>
|
||||
<button v-else class="btn btn-success btn-sm" @click="complete_task(task)" title="Complete the task">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
</button>
|
||||
<button v-if="!task.is_current_user_assigned" class="btn btn-secondary btn-sm" @click="take_task(task, cases_info.current_user)" title="Be assigned to the task">
|
||||
<i class="fa-solid fa-hand"></i>
|
||||
</button>
|
||||
<button v-else class="btn btn-secondary btn-sm" @click="remove_assign_task(task, cases_info.current_user)" title="Remove the assignment">
|
||||
<i class="fa-solid fa-handshake-slash"></i>
|
||||
</button>
|
||||
<a class="btn btn-primary btn-sm" :href="'/case/'+cases_info.case.id+'/edit_task/'+task.id" type="button" title="Edit the task">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
</a>
|
||||
<button class="btn btn-danger btn-sm" @click="delete_task(task, cases_info.tasks)" title="Delete the task">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Collapse Part -->
|
||||
<div class="collapse collapsetest" :id="'collapse'+task.id">
|
||||
<div class="card card-body" style="background-color: whitesmoke;">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<div v-if="!cases_info.permission.read_only && cases_info.present_in_case || cases_info.permission.admin">
|
||||
<div v-if="users_in_case">
|
||||
<h5>Assign</h5>
|
||||
<select data-placeholder="Users" multiple :class="'select2-selectUser'+task.id" :name="'selectUser'+task.id" :id="'selectUser'+task.id" style="min-width:200px">
|
||||
<template v-for="user in users_in_case.users_list">
|
||||
<option :value="user.id" v-if="present_user_in_task(task.users, user) == -1">[[user.first_name]] [[user.last_name]]</option>
|
||||
</template>
|
||||
</select>
|
||||
<button class="btn btn-primary" @click="assign_user_task()">Assign</button>
|
||||
</div>
|
||||
|
||||
<div v-if="task.users.length">
|
||||
<h5>Remove assign</h5>
|
||||
<div v-for="user in task.users">
|
||||
<span style="margin-right: 5px">[[user.first_name]] [[user.last_name]]</span>
|
||||
<button v-if="cases_info.current_user.id != user.id" class="btn btn-primary btn-sm" @click="notify_user(user.id)"><i class="fa-solid fa-bell"></i></button>
|
||||
<button class="btn btn-danger btn-sm" @click="remove_assigned_user(user.id)"><i class="fa-solid fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!cases_info.permission.read_only && cases_info.present_in_case || cases_info.permission.admin">
|
||||
<div>
|
||||
<h5>Change Status</h5>
|
||||
</div>
|
||||
<div>
|
||||
<div class="dropdown" :id="'dropdown_status_'+task.id">
|
||||
<template v-if="status_info">
|
||||
<button class="btn btn-secondary dropdown-toggle" :id="'button_'+task.id" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
[[ status_info.status[task.status_id -1].name ]]
|
||||
</button>
|
||||
<ul class="dropdown-menu" :id="'dropdown_ul_status_'+task.id">
|
||||
<template v-for="status_list in status_info.status">
|
||||
<li v-if="status_list.id != task.status_id">
|
||||
<button class="dropdown-item" @click="change_status(status_list.id, task)">[[ status_list.name ]]</button>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<div v-if="task.url">
|
||||
<div>
|
||||
<h5>Tool/Url</h5>
|
||||
</div>
|
||||
<div>
|
||||
[[task.url]]
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="task.instances.length">
|
||||
<div>
|
||||
<h5>Connectors</h5>
|
||||
</div>
|
||||
<div v-for="instance in task.instances" :title="instance.description">
|
||||
<img :src="'/static/icons/'+instance.icon" style="max-width: 30px;">
|
||||
<a style="margin-left: 5px" :href="instance.url">[[instance.url]]</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<div>
|
||||
<div>
|
||||
<h5>Files</h5>
|
||||
</div>
|
||||
<div>
|
||||
<input class="form-control" type="file" :id="'formFileMultiple'+task.id" multiple/>
|
||||
<button class="btn btn-primary" @click="add_file(task)">Add</button>
|
||||
</div>
|
||||
<br/>
|
||||
<template v-if="task.files.length">
|
||||
<template v-for="file in task.files">
|
||||
<div>
|
||||
<a class="btn btn-link" :href="'/case/task/'+task.id+'/download_file/'+file.id">
|
||||
[[ file.name ]]
|
||||
</a>
|
||||
<button class="btn btn-danger" @click="delete_file(file, task)"><i class="fa-solid fa-trash"></i></button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<div class="w-100">
|
||||
<div>
|
||||
<h5>Notes</h5>
|
||||
</div>
|
||||
<div v-if="task.notes">
|
||||
<template v-if="edit_mode">
|
||||
<div>
|
||||
<button class="btn btn-primary" @click="modif_note(task)" type="button" :id="'note_'+task.id">
|
||||
<div hidden>[[task.title]]</div>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<div style="display: flex;">
|
||||
<div style="background-color: white; border-width: 1px; border-style: solid; width: 50%" :id="'editor1_'+task.id"></div>
|
||||
<div style="background-color: white; border: 1px #515151 solid; padding: 5px; width: 50%" v-html="md.render(note_editor_render)"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="!cases_info.permission.read_only && cases_info.present_in_case || cases_info.permission.admin">
|
||||
<button class="btn btn-primary" @click="edit_note(task)" type="button" :id="'note_'+task.id">
|
||||
<div hidden>[[task.title]]</div>
|
||||
Edit
|
||||
</button>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Export
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<button v-if="!is_exporting" class="btn btn-link" @click="export_notes(task, 'pdf')" title="Export markdown as pdf">PDF</button>
|
||||
<button v-else class="btn btn-link" disabled>
|
||||
<span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
|
||||
<span role="status">Loading...</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button v-if="!is_exporting" class="btn btn-link" @click="export_notes(task, 'docx')" title="Export markdown as docx">DOCX</button>
|
||||
<button v-else class="btn btn-link" disabled>
|
||||
<span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
|
||||
<span role="status">Loading...</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<p style="background-color: white; border: 1px #515151 solid; padding: 5px;" v-html="md.render(notes)"></p>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else>
|
||||
<template v-if="!cases_info.permission.read_only && cases_info.present_in_case || cases_info.permission.admin">
|
||||
<div>
|
||||
<button class="btn btn-primary" @click="modif_note(task)" type="button" :id="'note_'+task.id">
|
||||
<div hidden>[[task.title]]</div>
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
<div style="display: flex;">
|
||||
<div style="background-color: white; border-width: 1px; border-style: solid; width: 50%" :id="'editor_'+task.id"></div>
|
||||
<div style="background-color: white; border: 1px #515151 solid; padding: 5px; width: 50%" v-html="md.render(note_editor_render)"></div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
const { ref, nextTick } = Vue
|
||||
export default {
|
||||
name: "History_view",
|
||||
delimiters: ['[[', ']]'],
|
||||
props: {
|
||||
history: Object,
|
||||
key_loop: Number
|
||||
},
|
||||
setup(props) {
|
||||
|
||||
|
||||
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="list-group" style="margin-bottom: 20px;">
|
||||
<a :href="'/query/'+history.uuid" class="list-group-item list-group-item-action">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">[[key_loop+1]]- [[history.query]]</h5>
|
||||
<small><i>[[history.uuid]]</i></small>
|
||||
</div>
|
||||
<p class="mb-1" style="color: green;"><u>Input Attribute</u>:</p>
|
||||
<div>[[history.input]]</div>
|
||||
<br>
|
||||
<p class="mb-1" style="color: #2000ff;"><u>Modules</u>:</p>
|
||||
<div>
|
||||
<template v-for="module in history.modules">[[module]],</template>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<div></div>
|
||||
<small><i>[[history.query_date]]</i></small>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
`
|
||||
}
|
|
@ -12,27 +12,7 @@
|
|||
|
||||
<div v-if="history">
|
||||
<template v-for="h, key in history">
|
||||
<div class="list-group" style="margin-bottom: 20px;">
|
||||
<a :href="'/query/'+h.uuid" class="list-group-item list-group-item-action">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">[[key+1]]- [[h.query]]</h5>
|
||||
<small><i>[[h.uuid]]</i></small>
|
||||
</div>
|
||||
<p class="mb-1" style="color: green;"><u>Input Attribute</u>:</p>
|
||||
<div>[[h.input]]</div>
|
||||
<br>
|
||||
<p class="mb-1" style="color: #2000ff;"><u>Modules</u>:</p>
|
||||
<div>
|
||||
<template v-for="module in h.modules">[[module]],</template>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<div></div>
|
||||
<small><i>[[h.query_date]]</i></small>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<history_view :history="h" :key_loop="key" />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
@ -43,8 +23,12 @@
|
|||
<script type="module">
|
||||
const { createApp, ref, onMounted, nextTick, defineComponent} = Vue
|
||||
import {display_toast, message_list} from '/static/js/toaster.js'
|
||||
import history_view from '/static/js/history_view.js'
|
||||
createApp({
|
||||
delimiters: ['[[', ']]'],
|
||||
components: {
|
||||
history_view
|
||||
},
|
||||
setup() {
|
||||
const history = ref({})
|
||||
|
||||
|
|
|
@ -39,19 +39,27 @@
|
|||
</div>
|
||||
<br>
|
||||
<h4>[[ current_config['module_name'] ]]</h4>
|
||||
<i>
|
||||
<small>
|
||||
Attributes:
|
||||
<template v-for="attr in current_config['input_attr']">
|
||||
[[ attr ]],
|
||||
</template>
|
||||
</small>
|
||||
</i>
|
||||
<template v-for="conf, key in current_config">
|
||||
<div class="mb-3" v-if="key != 'module_name' && !current_config['request_on_query'] && key != 'request_on_query'">
|
||||
<div class="mb-3" v-if="key != 'module_name' && !current_config['request_on_query'] && key != 'request_on_query' && key != 'input_attr'">
|
||||
<label :for="'form-'+key" class="form-label">[[key]]</label>
|
||||
<input type="text" class="form-control" :id="'form-'+key+'-'+current_config['module_name']" :value="conf">
|
||||
</div>
|
||||
</template>
|
||||
<div class="mb-3">
|
||||
<div class="mb-3" v-if="Object.keys(current_config).length > 3">
|
||||
<label :for="'form-'+current_config['module_name']+'-checkbox'" class="form-label">Request for config on query</label>
|
||||
<div>
|
||||
<input type="checkbox" :checked="current_config['request_on_query']" @click="check_request_on_query()" :id="'form-'+current_config['module_name']+'-checkbox'">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" @click="change_config()">Save</button>
|
||||
<button v-if="Object.keys(current_config).length > 3" class="btn btn-primary" @click="change_config()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -89,9 +97,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
current_config.value["module_name"] = module.name
|
||||
current_config.value["request_on_query"] = module.request_on_query
|
||||
}
|
||||
current_config.value["module_name"] = module.name
|
||||
current_config.value["input_attr"] = module.input_attr
|
||||
}
|
||||
|
||||
function close_panel(){
|
||||
|
@ -101,7 +110,7 @@
|
|||
async function change_config(){
|
||||
let result_dict = {}
|
||||
for(let key in current_config.value){
|
||||
if(key != "module_name" && key != "request_on_query"){
|
||||
if(key != "module_name" && key != "request_on_query" && key != "input_attr"){
|
||||
let loc = $("#form-"+key+"-"+current_config.value["module_name"]).val()
|
||||
result_dict[key] = loc
|
||||
current_config.value[key] = loc
|
||||
|
|
|
@ -10,11 +10,16 @@ def create_modules_db():
|
|||
|
||||
for module in modules:
|
||||
m = Module.query.filter_by(name=module["name"]).first()
|
||||
input_attr = ""
|
||||
if "input" in module["mispattributes"]:
|
||||
input_attr = json.dumps(module["mispattributes"]["input"])
|
||||
if not m:
|
||||
m = Module(
|
||||
name=module["name"],
|
||||
description=module["meta"]["description"],
|
||||
is_active=True
|
||||
is_active=True,
|
||||
request_on_query=False,
|
||||
input_attr=input_attr
|
||||
)
|
||||
db.session.add(m)
|
||||
db.session.commit()
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install virtualenv -y
|
||||
|
||||
virtualenv env
|
||||
source env/bin/activate
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
python app.py -i
|
||||
deactivate
|
|
@ -1,5 +1,4 @@
|
|||
#!/bin/bash
|
||||
source env/bin/activate
|
||||
|
||||
|
||||
function launch {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 183bf8fa2b87
|
||||
Revises: 91f830996ff4
|
||||
Create Date: 2024-02-09 15:21:17.274707
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '183bf8fa2b87'
|
||||
down_revision = '91f830996ff4'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('module', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('input_attr', sa.String(), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('module', schema=None) as batch_op:
|
||||
batch_op.drop_column('input_attr')
|
||||
|
||||
# ### end Alembic commands ###
|
|
@ -1,5 +1,6 @@
|
|||
flask
|
||||
sqlalchemy
|
||||
flask-session
|
||||
flask-sqlalchemy
|
||||
Flask-WTF
|
||||
Flask-Migrate
|
||||
|
@ -12,3 +13,4 @@ schedule
|
|||
pytest
|
||||
gunicorn
|
||||
jsonschema
|
||||
requests
|
||||
|
|
Loading…
Reference in New Issue