Greasy Fork is available in English.

DevOps Projects Overview

Zeigt alle Projekte für alle Organisationen

2024-05-23 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

  1. // ==UserScript==
  2. // @name DevOps Projects Overview
  3. // @namespace Violentmonkey Scripts
  4. // @match https://dev.azure.com/*
  5. // @grant none
  6. // @version 1.0.5
  7. // @author Der_Floh
  8. // @description Zeigt alle Projekte für alle Organisationen
  9. // @license MIT
  10. // @icon https://www.svgrepo.com/show/448271/azure-devops.svg
  11. // @homepageURL https://greatest.deepsurf.us/de/scripts/469631-evalea-dsgvo-test-solver
  12. // @supportURL https://greatest.deepsurf.us/de/scripts/469631-evalea-dsgvo-test-solver/feedback
  13. // ==/UserScript==
  14.  
  15. // jshint esversion: 8
  16.  
  17. let windowUrl = window.location.toString();
  18. if (windowUrl.endsWith('/'))
  19. windowUrl = windowUrl.slice(0, -1);
  20. const slashCount = windowUrl.match(/\//g).length;
  21.  
  22. window.addEventListener('load', async () => {
  23. if (window.location.toString().endsWith('?projectonly')) {
  24. addErrorHideCss();
  25. convertToProjectOnly();
  26. } else if (window.location.toString().endsWith('?default')) {
  27. return;
  28. } else {
  29. if (slashCount !== 3) {
  30. console.log("prevent execution");
  31. return;
  32. }
  33. addErrorHideCss();
  34. showAllProjects();
  35. }
  36. });
  37.  
  38. async function showAllProjects() {
  39. const navigation = await waitForElementToExistQuery(document.body, 'div[class*="top-level-navigation"][role="navigation"]');
  40. try {
  41. const showMoreButton = await waitForElementToExistQuery(navigation, 'span[class*="action-link top-navigation-item"][role="button"]');
  42. if (showMoreButton)
  43. showMoreButton.click();
  44. } catch { }
  45.  
  46. const projectsContainer = await waitForElementToExistId('skip-to-main-content');
  47. const currentProject = await waitForElementToExistQuery(projectsContainer, 'li[class="project-card flex-row flex-grow"]');
  48. const container = currentProject.parentNode;
  49. container.classList.add('wrap-ul');
  50. addWrapUlCss();
  51.  
  52. await addProjectCards(container);
  53. }
  54.  
  55. function addWrapUlCss() {
  56. const css = `
  57. ul.wrap-ul {
  58. display: flex;
  59. flex-wrap: wrap;
  60. padding: 0;
  61. list-style-type: none;
  62. margin: 0;
  63. }
  64.  
  65. ul.wrap-ul li,
  66. ul.wrap-ul iframe {
  67. flex: 1 1 auto;
  68. margin: 5px;
  69. box-sizing: border-box;
  70. }
  71. `;
  72. const style = document.createElement('style');
  73. style.type = 'text/css';
  74. style.innerHTML = css;
  75. document.head.appendChild(style);
  76. }
  77.  
  78. function addErrorHideCss() {
  79. const css = `
  80. .tfs-unhandled-error {
  81. display: none;
  82. }
  83. `;
  84. const style = document.createElement('style');
  85. style.type = 'text/css';
  86. style.innerHTML = css;
  87. document.head.appendChild(style);
  88. }
  89.  
  90. async function convertToProjectOnly() {
  91. const projectsContainer = await waitForElementToExistId('skip-to-main-content');
  92. const currentProject = await waitForElementToExistQuery(projectsContainer, 'li[class="project-card flex-row flex-grow"]');
  93. const container = currentProject.parentNode.parentNode;
  94. container.style.height = '100%';
  95. container.firstChild.style.height = '100%';
  96. container.firstChild.firstChild.style.height = '100%';
  97. container.firstChild.firstChild.firstChild.style.height = '100%';
  98. container.firstChild.firstChild.firstChild.firstChild.style.height = '100%';
  99. const projectName = currentProject.querySelector('div[class*="project-name"]').textContent;
  100. container.onclick = (event) => {
  101. event.preventDefault();
  102. const url = `https://dev.azure.com/${window.frameElement.getAttribute('name')}/${projectName}`;
  103. window.open(url, '_blank');
  104. };
  105. keepOnlyElementAndAncestors(container);
  106. }
  107.  
  108. function keepOnlyElementAndAncestors(element) {
  109. const elementsToKeep = new Set();
  110. let currentElement = element;
  111.  
  112. while (currentElement) {
  113. elementsToKeep.add(currentElement);
  114. currentElement = currentElement.parentElement;
  115. }
  116.  
  117. function addDescendantsToSet(element) {
  118. elementsToKeep.add(element);
  119. Array.from(element.children).forEach(child => {
  120. addDescendantsToSet(child);
  121. });
  122. }
  123. addDescendantsToSet(element);
  124.  
  125. const allElements = document.body.getElementsByTagName('*');
  126. Array.from(allElements).forEach(el => {
  127. if (!elementsToKeep.has(el)) {
  128. el.remove();
  129. }
  130. });
  131. }
  132.  
  133. async function addProjectCards(baseNode) {
  134. const projectCards = [];
  135. await waitForElementToExistQuery(document.body, 'a[class*="host-link navigation-link"][role="option"]');
  136. const projects = Array.from(document.body.querySelectorAll('a[class*="host-link navigation-link"][role="option"]'));
  137. projects.shift();
  138. for (const project of projects) {
  139. if (project.href.startsWith('https://dev.azure.com'))
  140. createIFrameForProject(baseNode, project);
  141. }
  142. }
  143.  
  144. function createIFrameForProject(baseNode, project) {
  145. const iframe = document.createElement('iframe');
  146. iframe.id = project.id.replace('__bolt-host-', 'project_');
  147. iframe.setAttribute('name', project.querySelector('span').textContent);
  148. iframe.src = project.href + '?projectonly';
  149. iframe.style.border = 'none';
  150. baseNode.appendChild(iframe);
  151. }
  152.  
  153. async function waitForElementToExistId(elementId) {
  154. return new Promise(async (resolve) => {
  155. async function checkElement() {
  156. const element = document.getElementById(elementId);
  157. if (element !== null)
  158. resolve(element);
  159. else
  160. setTimeout(checkElement, 100);
  161. }
  162. await checkElement();
  163. });
  164. }
  165.  
  166. async function waitForElementToExistQuery(baseNode, query, timeout = 3000) {
  167. return new Promise((resolve, reject) => {
  168. const startTime = Date.now();
  169. async function checkElement() {
  170. const element = baseNode.querySelector(query);
  171. if (element !== null) {
  172. resolve(element);
  173. } else {
  174. if (Date.now() - startTime > timeout) {
  175. reject(new Error(`Timeout: Element with query '${query}' did not appear within ${timeout}ms`));
  176. } else {
  177. setTimeout(checkElement, 100);
  178. }
  179. }
  180. }
  181. checkElement();
  182. });
  183. }