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

fix(confighttp): do not return 200 on errors #3385

Merged
merged 8 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
4 changes: 4 additions & 0 deletions docs/Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,8 @@ INPUT = ../README.md \
HTML_EXTRA_STYLESHEET += doc-styles.css

# extra js
HTML_EXTRA_FILES += api.js
HTML_EXTRA_FILES += configuration.js

# custom aliases
ALIASES += api_examples{3|}="@htmlonly<script>(function() { let examples = generateExamples('\1', '\2', \3); document.write(createTabs(examples)); })();</script>@endhtmlonly"
130 changes: 130 additions & 0 deletions docs/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
function generateExamples(endpoint, method, body = null) {
let curlBodyString = '';
let psBodyString = '';

if (body) {
const curlJsonString = JSON.stringify(body).replace(/"/g, '\\"');
curlBodyString = ` -d "${curlJsonString}"`;
psBodyString = `-Body (ConvertTo-Json ${JSON.stringify(body)})`;
}

return {
cURL: `curl -u user:pass -X ${method.trim()} -k https://localhost:47990${endpoint.trim()}${curlBodyString}`,
Python: `import json
import requests
from requests.auth import HTTPBasicAuth

requests.${method.trim().toLowerCase()}(
auth=HTTPBasicAuth('user', 'pass'),
url='https://localhost:47990${endpoint.trim()}',
verify=False,${body ? `\n json=${JSON.stringify(body)},` : ''}
).json()`,
JavaScript: `fetch('https://localhost:47990${endpoint.trim()}', {
method: '${method.trim()}',
headers: {
'Authorization': 'Basic ' + btoa('user:pass'),
'Content-Type': 'application/json',
}${body ? `,\n body: JSON.stringify(${JSON.stringify(body)}),` : ''}
})
.then(response => response.json())
.then(data => console.log(data));`,
PowerShell: `Invoke-RestMethod \`
-SkipCertificateCheck \`
-Uri 'https://localhost:47990${endpoint.trim()}' \`
-Method ${method.trim()} \`
-Headers @{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes('user:pass'))}
${psBodyString}`
};
}

function hashString(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash |= 0; // Convert to 32bit integer
}
return hash;
}

function createTabs(examples) {
const languages = Object.keys(examples);
let tabs = '<div class="tabs-overview-container"><div class="tabs-overview">';
let content = '<div class="tab-content">';

languages.forEach((lang, index) => {
const hash = hashString(examples[lang]);
tabs += `<button class="tab-button ${index === 0 ? 'active' : ''}" onclick="openTab(event, '${lang}')"><b class="tab-title" title=" ${lang} "> ${lang} </b></button>`;
content += `<div id="${lang}" class="tabcontent" style="display: ${index === 0 ? 'block' : 'none'};">
<div class="doxygen-awesome-fragment-wrapper">
<div class="fragment">
${examples[lang].split('\n').map(line => `<div class="line">${line}</div>`).join('')}
</div>
<doxygen-awesome-fragment-copy-button id="copy-button-${lang}-${hash}" title="Copy to clipboard">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path d="M0 0h24v24H0V0z" fill="none"></path>
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"></path>
</svg>
</doxygen-awesome-fragment-copy-button>
</div>
</div>`;
});

tabs += '</div></div>';
content += '</div>';

setTimeout(() => {
languages.forEach((lang, index) => {
const hash = hashString(examples[lang]);
const copyButton = document.getElementById(`copy-button-${lang}-${hash}`);
copyButton.addEventListener('click', copyContent);
});
}, 0);

return tabs + content;
}

function copyContent() {
const content = this.previousElementSibling.cloneNode(true);
if (content instanceof Element) {
// filter out line number from file listings
content.querySelectorAll(".lineno, .ttc").forEach((node) => {
node.remove();
});
let textContent = Array.from(content.querySelectorAll('.line'))
.map(line => line.innerText)
.join('\n')
.trim(); // Join lines with newline characters and trim leading/trailing whitespace
navigator.clipboard.writeText(textContent);
this.classList.add("success");
this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>`;
window.setTimeout(() => {
this.classList.remove("success");
this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>`;
}, 980);
} else {
console.error('Failed to copy: content is not a DOM element');
}
}

function openTab(evt, lang) {
const tabcontent = document.getElementsByClassName("tabcontent");
for (const content of tabcontent) {
content.style.display = "none";
}

const tablinks = document.getElementsByClassName("tab-button");
for (const link of tablinks) {
link.className = link.className.replace(" active", "");
}

const selectedTabs = document.querySelectorAll(`#${lang}`);
for (const tab of selectedTabs) {
tab.style.display = "block";
}

const selectedButtons = document.querySelectorAll(`.tab-button[onclick*="${lang}"]`);
for (const button of selectedButtons) {
button.className += " active";
}
}
11 changes: 9 additions & 2 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Sunshine has a RESTful API which can be used to interact with the service.
Unless otherwise specified, authentication is required for all API calls. You can authenticate using
basic authentication with the admin username and password.

@htmlonly
<script src="api.js"></script>
@endhtmlonly

## GET /api/apps
@copydoc confighttp::getApps()

Expand All @@ -14,7 +18,7 @@ basic authentication with the admin username and password.
## POST /api/apps
@copydoc confighttp::saveApp()

## DELETE /api/apps{index}
## DELETE /api/apps/{index}
@copydoc confighttp::deleteApp()

## POST /api/covers/upload
Expand All @@ -32,6 +36,9 @@ basic authentication with the admin username and password.
## POST /api/restart
@copydoc confighttp::restart()

## POST /api/reset-display-device-persistence
@copydoc confighttp::resetDisplayDevicePersistence()

## POST /api/password
@copydoc confighttp::savePassword()

Expand All @@ -47,7 +54,7 @@ basic authentication with the admin username and password.
## GET /api/clients/list
@copydoc confighttp::listClients()

## GET /api/apps/close
## POST /api/apps/close
@copydoc confighttp::closeApp()

<div class="section_buttons">
Expand Down
Loading
Loading