Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
baracudda committed Jul 30, 2024
2 parents 31c2d89 + c8a9dd5 commit ccd5851
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 9 deletions.
1 change: 1 addition & 0 deletions engage/channels/types/postmaster/schemes.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"YT": Schemes_Meta("pm_youtube", _("Postmaster Youtube"), _("Postmaster Youtube Identifier"), "icon-pm_youtube"),
"TIK": Schemes_Meta("pm_tiktok", _("Postmaster TikTok"), _("Postmaster TikTok Identifier"), "icon-pm_tiktok"),
"PM": Schemes_Meta("pm_service", _("Postmaster Device"), _("Postmaster Service Channel"), "icon-pm_service"),
"TOX": Schemes_Meta("pm_tox", _("Postmaster Tox"), _("Postmaster Tox Identifier"), "icon-tox"),
}

PM_Scheme2Mode = { val.scheme: key for key, val in PM_CHANNEL_MODES.items() }
Expand Down
40 changes: 40 additions & 0 deletions engage/hamls/pm/dialog-command.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<div id="modal-send-command" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="modalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content" style="width: 100%; height: 100%; position: absolute; top: 0; left: 0;">
<div class="modal-header">
<h5 class="header-text">Send Command</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" style="background-color: white;">
<div class="form-group">
<label for="command-select">Select Command</label>
<select id="command-select" style="width: 50%;">
<option value="" disabled selected>Select a command</option>
</select>
</div>
<div class="form-group" style="display: none;" id="options-container">
<label for="options-select">Options</label>
<select id="options-select" style="width: 50%;">
<option value="" disabled selected>Select an option</option>
</select>
</div>
<div class="form-group" style="display: none;" id="text-container">
<label for="text-input">Input</label>
{% verbatim %}
<input type="text" id="text-input" name="text-input" class="bs5 form-control" style="width: 47%;">
{% endverbatim %}
</div>
<div class="form-group" id="description-container">
<label for="command-description">Description</label>
<p id="command-description" style="background-color: white; border: none;"></p>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary ml-3" data-toggle="modal" data-target="#modal-send-command" id="btn-send-command">Send Command</button>
<button type="button" class="btn btn-default" data-dismiss="modal" aria-label="Close">Cancel</button>
</div>
</div>
</div>
</div>
25 changes: 24 additions & 1 deletion engage/hamls/pm/list.haml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@
%button#action-rename.button-action{data-toggle:"modal", data-target:"#modal-rename-channels"}
.-mt-1.mr-2.glyph.icon-edit
-trans "Rename"
%button#action-command.button-action{data-toggle:"modal", data-target:"#modal-send-command"}
.-mt-1.mr-2.glyph.icon-edit
-trans "Send Command"

- block table
.table-scroll.shadow.rounded-lg.overflow-x-auto
Expand Down Expand Up @@ -152,10 +155,30 @@
%p
-trans "Once the outbox is purged, it's messages will be gone forever. There is no way to undo this operation."

%temba-dialog#command-confirmation.hide(
header='{{ _("Confirm Action")|escapejs }}'
primaryButtonName='{{ _("Confirm")|escapejs }}'
destructive='true'
)
.p-6.confirmation-body
%p
-trans "Are you sure you want to proceed with this action?"
%p
&nbsp;
%p
-trans "This action cannot be undone"

{% if 'rename' in actions %}
{% include "pm/dialog-rename.haml" with groups=broadcast.groups.all %}
{% endif %}
{% include "pm/dialog-command.haml" with groups=broadcast.groups.all %}

-block extra-script
{% block extra-script %}
{{ block.super }}
<script src="{% static 'engage/js/pm-list.js' %}"></script>
<script>
window.contextData = {
commands_list: {{ commands_list|safe }},
};
</script>
{% endblock %}
62 changes: 62 additions & 0 deletions engage/pm/view/list.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import json

import requests
from django.http import JsonResponse
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.encoding import force_str

from smartmin.views import SmartListView
from rest_framework import status

from engage.channels.purge_outbox import PurgeOutboxMixin
from engage.channels.types.postmaster.postoffice import po_api_key
from engage.channels.types.postmaster.schemes import PM_CHANNEL_MODES
from temba.channels.types.postmaster import PostmasterType
from temba.orgs.views import OrgPermsMixin
from temba.utils.views import BulkActionMixin

from engage.channels.types.postmaster.postoffice import (
po_server_url,
po_api_key,
po_api_header,
)


class PmViewList(PurgeOutboxMixin, OrgPermsMixin, BulkActionMixin, SmartListView):
template_name = 'pm/list.html'
Expand Down Expand Up @@ -96,4 +109,53 @@ def get_gear_links(self):
return links
#enddef get_gear_links

@staticmethod
def get_commands_list():
if po_server_url is not None and po_api_key is not None:
r = requests.get(
f"{po_server_url}/engage/commands/list",
headers={po_api_header: po_api_key},
cookies=None,
verify=False,
)
if r.status_code == status.HTTP_200_OK:
return json.loads(r.content)["data"]
#endif
return None
#enddef get_commands_list

@staticmethod
def post_commands(data):
if po_server_url is not None and po_api_key is not None:
response = requests.post(
f"{po_server_url}/engage/commands/send",
headers={po_api_header: po_api_key},
json=data,
cookies=None,
verify=False,
)
return JsonResponse(response.json(), status=response.status_code)
#endif
return JsonResponse({'status': 'error', 'message': 'Invalid request'}, status=400)
#enddef post_commands

def post(self, request, *args, **kw1args):
data = json.loads(force_str(request.body))
user = self.request.user
org = user.get_org()
data["user_id"] = user.id
data["org_id"] = org.id

return self.post_commands(data)
#enddef post

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

commands = self.get_commands_list()
context['commands_list'] = json.dumps(commands)

return context
#enddef get_context_data

#endclass PmViewList
Binary file added engage/static/engage/img/tox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
165 changes: 159 additions & 6 deletions engage/static/engage/js/pm-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function handleSortOnHeader(e) {
$(document).ready(function(){
elListButtons = document.querySelector('.list-buttons-container');
$("th.header").click(handleSortOnHeader);
populateDropdown();
});

function getCheckedData(data2get='object-uuid') {
Expand All @@ -54,15 +55,16 @@ function getCheckedData(data2get='object-uuid') {
return checkedUuids;
}

function postReq( params ) {
function makeHttpRequest(params) {
const request = {
url: params.url,
type: params.data ? 'POST' : 'GET',
data: params.data,
headers: params.headers,
success: params.success,
error: params.error,
}
error: params.error
};

return $.ajax(request);
}

Expand All @@ -80,7 +82,7 @@ function wireupActionPurge() {
showSpinner();
let objList = getCheckedData('object-id');
Promise.all(objList.map((id) => {
return postReq({
return makeHttpRequest({
url: `/channels/purge/pm_service/${id}`,
success: (resp) => {
putToastInToaster('alert-success', resp);
Expand Down Expand Up @@ -113,7 +115,7 @@ function wireupActionRename() {
}
let objList = getCheckedData('object-id');
Promise.all(objList.map((id) => {
return postReq({
return makeHttpRequest({
url: `/pm/rename_channels/${id}/`,
data: {name_format: theNameFormat},
}).then((resp) => {
Expand All @@ -137,7 +139,158 @@ function wireupActionRename() {
}
}

window.addEventListener('DOMContentLoaded', function(e) {
function wireupActionCommand() {
const btnSubmit = document.querySelector("#btn-send-command");
const dropdownMenu = document.querySelector('#command-select');
const commandConfirmDialog = document.querySelector('#command-confirmation');

btnSubmit.addEventListener("click", function (e) {
e.preventDefault();
const selectedCommand = dropdownMenu.value;
const commandType = dropdownMenu.options[dropdownMenu.selectedIndex].dataset.type;
const needsConfirmation = dropdownMenu.options[dropdownMenu.selectedIndex].dataset.confirm === 'true';

let argsValue = [];
if (commandType === 'bool' || commandType === 'apps' || commandType === 'select') {
argsValue.push(document.getElementById("options-select").value);
} else if (commandType === 'text') {
argsValue.push(document.getElementById("text-input").value);
}

let selectedUUIDs = getCheckedData('object-uuid');
let deviceIDs = [];
selectedUUIDs.forEach(uuid => {
const rowDeviceID = $('tbody').find(`tr[data-object-uuid='${uuid}']`).find('td:nth-child(3)').text().trim();
deviceIDs.push(rowDeviceID);
});

if (needsConfirmation) {
commandConfirmDialog.classList.remove("hide");
commandConfirmDialog.open = true;
commandConfirmDialog.addEventListener("temba-button-clicked", function(e) {
if (!e.detail.button.secondary) {
showSpinner();
commandConfirmDialog.classList.add("hide");
commandConfirmDialog.open = false;
sendCommand(argsValue, deviceIDs, selectedCommand);
}
commandConfirmDialog.open = false;
});

} else {
showSpinner();
sendCommand(argsValue, deviceIDs, selectedCommand);
}
});
}

function sendCommand(argsValue, deviceIDs, selectedCommand) {
const requestData = {
device_ids: deviceIDs,
commands: [{
command: selectedCommand,
args: argsValue
}]
};

makeHttpRequest({
url: `/pm/`,
data: JSON.stringify(requestData),
success: (resp) => {
putToastInToaster('alert-success', 'Command succesfully sent');
},
error: (resp) => {
let errorMessage = 'Error sending command';
try {
const errorData = JSON.parse(resp.responseText);
errorMessage = `${errorData.error.cause}: ${errorData.error.message}`;
} catch (e) {
console.error('Error parsing JSON response:', resp.responseText);
}
putToastInToaster('alert-warning', errorMessage);
}
}).always(() => {
hideSpinner();
});
}

function populateDropdown() {
if (window.contextData.commands_list.command_groups) {
const commandGroups = window.contextData.commands_list.command_groups;
const dropdown = document.getElementById("command-select");
dropdown.innerHTML = ''; // Clear existing options if any

commandGroups.forEach(group => {
const optgroup = document.createElement("optgroup");
optgroup.label = group.name;

group.commands.forEach(command => {
if (!command.hidden) {
const option = document.createElement("option");
option.value = command.command;
option.textContent = command.label;
option.dataset.type = command.type;
option.dataset.confirm = command.confirm;
option.dataset.description = command.description;
if (command.options) {
option.dataset.options = JSON.stringify(command.options);
}
optgroup.appendChild(option);
}
});

dropdown.appendChild(optgroup);
});

dropdown.addEventListener('change', (e) => {
const selectedCommand = commandGroups.flatMap(group => group.commands).find(command => command.command === e.target.value);

const optionsContainer = document.getElementById("options-container");
const textContainer = document.getElementById("text-container");
const optionsSelect = document.getElementById("options-select");
const textInput = document.getElementById("text-input");
const descriptionContainer = document.getElementById("command-description");

if (selectedCommand) {
descriptionContainer.innerText = selectedCommand.description || '';

if (['bool', 'apps', 'select'].includes(selectedCommand.type)) {
optionsContainer.style.display = 'block';
textContainer.style.display = 'none';
optionsSelect.innerHTML = ''; // Clear previous options

if (selectedCommand.type === 'bool') {
['true', 'false'].forEach(value => {
const option = document.createElement("option");
option.value = value;
option.textContent = value.charAt(0).toUpperCase() + value.slice(1);
optionsSelect.appendChild(option);
});
} else if (selectedCommand.options) {
Object.entries(selectedCommand.options).forEach(([key, value]) => {
const opt = document.createElement("option");
opt.value = key;
opt.textContent = value;
optionsSelect.appendChild(opt);
});
}
} else if (selectedCommand.type === 'text') {
textContainer.style.display = 'block';
optionsContainer.style.display = 'none';
textInput.value = ''; // Clear previous input
} else {
optionsContainer.style.display = 'none';
textContainer.style.display = 'none';
}
}
});

dropdown.dispatchEvent(new Event('change'));
}
}

window.addEventListener('DOMContentLoaded', function (e) {
wireupActionPurge();
wireupActionRename();
wireupActionCommand();
});
6 changes: 5 additions & 1 deletion engage/static/engage/less/chat_modes.less
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
pm_mastodon,
pm_youtube,
pm_tiktok,
pm_service;
pm_service,
pm_tox;

/* Generic LESS function to set defaults */
.chat-mode-icon(@scheme: "pm_service") {
Expand Down Expand Up @@ -80,6 +81,9 @@
content: url("@{imgEngagePath}/postmaster.svg");
filter: invert(1);
}
.chat-mode-scheme(pm_tox) {
content: url("@{imgEngagePath}/tox.png");
}
/* ===== END Specific Scheme Definitions ===== */

/* Iterator over all the schemes to create the various CSS rules */
Expand Down
Loading

0 comments on commit ccd5851

Please sign in to comment.