mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-10-31 05:43:41 +00:00 
			
		
		
		
	feat: add silent logs reload
This commit is contained in:
		| @ -97,7 +97,7 @@ | |||||||
|                     <input id="package-info-refresh-input" type="checkbox" class="form-check-input" value="" checked> |                     <input id="package-info-refresh-input" type="checkbox" class="form-check-input" value="" checked> | ||||||
|                     <label for="package-info-refresh-input" class="form-check-label">update pacman databases</label> |                     <label for="package-info-refresh-input" class="form-check-label">update pacman databases</label> | ||||||
|  |  | ||||||
|                     <button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()" data-bs-dismiss="modal"><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button> |                     <button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()"><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button> | ||||||
|                     <button id="package-info-remove-button" type="submit" class="btn btn-danger" onclick="packageInfoRemove()" data-bs-dismiss="modal"><i class="bi bi-trash"></i><span class="d-none d-sm-inline"> remove</span></button> |                     <button id="package-info-remove-button" type="submit" class="btn btn-danger" onclick="packageInfoRemove()" data-bs-dismiss="modal"><i class="bi bi-trash"></i><span class="d-none d-sm-inline"> remove</span></button> | ||||||
|                 {% endif %} |                 {% endif %} | ||||||
|                 {% if autorefresh_intervals %} |                 {% if autorefresh_intervals %} | ||||||
| @ -315,6 +315,69 @@ | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     function loadLogs(packageBase, onFailure) { |     function loadLogs(packageBase, onFailure) { | ||||||
|  |         const sortFn = (left, right) => left.process_id.localeCompare(right.process_id) || left.version.localeCompare(right.version); | ||||||
|  |         const compareFn = (left, right) => left.process_id === right.process_id && left.version === right.version; | ||||||
|  |  | ||||||
|  |         makeRequest( | ||||||
|  |             `/api/v2/packages/${packageBase}/logs`, | ||||||
|  |             { | ||||||
|  |                 query: { | ||||||
|  |                     architecture: repository.architecture, | ||||||
|  |                     head: true, | ||||||
|  |                     repository: repository.repository, | ||||||
|  |                 }, | ||||||
|  |                 convert: response => response.json(), | ||||||
|  |             }, | ||||||
|  |             data => { | ||||||
|  |                 const currentVersions = Array.from(packageInfoLogsVersions.children) | ||||||
|  |                     .map(el => { | ||||||
|  |                         return { | ||||||
|  |                             process_id: el.dataset.processId, | ||||||
|  |                             version: el.dataset.version, | ||||||
|  |                         }; | ||||||
|  |                     }) | ||||||
|  |                     .sort(sortFn); | ||||||
|  |                 const newVersions = data | ||||||
|  |                     .map(el => { | ||||||
|  |                         return { | ||||||
|  |                             process_id: el.process_id, | ||||||
|  |                             version: el.version, | ||||||
|  |                         }; | ||||||
|  |                     }) | ||||||
|  |                     .sort(sortFn); | ||||||
|  |  | ||||||
|  |                 if (currentVersions.equals(newVersions, compareFn)) | ||||||
|  |                     loadLogsActive(packageBase); | ||||||
|  |                 else | ||||||
|  |                     loadLogsAll(packageBase, onFailure); | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function loadLogsActive(packageBase) { | ||||||
|  |         const activeLogSelector = packageInfoLogsVersions.querySelector(".active"); | ||||||
|  |  | ||||||
|  |         if (activeLogSelector) { | ||||||
|  |             makeRequest( | ||||||
|  |                 `/api/v2/packages/${packageBase}/logs`, | ||||||
|  |                 { | ||||||
|  |                     query: { | ||||||
|  |                         architecture: repository.architecture, | ||||||
|  |                         repository: repository.repository, | ||||||
|  |                         version: activeLogSelector.dataset.version, | ||||||
|  |                         process_id: activeLogSelector.dataset.processId, | ||||||
|  |                     }, | ||||||
|  |                     convert: response => response.json(), | ||||||
|  |                 }, | ||||||
|  |                 data => { | ||||||
|  |                     activeLogSelector.dataset.logs = convertLogs(data); | ||||||
|  |                     activeLogSelector.click(); | ||||||
|  |                 }, | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function loadLogsAll(packageBase, onFailure) { | ||||||
|         makeRequest( |         makeRequest( | ||||||
|             `/api/v2/packages/${packageBase}/logs`, |             `/api/v2/packages/${packageBase}/logs`, | ||||||
|             { |             { | ||||||
| @ -440,29 +503,6 @@ | |||||||
|         packagesAdd(packageBase, [], repository, {refresh: packageInfoRefreshInput.checked}); |         packagesAdd(packageBase, [], repository, {refresh: packageInfoRefreshInput.checked}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function reloadActiveLogs(packageBase) { |  | ||||||
|         const activeLogSelector = packageInfoLogsVersions.querySelector(".active"); |  | ||||||
|  |  | ||||||
|         if (activeLogSelector) { |  | ||||||
|             makeRequest( |  | ||||||
|                 `/api/v2/packages/${packageBase}/logs`, |  | ||||||
|                 { |  | ||||||
|                     query: { |  | ||||||
|                         architecture: repository.architecture, |  | ||||||
|                         repository: repository.repository, |  | ||||||
|                         version: activeLogSelector.dataset.version, |  | ||||||
|                         process_id: activeLogSelector.dataset.processId, |  | ||||||
|                     }, |  | ||||||
|                     convert: response => response.json(), |  | ||||||
|                 }, |  | ||||||
|                 data => { |  | ||||||
|                     activeLogSelector.dataset.logs = convertLogs(data); |  | ||||||
|                     activeLogSelector.click(); |  | ||||||
|                 }, |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     function showPackageInfo(packageBase) { |     function showPackageInfo(packageBase) { | ||||||
|         const isPackageBaseSet = packageBase !== undefined; |         const isPackageBaseSet = packageBase !== undefined; | ||||||
|         if (isPackageBaseSet) { |         if (isPackageBaseSet) { | ||||||
| @ -502,7 +542,7 @@ | |||||||
|                 const packageBase = packageInfoModal.dataset.package; |                 const packageBase = packageInfoModal.dataset.package; | ||||||
|                 // we only poll status and logs here |                 // we only poll status and logs here | ||||||
|                 loadPackage(packageBase); |                 loadPackage(packageBase); | ||||||
|                 reloadActiveLogs(packageBase); |                 loadLogs(packageBase); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -218,6 +218,21 @@ | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     Array.prototype.equals = function (right, comparator) { | ||||||
|  |         let index = this.length; | ||||||
|  |         if (index !== right.length) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         while (index--) { | ||||||
|  |             if (!comparator(this[index], right[index])) { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     Date.prototype.toISOStringShort = function () { |     Date.prototype.toISOStringShort = function () { | ||||||
|         const pad = number => String(number).padStart(2, "0"); |         const pad = number => String(number).padStart(2, "0"); | ||||||
|         return `${this.getFullYear()}-${pad(this.getMonth() + 1)}-${pad(this.getDate())} ${pad(this.getHours())}:${pad(this.getMinutes())}:${pad(this.getSeconds())}`; |         return `${this.getFullYear()}-${pad(this.getMonth() + 1)}-${pad(this.getDate())} ${pad(this.getHours())}:${pad(this.getMinutes())}:${pad(this.getSeconds())}`; | ||||||
|  | |||||||
| @ -27,6 +27,9 @@ class LogsSearchSchema(PaginationSchema): | |||||||
|     request log search schema |     request log search schema | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|  |     head = fields.Boolean(metadata={ | ||||||
|  |         "description": "Return versions only without fetching logs themselves", | ||||||
|  |     }) | ||||||
|     version = fields.String(metadata={ |     version = fields.String(metadata={ | ||||||
|         "description": "Package version to search", |         "description": "Package version to search", | ||||||
|         "example": __version__, |         "example": __version__, | ||||||
|  | |||||||
| @ -17,7 +17,10 @@ | |||||||
| # You should have received a copy of the GNU General Public License | # You should have received a copy of the GNU General Public License | ||||||
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||||
| # | # | ||||||
|  | import itertools | ||||||
|  |  | ||||||
| from aiohttp.web import Response, json_response | from aiohttp.web import Response, json_response | ||||||
|  | from dataclasses import replace | ||||||
| from typing import ClassVar | from typing import ClassVar | ||||||
|  |  | ||||||
| from ahriman.models.user_access import UserAccess | from ahriman.models.user_access import UserAccess | ||||||
| @ -28,7 +31,8 @@ from ahriman.web.views.status_view_guard import StatusViewGuard | |||||||
|  |  | ||||||
|  |  | ||||||
| class LogsView(StatusViewGuard, BaseView): | class LogsView(StatusViewGuard, BaseView): | ||||||
|     """ |     """        else: | ||||||
|  |  | ||||||
|     package logs web view |     package logs web view | ||||||
|  |  | ||||||
|     Attributes: |     Attributes: | ||||||
| @ -66,5 +70,14 @@ class LogsView(StatusViewGuard, BaseView): | |||||||
|  |  | ||||||
|         logs = self.service(package_base=package_base).package_logs_get(package_base, version, process, limit, offset) |         logs = self.service(package_base=package_base).package_logs_get(package_base, version, process, limit, offset) | ||||||
|  |  | ||||||
|  |         head = self.request.query.get("head", "false") | ||||||
|  |         # pylint: disable=protected-access | ||||||
|  |         if self.configuration._convert_to_boolean(head):  # type: ignore[attr-defined] | ||||||
|  |             # logs should be sorted already | ||||||
|  |             logs = [ | ||||||
|  |                 replace(next(log_records), message="")  # remove messages | ||||||
|  |                 for _, log_records in itertools.groupby(logs, lambda log_record: log_record.log_record_id) | ||||||
|  |             ] | ||||||
|  |  | ||||||
|         response = [log_record.view() for log_record in logs] |         response = [log_record.view() for log_record in logs] | ||||||
|         return json_response(response) |         return json_response(response) | ||||||
|  | |||||||
| @ -139,3 +139,41 @@ async def test_get_not_found(client: TestClient, package_ahriman: Package) -> No | |||||||
|     response = await client.get(f"/api/v2/packages/{package_ahriman.base}/logs") |     response = await client.get(f"/api/v2/packages/{package_ahriman.base}/logs") | ||||||
|     assert response.status == 404 |     assert response.status == 404 | ||||||
|     assert not response_schema.validate(await response.json()) |     assert not response_schema.validate(await response.json()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def test_get_head(client: TestClient, package_ahriman: Package) -> None: | ||||||
|  |     """ | ||||||
|  |     must return only versions if head parameter is set | ||||||
|  |     """ | ||||||
|  |     await client.post(f"/api/v1/packages/{package_ahriman.base}", | ||||||
|  |                       json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()}) | ||||||
|  |     await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", | ||||||
|  |                       json={"created": 42.0, "message": "message 1", "version": "42"}) | ||||||
|  |     await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", | ||||||
|  |                       json={"created": 43.0, "message": "message 2", "version": "42"}) | ||||||
|  |     await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", | ||||||
|  |                       json={"created": 44.0, "message": "message 3", "version": "43"}) | ||||||
|  |     request_schema = pytest.helpers.schema_request(LogsView.get, location="querystring") | ||||||
|  |     response_schema = pytest.helpers.schema_response(LogsView.get) | ||||||
|  |  | ||||||
|  |     payload = {"head": "true"} | ||||||
|  |     assert not request_schema.validate(payload) | ||||||
|  |     response = await client.get(f"/api/v2/packages/{package_ahriman.base}/logs", params=payload) | ||||||
|  |     assert response.status == 200 | ||||||
|  |  | ||||||
|  |     logs = await response.json() | ||||||
|  |     assert not response_schema.validate(logs) | ||||||
|  |     assert logs == [ | ||||||
|  |         { | ||||||
|  |             "created": 42.0, | ||||||
|  |             "message": "", | ||||||
|  |             "version": "42", | ||||||
|  |             "process_id": LogRecordId.DEFAULT_PROCESS_ID, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "created": 44.0, | ||||||
|  |             "message": "", | ||||||
|  |             "version": "43", | ||||||
|  |             "process_id": LogRecordId.DEFAULT_PROCESS_ID, | ||||||
|  |         }, | ||||||
|  |     ] | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user