Cloudflare force create a non-glue record that is beneath a delegated child zone.

Plugin for Cloudflare. Fix error for: Cannot create a non-glue record that is beneath a delegated child zone. (Code: 89018).

  1. // ==UserScript==
  2. // @name Cloudflare force create a non-glue record that is beneath a delegated child zone.
  3. // @namespace https://medium.com/chouhsiang
  4. // @version 1.2
  5. // @description Plugin for Cloudflare. Fix error for: Cannot create a non-glue record that is beneath a delegated child zone. (Code: 89018).
  6. // @author Sean Chou
  7. // @match https://dash.cloudflare.com/*
  8. // @icon
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12. async function submit(ele) {
  13. const u = new URL(window.location);
  14. const path = u.pathname.split("/");
  15. const account = path[1];
  16. const domain = path[2];
  17.  
  18. // Check Insert or Update
  19. const form = ele.form;
  20. let record_id = form.parentElement.parentElement.id;
  21. record_id = record_id == "" ? null : record_id.split("-")[0];
  22.  
  23. // Get zone id
  24. let url = `https://dash.cloudflare.com/api/v4/zones?name=${domain}&account.id=${account}`;
  25. let response = await fetch(url);
  26. let data = await response.json();
  27. const zone = data.result[0].id;
  28.  
  29. // Get atok
  30. url = "https://dash.cloudflare.com/api/v4/system/bootstrap";
  31. response = await fetch(url);
  32. data = await response.json();
  33. const atok = data.result.data.atok;
  34.  
  35. // Get form data
  36. const type = form.querySelector("[aria-labelledby=dns-label-type]").value;
  37. let name = form.querySelector("[aria-labelledby=dns-label-name]").value;
  38. if (!name.endsWith(domain)) {
  39. name += "." + domain;
  40. }
  41. let content;
  42. if (type == "A") {
  43. content = form.querySelector(
  44. "[aria-labelledby=dns-label-ipv4_address]"
  45. ).value;
  46. } else if (type == "AAAA") {
  47. content = form.querySelector(
  48. "[aria-labelledby=dns-label-ipv6_address]"
  49. ).value;
  50. } else if (type == "CNAME") {
  51. content = form.querySelector("[aria-labelledby=dns-label-target]").value;
  52. } else {
  53. alert("Only support A、AAAA、CNAME");
  54. return;
  55. }
  56.  
  57. // Get ns record & delete
  58. const parent_domain = name.split(".").slice(1).join(".");
  59. url = `https://dash.cloudflare.com/api/v4/zones/${zone}/dns_records?type=NS&name=${parent_domain}`;
  60. response = await fetch(url);
  61. data = await response.json();
  62. const ns = data.result;
  63. for (const i of ns) {
  64. const dns_id = i.id;
  65. url = `https://dash.cloudflare.com/api/v4/zones/${zone}/dns_records/${dns_id}`;
  66. await fetch(url, {
  67. method: "DELETE",
  68. headers: {
  69. "X-Atok": atok,
  70. "X-Cross-Site-Security": "dash",
  71. },
  72. });
  73. }
  74.  
  75. // Insert or update record
  76. let d = {
  77. content: content,
  78. name: name,
  79. type: type,
  80. zone_id: zone,
  81. zone_name: domain,
  82. proxied: true,
  83. ttl: 1,
  84. comment: "",
  85. data: {},
  86. proxiable: true,
  87. tags: [],
  88. };
  89. if (record_id) {
  90. url = `https://dash.cloudflare.com/api/v4/zones/${zone}/dns_records/${record_id}`;
  91. response = await fetch(url, {
  92. method: "PUT",
  93. headers: {
  94. "Content-Type": "application/json",
  95. "X-Atok": atok,
  96. "X-Cross-Site-Security": "dash",
  97. },
  98. body: JSON.stringify(d),
  99. });
  100. data = await response.json();
  101. if (data.success) {
  102. alert("Force update succeeded");
  103. } else {
  104. alert("Force update failed:" + JSON.stringify(data.errors));
  105. }
  106. } else {
  107. url = `https://dash.cloudflare.com/api/v4/zones/${zone}/dns_records`;
  108. response = await fetch(url, {
  109. method: "POST",
  110. headers: {
  111. "Content-Type": "application/json",
  112. "X-Atok": atok,
  113. "X-Cross-Site-Security": "dash",
  114. },
  115. body: JSON.stringify(d),
  116. });
  117. data = await response.json();
  118. if (data.success) {
  119. alert("Force create succeeded");
  120. } else {
  121. alert("Force create succeeded:" + JSON.stringify(data.errors));
  122. }
  123. }
  124.  
  125. // Add ns record
  126. for (const i of ns) {
  127. url = `https://dash.cloudflare.com/api/v4/zones/${zone}/dns_records`;
  128. d = {
  129. content: i.content,
  130. name: i.name,
  131. type: "NS",
  132. zone_id: zone,
  133. zone_name: domain,
  134. };
  135. await fetch(url, {
  136. method: "POST",
  137. headers: {
  138. "Content-Type": "application/json",
  139. "X-Atok": atok,
  140. "X-Cross-Site-Security": "dash",
  141. },
  142. body: JSON.stringify(d),
  143. });
  144. }
  145. location.reload();
  146. }
  147.  
  148. async function addBtn() {
  149. const u = new URL(window.location);
  150. if (u.host == "dash.cloudflare.com" && u.pathname.endsWith("/dns/records")) {
  151. const old_btns = document.querySelectorAll(
  152. "button[data-testid=dns-record-form-save-button]"
  153. );
  154. const new_btns = document.querySelectorAll(
  155. "button[data-testid=force-save-button]"
  156. );
  157. if (old_btns.length > new_btns.length) {
  158. const new_btn = document.createElement("button");
  159. new_btn.setAttribute("data-testid", "force-save-button");
  160. new_btn.setAttribute("type", "button");
  161. new_btn.setAttribute("class", old_btns[0].className);
  162. new_btn.setAttribute(
  163. "style",
  164. "margin-left: 8px; background-color: rgb(235, 20, 3)"
  165. );
  166. new_btn.innerText = "Force Save";
  167. new_btn.onclick = async function () {
  168. await submit(this);
  169. };
  170. old_btns.forEach(function (old_btn) {
  171. if (old_btn.nextSibling == null) {
  172. old_btn.parentNode.insertBefore(new_btn, old_btn.nextSibling);
  173. }
  174. });
  175. }
  176. }
  177. }
  178.  
  179. setInterval(async () => await addBtn(), 1000);