Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show qr codes in portal and Search documents in UI when not in production mode #123

Merged
merged 28 commits into from
Dec 20, 2024
Merged
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5db5098
Add test for POST document.
masv3971 Dec 19, 2024
6c4ba2c
UI+APIGW: first working embryo-version of search documents when not i…
matskramer Nov 29, 2024
f3aeaed
UI: concept for exact response type supported in post
matskramer Dec 2, 2024
ed4900a
APIGW: search documents now has a limit for number of results
matskramer Dec 3, 2024
2dc0f55
APIGW: search documents now can return a dynamic projection and/or so…
matskramer Dec 3, 2024
b5c1417
APIGW: even more advanced search documents to support known needs in …
matskramer Dec 4, 2024
69207a8
UI: more search filters, dynamic limit and checkbox to show complete …
matskramer Dec 5, 2024
11646f0
Portal qr: portal service now serve a static index.html with a bulma …
matskramer Dec 5, 2024
771dc68
Portal qr: common worker files for ui and portal
matskramer Dec 5, 2024
1f513bd
Portal qr: upgraded bulma.io css to v1.0.2 and simple static HTML lay…
matskramer Dec 5, 2024
7675c29
Portal qr: endpoint to search documents in portal calling apigw using…
matskramer Dec 5, 2024
5ee2323
Portal qr: work in progress to display qr code and other attributes f…
matskramer Dec 6, 2024
ce33f4e
Portal qr: simple display of users qr codes for business desicions (d…
matskramer Dec 9, 2024
cfe24c5
Portal qr: some info about each qr-code.
matskramer Dec 9, 2024
497831d
UI: result of search documents now displays in a table incl. error ha…
matskramer Dec 11, 2024
184c618
UI: now possible to display complete document and create a credential…
matskramer Dec 12, 2024
f101484
UI: removed local config value for qr url and now displays search err…
matskramer Dec 12, 2024
751e801
UI: qr-code can now be displayed in modal from documents table
matskramer Dec 13, 2024
f1ea640
UI: follow qr-code link in new window/tab, better copy of json from m…
matskramer Dec 13, 2024
eb839d0
UI: Search documents button in main menu bar and the search form is a…
matskramer Dec 13, 2024
373a419
UI: A document can now be deleted.
matskramer Dec 13, 2024
8b0456e
UI: Renamed Documents to documents in search reply
matskramer Dec 13, 2024
8569c1a
UI: minor UI improvments (complete document extracted from search res…
matskramer Dec 16, 2024
1e7c6b9
UI: added some documentation to exported funcs. Max limit for search …
matskramer Dec 18, 2024
484d5da
Enhanced pkg http client impl and also using it for search documents …
matskramer Dec 19, 2024
b736eb2
Portal now has config with fewer levels
matskramer Dec 20, 2024
d3f2f8f
Removed custom http clients from Portal
matskramer Dec 20, 2024
64be2b0
New config values for Portal
matskramer Dec 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
UI: follow qr-code link in new window/tab, better copy of json from m…
…odals and some NOSQL injection protection for search documents
matskramer committed Dec 20, 2024
commit f1ea640f0aac2502249083c8fd183c144d3ba036
10 changes: 6 additions & 4 deletions internal/apigw/db/methods_vc_datastore.go
Original file line number Diff line number Diff line change
@@ -404,6 +404,8 @@ func (c *VCDatastoreColl) SearchDocuments(ctx context.Context, query *SearchDocu
sort = append(sort, bson.E{Key: "meta.document_id", Value: 1})
}

c.log.Debug("Searching documents using", "filter", filter, "findOptions", findOptions)

cursor, err := c.Coll.Find(ctx, filter, findOptions)
if err != nil {
return nil, false, err
@@ -429,16 +431,16 @@ func buildSearchDocumentsFilter(query *SearchDocumentsQuery) bson.M {
//TODO(mk): check explain to see if any indexes are needed
matskramer marked this conversation as resolved.
Show resolved Hide resolved

if query.AuthenticSource != "" {
filter["meta.authentic_source"] = query.AuthenticSource
filter["meta.authentic_source"] = bson.M{"$eq": query.AuthenticSource}
}
if query.DocumentType != "" {
filter["meta.document_type"] = query.DocumentType
filter["meta.document_type"] = bson.M{"$eq": query.DocumentType}
}
if query.DocumentID != "" {
filter["meta.document_id"] = query.DocumentID
filter["meta.document_id"] = bson.M{"$eq": query.DocumentID}
}
if query.CollectID != "" {
filter["meta.collect.id"] = query.CollectID
filter["meta.collect.id"] = bson.M{"$eq": query.CollectID}
}

identityConditions := bson.M{}
66 changes: 39 additions & 27 deletions internal/ui/static/ui.js
Original file line number Diff line number Diff line change
@@ -667,7 +667,7 @@ function buildAndDisplayModal(title) {
const closeIconId = generateUUID();
const closeButtonId = generateUUID();
const modalBodyDivId = generateUUID();
const copyButtonId = generateUUID();
const footerId = generateUUID();

const modal = document.createElement('div');
modal.id = modalId;
@@ -682,8 +682,8 @@ function buildAndDisplayModal(title) {
<section class="modal-card-body">
<div id="${modalBodyDivId}"></div>
</section>
<footer class="modal-card-foot">
<button id="${closeButtonId}" class="button is-success">Close</button> <button id="${copyButtonId}" class="button">Copy content</button>
<footer id="${footerId}" class="modal-card-foot">
<button id="${closeButtonId}" class="button is-success">Close</button>
</footer>
</div>
`;
@@ -692,11 +692,9 @@ function buildAndDisplayModal(title) {

const closeIcon = document.getElementById(closeIconId);
const closeButton = document.getElementById(closeButtonId);
const copyButton = document.getElementById(copyButtonId);

closeIcon.addEventListener('click', () => closeModalAndRemoveFromDOM(modalId));
closeButton.addEventListener('click', () => closeModalAndRemoveFromDOM(modalId));
copyButton.addEventListener('click', () => copyContentWithinDivToClipboard(modalBodyDivId));

const modalBody = modal.querySelector('.modal-card-body');
const modalBodyDiv = document.getElementById(modalBodyDivId);
@@ -705,35 +703,37 @@ function buildAndDisplayModal(title) {
modal: modal,
modalBody: modalBody,
modalBodyDiv: modalBodyDiv,
footer: document.getElementById(footerId),
};
}

function copyContentWithinDivToClipboard(divId) {
function copyContentWithinDivToClipboard(divId, jsonParseAndStringify = false) {
const contentDiv = document.getElementById(divId);

if (contentDiv) {
const content = contentDiv.textContent || contentDiv.innerText;
let content = contentDiv.textContent || contentDiv.innerText;
if (jsonParseAndStringify) {
content = JSON.stringify(JSON.parse(content));
}

if (navigator.clipboard && navigator.clipboard.writeText) {
// Modern Clipboard API
// Modern Clipboard API (requires https)
navigator.clipboard.writeText(content)
.then(() => alert('Content copied to clipboard!'))
.catch(err => {
console.error('Failed to copy content: ', err);
alert('Failed to copy content.');
alert('Failed to copy content');
});
} else {
// Fallback for older browsers
// Fallback for older browsers (or http)
const textArea = document.createElement('textarea');
textArea.value = content;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
alert('Content copied to clipboard!');
} catch (err) {
console.error('Fallback: Unable to copy', err);
alert('Failed to copy content.');
alert('Failed to copy content');
}
document.body.removeChild(textArea);
}
@@ -742,7 +742,6 @@ function copyContentWithinDivToClipboard(divId) {
}
}


function closeModalAndRemoveFromDOM(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
@@ -794,6 +793,13 @@ function displayCompleteDocumentInModal(rowData) {
}),
}).then(data => {
modalBodyDiv.innerText = JSON.stringify(data, null, 2);

const copyButton = document.createElement("button");
copyButton.id = generateUUID();
copyButton.classList.add("button");
copyButton.textContent = "Copy json";
copyButton.addEventListener('click', () => copyContentWithinDivToClipboard(modalParts.modalBodyDiv.id, true));
modalParts.footer.appendChild(copyButton);
}).catch(err => {
console.error("Unexpected error:", err);
displayErrorTag("Failed to search for documents: ", modalBodyDiv, err);
@@ -823,29 +829,28 @@ function displayQRInModal(rowData) {
return;
}


//TODO(mk): check/error handling if no qr or base64_image exist
const img = document.createElement("img");
img.src = `data:image/png;base64,${data.Documents[0].qr.base64_image}`;
modalBodyDiv.appendChild(img);

const followLinkButton = document.createElement("button");
followLinkButton.id = generateUUID();
followLinkButton.classList.add("button", "is-link");
followLinkButton.title = data.Documents[0].qr.credential_offer;
followLinkButton.textContent = "Follow QR-code (opens a new browser window or tab)";
followLinkButton.addEventListener("click", function () {
const url = data.Documents[0].qr.credential_offer;
window.open(`${url}`, "_blank");
});
modalParts.footer.appendChild(followLinkButton);

//modalBodyDiv.innerText = JSON.stringify(data, null, 2);
}).catch(err => {
console.error("Unexpected error:", err);
displayErrorTag("Failed to display QR-code: ", modalBodyDiv, err);
});


// if (doc.qr?.base64_image) {
// const img = document.createElement("img");
// img.src = `data:image/png;base64,${doc.qr.base64_image}`;
// cell1.appendChild(img);
// } else {
// const pQrNotFound = document.createElement("p");
// pQrNotFound.innerText = "No qr code found in document";
// cell1.appendChild(pQrNotFound);
// }


}

function displayCreateCredentialInModal(rowData) {
@@ -875,6 +880,13 @@ function displayCreateCredentialInModal(rowData) {
}),
}).then(data => {
modalBodyDiv.innerText = JSON.stringify(data, null, 2);

const copyButton = document.createElement("button");
copyButton.id = generateUUID();
copyButton.classList.add("button");
copyButton.textContent = "Copy json";
copyButton.addEventListener('click', () => copyContentWithinDivToClipboard(modalParts.modalBodyDiv.id, true));
modalParts.footer.appendChild(copyButton);
}).catch(err => {
console.error("Unexpected error:", err);
displayErrorTag("Failed to create credential: ", modalBodyDiv, err);