{"id":658815,"date":"2026-03-24T21:13:54","date_gmt":"2026-03-24T13:13:54","guid":{"rendered":"http:\/\/puo.cn\/?p=658815"},"modified":"2026-03-24T21:13:59","modified_gmt":"2026-03-24T13:13:59","slug":"dmit-%e5%b0%8f%e7%bb%84%e4%bb%b6%e5%88%86%e4%ba%ab","status":"publish","type":"post","link":"http:\/\/puo.cn\/?p=658815","title":{"rendered":"dmit \u5c0f\u7ec4\u4ef6\u5206\u4eab"},"content":{"rendered":"<p>\u65e9\u4e0a\u770b\u89c1\u5927\u4f6c\u5206\u4eab\u7684 scriptable dmit\u5c0f\u7ec4\u4ef6<br \/>\n<a rel=\"nofollow\" href=\"https:\/\/www.nodeseek.com\/post-661149-1\">https:\/\/www.nodeseek.com\/post-661149-1<\/a><br \/>\n\u4f18\u5316\u4e86\u4e00\u4e0b\uff0c\u591a\u52a0\u4e86\u4e00\u4e9b\u4fe1\u606f\u548c\u5c0f\u529f\u80fd\uff0c\u9700\u8981\u53ef\u81ea\u53d6\uff0c\u56e0\u4e3a\u6211\u53ea\u6709\u4e00\u53f0\u8bbe\u5907\uff0c\u5355\u8bbe\u5907\u4f7f\u7528\u662f\u6ca1\u95ee\u9898\u7684\uff0c\u591a\u8bbe\u5907\u4e0d\u4fdd\u8bc1\u54c8\uff5e\uff5e<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-658819\" title=\"20260325051341657\" src=\"\/wp-content\/uploads\/replace\/54bc225ddcd1568f8ffd02783304f808.png\" alt=\"20260325051341657\" width=\"686\" height=\"599\" \/><br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-658820\" title=\"20260325051341787\" src=\"\/wp-content\/uploads\/replace\/c4d0c24030648847aed83114101514ce.png\" alt=\"20260325051341787\" width=\"1320\" height=\"716\" \/><br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-658821\" title=\"20260325051342527\" src=\"\/wp-content\/uploads\/replace\/a666b478a12549bed2060cf16c6fe7e8.png\" alt=\"20260325051342527\" width=\"1084\" height=\"1080\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>const ScriptName = &#8220;DMIT&#8221;;<br \/>\nconst COOKIE_NAME = `${ScriptName}_WHMCSlogin_auth_tk`;<br \/>\nconst PRODUCTS_CACHE_KEY = `${ScriptName}_products_cache`;<br \/>\nconst CANVAS_SIZE = 50;<br \/>\nconst RADIUS = 20;<br \/>\nconst LINE_WIDTH = 4;<br \/>\nconst productsIndex = args.widgetParameter ? parseInt(args.widgetParameter) &#8211; 1 : 0;<br \/>\nfunction getCookie() {<br \/>\nif (Keychain.contains(COOKIE_NAME)) {<br \/>\nreturn Keychain.get(COOKIE_NAME);<br \/>\n}<br \/>\nreturn null;<br \/>\n}<br \/>\nfunction getCachedProducts() {<br \/>\nif (Keychain.contains(PRODUCTS_CACHE_KEY)) {<br \/>\ntry {<br \/>\nconst cached = JSON.parse(Keychain.get(PRODUCTS_CACHE_KEY));<br \/>\nif (cached.data &amp;&amp; cached.data.length &gt; 0) return cached.data;<br \/>\n} catch (e) {}<br \/>\n}<br \/>\nreturn null;<br \/>\n}<br \/>\nfunction setCachedProducts(products) {<br \/>\nKeychain.set(PRODUCTS_CACHE_KEY, JSON.stringify({ data: products }));<br \/>\n}<br \/>\nfunction formatMB(mb) {<br \/>\nif (mb &gt;= 1024) return `${(mb \/ 1024).toFixed(1)} GB`;<br \/>\nreturn `${mb.toFixed(1)} MB`;<br \/>\n}<br \/>\nfunction formatUptime(seconds) {<br \/>\nconst days = Math.floor(seconds \/ 86400);<br \/>\nconst hours = Math.floor((seconds % 86400) \/ 3600);<br \/>\nconst mins = Math.floor((seconds % 3600) \/ 60);<br \/>\nreturn { days, hours, mins };<br \/>\n}<br \/>\nasync function safeLoadJSON(request) {<br \/>\nconst text = await request.loadString();<br \/>\ntry {<br \/>\nreturn JSON.parse(text);<br \/>\n} catch (e) {<br \/>\nthrow new Error(&#8220;\u670d\u52a1\u5668\u8fd4\u56de\u4e86\u975e JSON \u6570\u636e\uff0c\u53ef\u80fd\u88ab Cloudflare \u62e6\u622a&#8221;);<br \/>\n}<br \/>\n}<br \/>\nasync function loadProductDetailStandard(id) {<br \/>\nconst resp = new Request(`https:\/\/www.dmit.io\/clientarea.php?action=productdetails&amp;id=${id}&amp;json=1&amp;pure=1&amp;page=standard&amp;subaction=whmcsdetail`);<br \/>\nresp.method = &#8220;GET&#8221;;<br \/>\nconst cookie = getCookie();<br \/>\nif (!cookie) {<br \/>\nthrow new Error(&#8220;\u672a\u627e\u5230 Cookie&#8221;);<br \/>\n}<br \/>\nresp.headers = {<br \/>\n&#8220;Cookie&#8221;: cookie<br \/>\n};<br \/>\nconst result = await safeLoadJSON(resp);<br \/>\nif (result.result === &#8220;success&#8221; &amp;&amp; result.success) {<br \/>\nreturn result.success;<br \/>\n}<br \/>\nthrow new Error(result.error || &#8220;\u672a\u77e5\u9519\u8bef&#8221;);<br \/>\n}<br \/>\nasync function loadProducts() {<br \/>\ntry {<br \/>\nconst reqp = new Request(&#8220;https:\/\/www.dmit.io\/clientarea.php?action=services&#8221;);<br \/>\nreqp.method = &#8220;GET&#8221;;<br \/>\nconst cookie = getCookie();<br \/>\nif (!cookie) {<br \/>\nthrow new Error(&#8220;\u672a\u627e\u5230 Cookie&#8221;);<br \/>\n}<br \/>\nreqp.headers = {<br \/>\n&#8220;Cookie&#8221;: cookie<br \/>\n};<br \/>\nconst htmlContent = await reqp.loadString();<br \/>\nconst products = parseProductsFromHtml(htmlContent);<br \/>\nif (products.length &gt; 0) {<br \/>\nsetCachedProducts(products);<br \/>\nreturn products;<br \/>\n}<br \/>\nreturn getCachedProducts() || [];<br \/>\n}<br \/>\ncatch (error) {<br \/>\nconsole.error(error);<br \/>\nreturn getCachedProducts() || [];<br \/>\n}<br \/>\n}<br \/>\nasync function loadAllGraphData(id) {<br \/>\ntry {<br \/>\nconst resp = new Request(`https:\/\/www.dmit.io\/clientarea.php?action=productdetails&amp;id=${id}&amp;json=1&amp;pure=1&amp;page=graph&amp;subaction=charts`);<br \/>\nresp.method = &#8220;POST&#8221;;<br \/>\nresp.body = &#8220;data%5Btimeframe%5D=hour&#8221;;<br \/>\nconst cookie = getCookie();<br \/>\nif (!cookie) throw new Error(&#8220;\u672a\u627e\u5230 Cookie&#8221;);<br \/>\nresp.headers = { &#8220;Cookie&#8221;: cookie };<br \/>\nconst result = await safeLoadJSON(resp);<br \/>\nif (result.result === &#8220;success&#8221; &amp;&amp; result.success) return result.success;<br \/>\nreturn {};<br \/>\n} catch (error) {<br \/>\nconsole.error(error);<br \/>\nreturn {};<br \/>\n}<br \/>\n}<br \/>\nasync function loadVMDetails(id) {<br \/>\ntry {<br \/>\nconst resp = new Request(`https:\/\/www.dmit.io\/clientarea.php?action=productdetails&amp;id=${id}&amp;json=1&amp;pure=1&amp;page=home&amp;subaction=detailsVM`);<br \/>\nresp.method = &#8220;GET&#8221;;<br \/>\nconst cookie = getCookie();<br \/>\nif (!cookie) throw new Error(&#8220;\u672a\u627e\u5230 Cookie&#8221;);<br \/>\nresp.headers = { &#8220;Cookie&#8221;: cookie };<br \/>\nconst result = await safeLoadJSON(resp);<br \/>\nif (result.result === &#8220;success&#8221; &amp;&amp; result.success) return result.success;<br \/>\nreturn null;<br \/>\n} catch (error) {<br \/>\nconsole.error(error);<br \/>\nreturn null;<br \/>\n}<br \/>\n}<br \/>\nfunction applyPrimaryTextColor(text) {<br \/>\ntext.textColor = Color.dynamic(Color.black(), Color.white());<br \/>\n}<br \/>\nfunction applySecondaryTextColor(text) {<br \/>\ntext.textColor = Color.dynamic(Color.gray(), Color.lightGray());<br \/>\n}<br \/>\nfunction pickProductWithFallback(products, preferredIndex) {<br \/>\nif (products.length === 0) {<br \/>\nreturn null;<br \/>\n}<br \/>\nconst startIndex = Math.min(Math.max(preferredIndex, 0), products.length &#8211; 1);<br \/>\nfor (let i = startIndex; i &gt;= 0; i&#8211;) {<br \/>\nconst product = products[i];<br \/>\nif (product) {<br \/>\nreturn product;<br \/>\n}<br \/>\n}<br \/>\nfor (let i = startIndex + 1; i &lt; products.length; i++) {<br \/>\nconst product = products[i];<br \/>\nif (product) {<br \/>\nreturn product;<br \/>\n}<br \/>\n}<br \/>\nreturn null;<br \/>\n}<br \/>\nfunction showWidgetErrorAndFinish(widget, message) {<br \/>\nconst errorText = widget.addText(message);<br \/>\nerrorText.textColor = Color.red();<br \/>\nerrorText.font = Font.semiboldSystemFont(14);<br \/>\nScript.setWidget(widget);<br \/>\nScript.complete();<br \/>\n}<br \/>\nasync function handleSettingsMenu() {<br \/>\nconst setting = new Alert();<br \/>\nsetting.title = &#8220;\u8bbe\u7f6e&#8221;;<br \/>\nsetting.message = &#8220;\u914d\u7f6eDMIT\u5c0f\u7ec4\u4ef6&#8221;;<br \/>\nsetting.addAction(&#8220;\u8bbe\u7f6e Cookie&#8221;);<br \/>\nsetting.addDestructiveAction(&#8220;\u5220\u9664 Cookie&#8221;);<br \/>\nsetting.addAction(&#8220;\u53d6\u6d88&#8221;);<br \/>\nconst response = await setting.present();<br \/>\nif (response === 0) {<br \/>\nconst cookieInput = new Alert();<br \/>\ncookieInput.title = &#8220;\u8f93\u5165 Cookie&#8221;;<br \/>\ncookieInput.message = &#8220;\u8bf7\u8f93\u5165\u60a8\u7684 cookie \u503c:&#8221;;<br \/>\ncookieInput.addTextField(&#8220;Cookie&#8221;);<br \/>\ncookieInput.addAction(&#8220;\u4fdd\u5b58&#8221;);<br \/>\ncookieInput.addAction(&#8220;\u53d6\u6d88&#8221;);<br \/>\nconst cookieResponse = await cookieInput.present();<br \/>\nif (cookieResponse === 0) {<br \/>\nconst cookieValue = (cookieInput.textFieldValue(0) ?? &#8220;&#8221;).trim();<br \/>\nif (!cookieValue) {<br \/>\nconst invalidAlert = new Alert();<br \/>\ninvalidAlert.title = &#8220;Cookie \u8f93\u5165\u6709\u8bef&#8221;;<br \/>\ninvalidAlert.message = &#8220;Cookie \u4e0d\u80fd\u4e3a\u7a7a\uff0c\u8bf7\u91cd\u65b0\u8f93\u5165\u3002&#8221;;<br \/>\ninvalidAlert.addAction(&#8220;\u597d\u7684&#8221;);<br \/>\nawait invalidAlert.present();<br \/>\n}<br \/>\nelse {<br \/>\nKeychain.set(COOKIE_NAME, cookieValue);<br \/>\nconst confirmAlert = new Alert();<br \/>\nconfirmAlert.title = &#8220;\u4fdd\u5b58\u6210\u529f&#8221;;<br \/>\nconfirmAlert.message = &#8220;\u60a8\u7684 cookie \u5df2\u6210\u529f\u4fdd\u5b58\u3002&#8221;;<br \/>\nconfirmAlert.addAction(&#8220;\u597d\u7684&#8221;);<br \/>\nawait confirmAlert.present();<br \/>\n}<br \/>\n}<br \/>\nreturn true;<br \/>\n}<br \/>\nif (response === 1) {<br \/>\nconst confirmDelete = new Alert();<br \/>\nconfirmDelete.title = &#8220;\u786e\u8ba4\u5220\u9664&#8221;;<br \/>\nconfirmDelete.message = &#8220;\u60a8\u786e\u5b9a\u8981\u5220\u9664\u5df2\u4fdd\u5b58\u7684 Cookie \u5417\uff1f&#8221;;<br \/>\nconfirmDelete.addDestructiveAction(&#8220;\u5220\u9664&#8221;);<br \/>\nconfirmDelete.addAction(&#8220;\u53d6\u6d88&#8221;);<br \/>\nconst deleteResponse = await confirmDelete.present();<br \/>\nif (deleteResponse === 0 &amp;&amp; Keychain.contains(COOKIE_NAME)) {<br \/>\nKeychain.remove(COOKIE_NAME);<br \/>\nconst deletedAlert = new Alert();<br \/>\ndeletedAlert.title = &#8220;\u5220\u9664\u6210\u529f&#8221;;<br \/>\ndeletedAlert.message = &#8220;\u5df2\u6210\u529f\u5220\u9664\u4fdd\u5b58\u7684 Cookie\u3002&#8221;;<br \/>\ndeletedAlert.addAction(&#8220;\u597d\u7684&#8221;);<br \/>\nawait deletedAlert.present();<br \/>\n}<br \/>\nreturn true;<br \/>\n}<br \/>\nreturn false;<br \/>\n}<br \/>\nfunction parseProductsFromHtml(htmlContent) {<br \/>\nconst products = [];<br \/>\nconst cleanText = (value) =&gt; {<br \/>\nreturn value<br \/>\n.replace(\/&lt;[^&gt;]*&gt;\/g, &#8221; &#8220;)<br \/>\n.replace(\/&amp;nbsp;\/g, &#8221; &#8220;)<br \/>\n.replace(\/\\s+\/g, &#8221; &#8220;)<br \/>\n.trim();<br \/>\n};<br \/>\nconst rowRegex = \/&lt;li[^&gt;]*class=&#8221;[^&#8221;]*\\bproducts-item\\b[^&#8221;]*&#8221;[^&gt;]*products_id=&#8221;(\\d+)&#8221;[^&gt;]*&gt;[\\s\\S]*?&lt;\\\/li&gt;\/g;<br \/>\nlet rowMatch;<br \/>\nwhile ((rowMatch = rowRegex.exec(htmlContent)) !== null) {<br \/>\nconst rowHtml = rowMatch[0];<br \/>\nconst id = parseInt(rowMatch[1], 10);<br \/>\nconst nameMatch = rowHtml.match(\/&lt;div class=&#8221;products-title-text&#8221;&gt;([\\s\\S]*?)&lt;\\\/div&gt;\/);<br \/>\nconst name = nameMatch ? cleanText(nameMatch[1]) : &#8220;&#8221;;<br \/>\nconst domainMatch = rowHtml.match(\/&lt;div class=&#8221;products-domain&#8221;&gt;([\\s\\S]*?)&lt;\\\/div&gt;\/);<br \/>\nlet code = &#8220;&#8221;;<br \/>\nif (domainMatch) {<br \/>\nconst domainText = cleanText(domainMatch[1]);<br \/>\ncode = domainText.split(&#8221; &#8211; &#8220;)[0].trim();<br \/>\n}<br \/>\nif (!code) {<br \/>\nconst fallbackCodeMatch = rowHtml.match(\/products-name=&#8221;([^&#8221;]+)&#8221;\/);<br \/>\ncode = fallbackCodeMatch ? fallbackCodeMatch[1].trim() : &#8220;&#8221;;<br \/>\n}<br \/>\nif (!name || !code || Number.isNaN(id)) {<br \/>\ncontinue;<br \/>\n}<br \/>\nproducts.push({ id, name, code });<br \/>\n}<br \/>\nreturn products;<br \/>\n}<br \/>\nfunction gradientColorByRatio(ratio) {<br \/>\nconst t = Math.max(0, Math.min(1, ratio));<br \/>\nconst start = { r: 46, g: 204, b: 113 };<br \/>\nconst end = { r: 231, g: 76, b: 60 };<br \/>\nconst r = Math.round(start.r + (end.r &#8211; start.r) * t);<br \/>\nconst g = Math.round(start.g + (end.g &#8211; start.g) * t);<br \/>\nconst b = Math.round(start.b + (end.b &#8211; start.b) * t);<br \/>\nreturn new Color(`#${r.toString(16).padStart(2, &#8220;0&#8221;)}${g.toString(16).padStart(2, &#8220;0&#8221;)}${b.toString(16).padStart(2, &#8220;0&#8221;)}`);<br \/>\n}<br \/>\nfunction drawCircleProgress(progress) {<br \/>\nconst clampedProgress = Math.max(0, Math.min(1, progress));<br \/>\nconst context = new DrawContext();<br \/>\ncontext.size = new Size(CANVAS_SIZE, CANVAS_SIZE);<br \/>\ncontext.opaque = false;<br \/>\ncontext.respectScreenScale = true;<br \/>\nconst center = new Point(CANVAS_SIZE \/ 2, CANVAS_SIZE \/ 2);<br \/>\ncontext.setStrokeColor(new Color(&#8220;#222&#8221;));<br \/>\ncontext.setLineWidth(LINE_WIDTH);<br \/>\ncontext.strokeEllipse(new Rect(center.x &#8211; RADIUS, center.y &#8211; RADIUS, RADIUS * 2, RADIUS * 2));<br \/>\nconst totalDegrees = 360 * clampedProgress;<br \/>\nconst stepDegrees = 3.6;<br \/>\nfor (let i = 0; i &lt; totalDegrees; i += stepDegrees) {<br \/>\nconst ratio = totalDegrees === 0 ? 0 : i \/ totalDegrees;<br \/>\ncontext.setFillColor(gradientColorByRatio(ratio));<br \/>\nconst angle = (i &#8211; 90) * (Math.PI \/ 180);<br \/>\nconst x = center.x + RADIUS * Math.cos(angle);<br \/>\nconst y = center.y + RADIUS * Math.sin(angle);<br \/>\ncontext.fillEllipse(new Rect(x &#8211; LINE_WIDTH \/ 2, y &#8211; LINE_WIDTH \/ 2, LINE_WIDTH, LINE_WIDTH));<br \/>\n}<br \/>\nconst percentText = `${Math.round(clampedProgress * 100)}%`;<br \/>\ncontext.setTextColor(new Color(&#8220;#FFFFFF&#8221;));<br \/>\ncontext.setFont(Font.semiboldSystemFont(10));<br \/>\ncontext.setTextAlignedCenter();<br \/>\ncontext.drawTextInRect(percentText, new Rect(0, 18, CANVAS_SIZE, 14));<br \/>\nreturn context.getImage();<br \/>\n}<br \/>\nfunction createProgressBarImage(total, current, width, height, fillColor, backgroundColor) {<br \/>\nconst context = new DrawContext();<br \/>\ncontext.size = new Size(width, height);<br \/>\ncontext.opaque = false;<br \/>\ncontext.respectScreenScale = true;<br \/>\nconst bgPath = new Path();<br \/>\nbgPath.addRoundedRect(new Rect(0, 0, width, height), height \/ 2, height \/ 2);<br \/>\ncontext.setFillColor(backgroundColor);<br \/>\ncontext.addPath(bgPath);<br \/>\ncontext.fillPath();<br \/>\nconst progressWidth = Math.floor(width * Math.min(current \/ total, 1));<br \/>\nconst fillPath = new Path();<br \/>\nfillPath.addRoundedRect(new Rect(0, 0, progressWidth, height), height \/ 2, height \/ 2);<br \/>\ncontext.setFillColor(fillColor);<br \/>\ncontext.addPath(fillPath);<br \/>\ncontext.fillPath();<br \/>\nreturn context.getImage();<br \/>\n}<br \/>\nfunction createLineChartImage(data, width, height, lineColor = new Color(&#8220;#1F6FD4&#8221;), secondaryLineColor = new Color(&#8220;#16A085&#8221;)) {<br \/>\nconst context = new DrawContext();<br \/>\ncontext.size = new Size(width, height);<br \/>\ncontext.opaque = false;<br \/>\ncontext.respectScreenScale = true;<br \/>\nconst seriesList = Array.isArray(data[0])<br \/>\n? data.filter((series) =&gt; series.length &gt; 0)<br \/>\n: [data];<br \/>\nif (seriesList.length === 0) {<br \/>\nreturn context.getImage();<br \/>\n}<br \/>\nconst padding = 4;<br \/>\nconst chartWidth = Math.max(1, width &#8211; padding * 2);<br \/>\nconst chartHeight = Math.max(1, height &#8211; padding * 2);<br \/>\nconst mergedData = seriesList.flat();<br \/>\nconst minValue = Math.min(&#8230;mergedData);<br \/>\nconst maxValue = Math.max(&#8230;mergedData);<br \/>\nconst range = Math.max(1e-6, maxValue &#8211; minValue);<br \/>\nconst getPoints = (series) =&gt; {<br \/>\nreturn series.map((value, index) =&gt; {<br \/>\nconst x = series.length === 1<br \/>\n? padding + chartWidth \/ 2<br \/>\n: padding + (index \/ (series.length &#8211; 1)) * chartWidth;<br \/>\nconst normalized = (value &#8211; minValue) \/ range;<br \/>\nconst y = padding + chartHeight * (1 &#8211; normalized);<br \/>\nreturn new Point(x, y);<br \/>\n});<br \/>\n};<br \/>\nconst drawSeries = (points, color, fillAlpha) =&gt; {<br \/>\nif (points.length === 0) {<br \/>\nreturn;<br \/>\n}<br \/>\nconst areaPath = new Path();<br \/>\nareaPath.move(new Point(points[0].x, height &#8211; padding));<br \/>\nfor (const point of points) {<br \/>\nareaPath.addLine(point);<br \/>\n}<br \/>\nareaPath.addLine(new Point(points[points.length &#8211; 1].x, height &#8211; padding));<br \/>\nareaPath.closeSubpath();<br \/>\ncontext.setFillColor(new Color(color.hex, fillAlpha));<br \/>\ncontext.addPath(areaPath);<br \/>\ncontext.fillPath();<br \/>\nif (points.length === 1) {<br \/>\ncontext.setFillColor(color);<br \/>\ncontext.fillEllipse(new Rect(points[0].x &#8211; 1.5, points[0].y &#8211; 1.5, 3, 3));<br \/>\nreturn;<br \/>\n}<br \/>\nconst linePath = new Path();<br \/>\nlinePath.move(points[0]);<br \/>\nfor (let i = 1; i &lt; points.length; i++) {<br \/>\nconst mid = new Point((points[i &#8211; 1].x + points[i].x) \/ 2, (points[i &#8211; 1].y + points[i].y) \/ 2);<br \/>\nlinePath.addQuadCurve(mid, points[i &#8211; 1]);<br \/>\n}<br \/>\nlinePath.addLine(points[points.length &#8211; 1]);<br \/>\ncontext.setStrokeColor(color);<br \/>\ncontext.setLineWidth(1.6);<br \/>\ncontext.addPath(linePath);<br \/>\ncontext.strokePath();<br \/>\n};<br \/>\ndrawSeries(getPoints(seriesList[0]), lineColor, 0.12);<br \/>\nif (seriesList.length &gt; 1) {<br \/>\ndrawSeries(getPoints(seriesList[1]), secondaryLineColor, 0.08);<br \/>\n}<br \/>\nreturn context.getImage();<br \/>\n}<br \/>\nasync function renderAccessoryCircular(widget, product) {<br \/>\nconst productDetails = await loadProductDetailStandard(product.id.toString());<br \/>\nconst progress = productDetails.bwusage \/ productDetails.bwlimit;<br \/>\nconst chartImage = drawCircleProgress(progress);<br \/>\nwidget.addImage(chartImage);<br \/>\nScript.setWidget(widget);<br \/>\n}<br \/>\nasync function renderAccessoryInline(widget, product) {<br \/>\nconst productDetails = await loadProductDetailStandard(product.id.toString());<br \/>\nconst progress = productDetails.bwusage \/ productDetails.bwlimit;<br \/>\nconst chartImage = drawCircleProgress(progress);<br \/>\nwidget.addImage(chartImage);<br \/>\nconst label = widget.addText(`${product.code}|${Math.round(progress * 100)}%`);<br \/>\nlabel.font = Font.semiboldSystemFont(12);<br \/>\napplyPrimaryTextColor(label);<br \/>\nScript.setWidget(widget);<br \/>\n}<br \/>\nasync function renderAccessoryRectangular(widget, product) {<br \/>\nconst productDetails = await loadProductDetailStandard(product.id.toString());<br \/>\nconst line1Stack = widget.addStack();<br \/>\nconst ipText = line1Stack.addText(`IP: ${productDetails.dedicatedip}`);<br \/>\nipText.font = Font.semiboldSystemFont(12);<br \/>\napplyPrimaryTextColor(ipText);<br \/>\nline1Stack.addSpacer();<br \/>\nconst updateTimeText = line1Stack.addText(new Date().toLocaleTimeString([], { hour: &#8220;2-digit&#8221;, minute: &#8220;2-digit&#8221; }));<br \/>\nupdateTimeText.font = Font.semiboldSystemFont(12);<br \/>\napplySecondaryTextColor(updateTimeText);<br \/>\nconst line2Stack = widget.addStack();<br \/>\nconst trafficChart = createProgressBarImage(productDetails.bwlimit, productDetails.bwusage, 100, 12, new Color(&#8220;#1F6FD4&#8221;), new Color(&#8220;#E0E0E0&#8221;));<br \/>\nline2Stack.addImage(trafficChart);<br \/>\nconst line3Stack = widget.addStack();<br \/>\nconst usedGb = (productDetails.bwusage \/ 1024).toFixed(2);<br \/>\nconst totalGb = (productDetails.bwlimit \/ 1024).toFixed(2);<br \/>\nline3Stack.addSpacer();<br \/>\nconst trafficText = line3Stack.addText(`\u6d41\u91cf: ${usedGb}GB \/ ${totalGb}GB`);<br \/>\ntrafficText.font = Font.semiboldSystemFont(10);<br \/>\napplySecondaryTextColor(trafficText);<br \/>\nline3Stack.addSpacer();<br \/>\nScript.setWidget(widget);<br \/>\n}<br \/>\nasync function renderSmall(widget, product) {<br \/>\nconst productDetails = await loadProductDetailStandard(product.id.toString());<br \/>\nconst isDark = Device.isUsingDarkAppearance();<br \/>\nconst primary = Color.dynamic(Color.black(), Color.white());<br \/>\nconst secondary = Color.dynamic(new Color(&#8220;#6C6C70&#8221;), new Color(&#8220;#8E8E93&#8221;));<br \/>\nconst green = new Color(&#8220;#30D158&#8221;);<br \/>\nconst red = new Color(&#8220;#FF3B30&#8221;);<br \/>\nconst progressBg = isDark ? new Color(&#8220;#3A3A3C&#8221;) : new Color(&#8220;#D1D1D6&#8221;);<br \/>\nwidget.backgroundColor = Color.dynamic(new Color(&#8220;#FFFFFF&#8221;), new Color(&#8220;#1C1C1E&#8221;));<br \/>\nwidget.setPadding(12, 12, 12, 12);<br \/>\n\/\/ \u5934\u90e8\u884c\uff1a\u540d\u79f0 + \u72b6\u6001<br \/>\nconst headerRow = widget.addStack();<br \/>\nheaderRow.centerAlignContent();<br \/>\nconst nameText = headerRow.addText(productDetails.hostname || product.name);<br \/>\nnameText.font = Font.boldSystemFont(13);<br \/>\nnameText.textColor = primary;<br \/>\nnameText.lineLimit = 1;<br \/>\nconst codeText = widget.addText(product.code);<br \/>\ncodeText.font = Font.systemFont(9);<br \/>\ncodeText.textColor = secondary;<br \/>\nwidget.addSpacer(4);<br \/>\n\/\/ \u6d41\u91cf\u5361\u7247<br \/>\nconst trafficCard = widget.addStack();<br \/>\ntrafficCard.layoutVertically();<br \/>\ntrafficCard.backgroundColor = Color.dynamic(new Color(&#8220;#F2F2F7&#8221;), new Color(&#8220;#2C2C2E&#8221;));<br \/>\ntrafficCard.cornerRadius = 10;<br \/>\ntrafficCard.setPadding(6, 10, 6, 10);<br \/>\n\/\/ \u6807\u9898\u884c<br \/>\nconst trafficHeader = trafficCard.addStack();<br \/>\ntrafficHeader.centerAlignContent();<br \/>\nconst globeSym = SFSymbol.named(&#8220;globe&#8221;);<br \/>\nglobeSym.applyFont(Font.systemFont(9));<br \/>\nconst globeImg = trafficHeader.addImage(globeSym.image);<br \/>\nglobeImg.imageSize = new Size(10, 10);<br \/>\nglobeImg.tintColor = secondary;<br \/>\ntrafficHeader.addSpacer(3);<br \/>\nconst trafficLabel = trafficHeader.addText(&#8220;Traffic&#8221;);<br \/>\ntrafficLabel.font = Font.systemFont(9);<br \/>\ntrafficLabel.textColor = secondary;<br \/>\ntrafficHeader.addSpacer();<br \/>\nconst usedGB = (productDetails.bwusage \/ 1024).toFixed(1);<br \/>\nconst totalGB = (productDetails.bwlimit \/ 1024).toFixed(1);<br \/>\nconst usageLabel = trafficHeader.addText(`${usedGB}\/${totalGB} GB`);<br \/>\nusageLabel.font = Font.systemFont(9);<br \/>\nusageLabel.textColor = secondary;<br \/>\ntrafficCard.addSpacer(2);<br \/>\n\/\/ \u767e\u5206\u6bd4 + \u5269\u4f59<br \/>\nconst usagePercent = Math.round(productDetails.bwusage \/ productDetails.bwlimit * 100);<br \/>\nconst pctRow = trafficCard.addStack();<br \/>\npctRow.centerAlignContent();<br \/>\nconst percentText = pctRow.addText(`${usagePercent}%`);<br \/>\npercentText.font = Font.boldSystemFont(28);<br \/>\npercentText.textColor = usagePercent &gt;= 80 ? red : green;<br \/>\npctRow.addSpacer();<br \/>\nconst remainGB = ((productDetails.bwlimit &#8211; productDetails.bwusage) \/ 1024).toFixed(1);<br \/>\nconst remainText = pctRow.addText(`\u5269\u4f59 ${remainGB} GB`);<br \/>\nremainText.font = Font.systemFont(9);<br \/>\nremainText.textColor = secondary;<br \/>\ntrafficCard.addSpacer(2);<br \/>\n\/\/ \u8fdb\u5ea6\u6761<br \/>\nconst progressBar = createProgressBarImage(<br \/>\nproductDetails.bwlimit, productDetails.bwusage,<br \/>\n130, 6, green, progressBg<br \/>\n);<br \/>\nconst barRow = trafficCard.addStack();<br \/>\nbarRow.size = new Size(0, 6);<br \/>\nconst progressImg = barRow.addImage(progressBar);<br \/>\nprogressImg.imageSize = new Size(130, 6);<br \/>\nwidget.addSpacer(4);<br \/>\n\/\/ \u5e95\u90e8\u4fe1\u606f\u884c<br \/>\nconst bottomRow = widget.addStack();<br \/>\nbottomRow.centerAlignContent();<br \/>\nconst regDate = new Date(productDetails.regdate);<br \/>\nif (!isNaN(regDate.getTime())) {<br \/>\nconst now = new Date();<br \/>\nconst regDay = regDate.getDate();<br \/>\nconst lastReset = new Date(now.getFullYear(), now.getMonth(), regDay);<br \/>\nif (lastReset &gt; now) lastReset.setMonth(lastReset.getMonth() &#8211; 1);<br \/>\nconst nextReset = new Date(lastReset);<br \/>\nnextReset.setMonth(nextReset.getMonth() + 1);<br \/>\nconst daysUntilReset = Math.ceil((nextReset &#8211; now) \/ 86400000);<br \/>\nconst resetText = bottomRow.addText(`${daysUntilReset}\u5929\u540e\u91cd\u7f6e`);<br \/>\nresetText.font = Font.systemFont(9);<br \/>\nresetText.textColor = secondary;<br \/>\n}<br \/>\nbottomRow.addSpacer();<br \/>\nconst invoiceDateStr = productDetails.nextinvoicedate || &#8220;&#8221;;<br \/>\nconst invoiceDate = new Date(invoiceDateStr);<br \/>\nif (!isNaN(invoiceDate.getTime())) {<br \/>\nconst daysLeft = Math.ceil((invoiceDate.getTime() &#8211; Date.now()) \/ 86400000);<br \/>\nconst renewBadge = bottomRow.addStack();<br \/>\nrenewBadge.backgroundColor = new Color(green.hex, 0.2);<br \/>\nrenewBadge.cornerRadius = 4;<br \/>\nrenewBadge.setPadding(1, 4, 1, 4);<br \/>\nconst renewText = renewBadge.addText(`\u7eed\u8d39 ${daysLeft}d`);<br \/>\nrenewText.font = Font.boldSystemFont(9);<br \/>\nrenewText.textColor = daysLeft &lt;= 30 ? red : green;<br \/>\n}<br \/>\nScript.setWidget(widget);<br \/>\n}<br \/>\nasync function renderMedium(widget, product) {<br \/>\nconst productDetails = await loadProductDetailStandard(product.id.toString());<br \/>\nconst isDark = Device.isUsingDarkAppearance();<br \/>\nconst primary = Color.dynamic(Color.black(), Color.white());<br \/>\nconst secondary = Color.dynamic(new Color(&#8220;#6C6C70&#8221;), new Color(&#8220;#8E8E93&#8221;));<br \/>\nconst green = new Color(&#8220;#30D158&#8221;);<br \/>\nconst red = new Color(&#8220;#FF3B30&#8221;);<br \/>\nconst blue = new Color(&#8220;#007AFF&#8221;);<br \/>\nconst orange = new Color(&#8220;#FF9F0A&#8221;);<br \/>\nconst progressBg = isDark ? new Color(&#8220;#3A3A3C&#8221;) : new Color(&#8220;#D1D1D6&#8221;);<br \/>\nwidget.backgroundColor = Color.dynamic(new Color(&#8220;#FFFFFF&#8221;), new Color(&#8220;#1C1C1E&#8221;));<br \/>\nwidget.setPadding(12, 14, 12, 14);<br \/>\nconst nameText = widget.addText(productDetails.hostname || product.name);<br \/>\nnameText.font = Font.boldSystemFont(14);<br \/>\nnameText.textColor = primary;<br \/>\nnameText.lineLimit = 1;<br \/>\nconst subRow = widget.addStack();<br \/>\nsubRow.spacing = 6;<br \/>\nconst codeText = subRow.addText(product.code);<br \/>\ncodeText.font = Font.systemFont(10);<br \/>\ncodeText.textColor = secondary;<br \/>\nconst ipText = subRow.addText(productDetails.dedicatedip || &#8220;&#8221;);<br \/>\nipText.font = new Font(&#8220;Menlo&#8221;, 10);<br \/>\nipText.textColor = secondary;<br \/>\nwidget.addSpacer();<br \/>\n\/\/ \u6d41\u91cf\u5361\u7247<br \/>\nconst trafficCard = widget.addStack();<br \/>\ntrafficCard.layoutVertically();<br \/>\ntrafficCard.backgroundColor = Color.dynamic(new Color(&#8220;#F2F2F7&#8221;), new Color(&#8220;#2C2C2E&#8221;));<br \/>\ntrafficCard.cornerRadius = 10;<br \/>\ntrafficCard.setPadding(8, 12, 8, 12);<br \/>\n\/\/ \u6807\u9898\u884c<br \/>\nconst trafficHeader = trafficCard.addStack();<br \/>\ntrafficHeader.centerAlignContent();<br \/>\nconst globeSymbol = SFSymbol.named(&#8220;globe&#8221;);<br \/>\nglobeSymbol.applyFont(Font.systemFont(11));<br \/>\nconst globeImg = trafficHeader.addImage(globeSymbol.image);<br \/>\nglobeImg.imageSize = new Size(12, 12);<br \/>\nglobeImg.tintColor = secondary;<br \/>\ntrafficHeader.addSpacer(3);<br \/>\nconst trafficLabel = trafficHeader.addText(&#8220;Traffic&#8221;);<br \/>\ntrafficLabel.font = Font.systemFont(11);<br \/>\ntrafficLabel.textColor = secondary;<br \/>\ntrafficHeader.addSpacer();<br \/>\nconst usedGB = (productDetails.bwusage \/ 1024).toFixed(1);<br \/>\nconst totalGB = (productDetails.bwlimit \/ 1024).toFixed(1);<br \/>\nconst usageLabel = trafficHeader.addText(`${usedGB} \/ ${totalGB} GB`);<br \/>\nusageLabel.font = Font.boldSystemFont(11);<br \/>\nusageLabel.textColor = primary;<br \/>\ntrafficCard.addSpacer(4);<br \/>\n\/\/ \u767e\u5206\u6bd4 + \u5269\u4f59<br \/>\nconst usagePercent = Math.round(productDetails.bwusage \/ productDetails.bwlimit * 100);<br \/>\nconst pctRow = trafficCard.addStack();<br \/>\npctRow.centerAlignContent();<br \/>\nconst percentText = pctRow.addText(`${usagePercent}%`);<br \/>\npercentText.font = Font.boldSystemFont(30);<br \/>\npercentText.textColor = usagePercent &gt;= 80 ? red : green;<br \/>\npctRow.addSpacer();<br \/>\nconst remainGB = ((productDetails.bwlimit &#8211; productDetails.bwusage) \/ 1024).toFixed(1);<br \/>\nconst remainLabel = pctRow.addText(`\u5269\u4f59 ${remainGB} GB`);<br \/>\nremainLabel.font = Font.systemFont(9);<br \/>\nremainLabel.textColor = secondary;<br \/>\ntrafficCard.addSpacer(4);<br \/>\n\/\/ \u8fdb\u5ea6\u6761<br \/>\nconst progressBar = createProgressBarImage(<br \/>\nproductDetails.bwlimit, productDetails.bwusage,<br \/>\n280, 8, green, progressBg<br \/>\n);<br \/>\nconst barRow = trafficCard.addStack();<br \/>\nbarRow.size = new Size(0, 8);<br \/>\nconst progressImg = barRow.addImage(progressBar);<br \/>\nprogressImg.imageSize = new Size(280, 8);<br \/>\ntrafficCard.addSpacer(4);<br \/>\n\/\/ \u5165\u7ad9\u51fa\u7ad9 + \u65e5\u5747 + \u91cd\u7f6e<br \/>\nconst inOutRow = trafficCard.addStack();<br \/>\ninOutRow.centerAlignContent();<br \/>\nconst inArrow = inOutRow.addText(&#8220;\u2199&#8221;);<br \/>\ninArrow.font = Font.systemFont(10);<br \/>\ninArrow.textColor = blue;<br \/>\ninOutRow.addSpacer(2);<br \/>\nconst inLabel = inOutRow.addText(`\u5165\u7ad9 ${formatMB(productDetails.bwusage_in || 0)}`);<br \/>\ninLabel.font = Font.systemFont(10);<br \/>\ninLabel.textColor = secondary;<br \/>\ninOutRow.addSpacer(8);<br \/>\nconst outArrow = inOutRow.addText(&#8220;\u2197&#8221;);<br \/>\noutArrow.font = Font.systemFont(10);<br \/>\noutArrow.textColor = orange;<br \/>\ninOutRow.addSpacer(2);<br \/>\nconst outLabel = inOutRow.addText(`\u51fa\u7ad9 ${formatMB(productDetails.bwusage_out || 0)}`);<br \/>\noutLabel.font = Font.systemFont(10);<br \/>\noutLabel.textColor = secondary;<br \/>\ntrafficCard.addSpacer(2);<br \/>\nconst bottomRow = trafficCard.addStack();<br \/>\nbottomRow.centerAlignContent();<br \/>\nconst regDate = new Date(productDetails.regdate);<br \/>\nif (!isNaN(regDate.getTime())) {<br \/>\nconst now = new Date();<br \/>\nconst regDay = regDate.getDate();<br \/>\nconst lastReset = new Date(now.getFullYear(), now.getMonth(), regDay);<br \/>\nif (lastReset &gt; now) lastReset.setMonth(lastReset.getMonth() &#8211; 1);<br \/>\nconst daysSinceReset = Math.max(1, Math.floor((now &#8211; lastReset) \/ 86400000));<br \/>\nconst dailyAvgMB = productDetails.bwusage \/ daysSinceReset;<br \/>\nconst nextReset = new Date(lastReset);<br \/>\nnextReset.setMonth(nextReset.getMonth() + 1);<br \/>\nconst daysUntilReset = Math.ceil((nextReset &#8211; now) \/ 86400000);<br \/>\nconst avgText = bottomRow.addText(`\u65e5\u5747 ${formatMB(dailyAvgMB)}`);<br \/>\navgText.font = Font.systemFont(9);<br \/>\navgText.textColor = secondary;<br \/>\nbottomRow.addSpacer(8);<br \/>\nconst resetText = bottomRow.addText(`${daysUntilReset}\u5929\u540e\u91cd\u7f6e`);<br \/>\nresetText.font = Font.systemFont(9);<br \/>\nresetText.textColor = secondary;<br \/>\n}<br \/>\nbottomRow.addSpacer();<br \/>\nconst invoiceDateStr = productDetails.nextinvoicedate || &#8220;&#8221;;<br \/>\nconst invoiceDate = new Date(invoiceDateStr);<br \/>\nif (!isNaN(invoiceDate.getTime())) {<br \/>\nconst daysLeft = Math.ceil((invoiceDate.getTime() &#8211; Date.now()) \/ 86400000);<br \/>\nconst renewBadge = bottomRow.addStack();<br \/>\nrenewBadge.backgroundColor = new Color(green.hex, 0.2);<br \/>\nrenewBadge.cornerRadius = 4;<br \/>\nrenewBadge.setPadding(1, 4, 1, 4);<br \/>\nconst renewText = renewBadge.addText(`${daysLeft}d`);<br \/>\nrenewText.font = Font.boldSystemFont(9);<br \/>\nrenewText.textColor = daysLeft &lt;= 30 ? red : green;<br \/>\nbottomRow.addSpacer(4);<br \/>\nconst dateText = bottomRow.addText(invoiceDateStr.split(&#8221; &#8220;)[0]);<br \/>\ndateText.font = Font.systemFont(9);<br \/>\ndateText.textColor = secondary;<br \/>\n}<\/p>\n<p>Script.setWidget(widget);<\/p>\n<p>}<br \/>\nasync function renderLarge(widget, product) {<\/p>\n<p>const pid = product.id.toString();<br \/>\nconst [productDetails, vmDetails, allGraphData] = await Promise.all([<br \/>\nloadProductDetailStandard(pid),<br \/>\nloadVMDetails(pid),<br \/>\nloadAllGraphData(pid),<br \/>\n]);<\/p>\n<p>const cpuData = allGraphData.cpu || { datasets: [] };<br \/>\nconst memData = allGraphData.mem || { datasets: [] };<br \/>\nconst netData = allGraphData.net || { datasets: [] };<br \/>\nconst isDark = Device.isUsingDarkAppearance();<br \/>\nconst theme = {<br \/>\nbg: Color.dynamic(new Color(&#8220;#FFFFFF&#8221;), new Color(&#8220;#1C1C1E&#8221;)),<br \/>\ncard: Color.dynamic(new Color(&#8220;#F2F2F7&#8221;), new Color(&#8220;#2C2C2E&#8221;)),<br \/>\nprimary: Color.dynamic(Color.black(), Color.white()),<br \/>\nsecondary: Color.dynamic(new Color(&#8220;#6C6C70&#8221;), new Color(&#8220;#8E8E93&#8221;)),<br \/>\nprogressBg: isDark ? new Color(&#8220;#3A3A3C&#8221;) : new Color(&#8220;#D1D1D6&#8221;),<br \/>\ngreen: new Color(&#8220;#30D158&#8221;),<br \/>\nblue: new Color(&#8220;#007AFF&#8221;),<br \/>\norange: new Color(&#8220;#FF9F0A&#8221;),<br \/>\nred: new Color(&#8220;#FF3B30&#8221;),<br \/>\n};<br \/>\nwidget.backgroundColor = theme.bg;<br \/>\nwidget.setPadding(12, 14, 12, 14);<\/p>\n<p>\/\/ === ROW 1: HEADER ===<br \/>\nconst headerRow = widget.addStack();<br \/>\nheaderRow.centerAlignContent();<br \/>\nconst headerLeft = headerRow.addStack();<br \/>\nheaderLeft.layoutVertically();<br \/>\nconst nameText = headerLeft.addText(productDetails.hostname || product.name);<br \/>\nnameText.font = Font.boldSystemFont(16);<br \/>\nnameText.textColor = theme.primary;<br \/>\nnameText.lineLimit = 1;<br \/>\nconst subInfo = headerLeft.addStack();<br \/>\nsubInfo.spacing = 6;<br \/>\nconst codeText = subInfo.addText(product.code);<br \/>\ncodeText.font = Font.systemFont(10);<br \/>\ncodeText.textColor = theme.secondary;<br \/>\nconst ipLabel = subInfo.addText(productDetails.dedicatedip || &#8220;&#8221;);<br \/>\nipLabel.font = new Font(&#8220;Menlo&#8221;, 10);<br \/>\nipLabel.textColor = theme.secondary;<br \/>\nheaderRow.addSpacer();<br \/>\nconst isRunning = vmDetails &amp;&amp; vmDetails.status === &#8220;Running&#8221;;<br \/>\nconst statusBadge = headerRow.addStack();<br \/>\nstatusBadge.backgroundColor = new Color((isRunning ? theme.green : theme.red).hex, 0.15);<br \/>\nstatusBadge.cornerRadius = 6;<br \/>\nstatusBadge.setPadding(3, 8, 3, 8);<br \/>\nstatusBadge.centerAlignContent();<br \/>\nconst dotCtx = new DrawContext();<br \/>\ndotCtx.size = new Size(8, 8);<br \/>\ndotCtx.opaque = false;<br \/>\ndotCtx.respectScreenScale = true;<br \/>\ndotCtx.setFillColor(isRunning ? theme.green : theme.red);<br \/>\ndotCtx.fillEllipse(new Rect(0, 0, 8, 8));<br \/>\nconst dotImg = statusBadge.addImage(dotCtx.getImage());<br \/>\ndotImg.imageSize = new Size(8, 8);<br \/>\nstatusBadge.addSpacer(4);<br \/>\nconst statusText = statusBadge.addText(isRunning ? &#8220;\u5728\u7ebf&#8221; : &#8220;\u79bb\u7ebf&#8221;);<br \/>\nstatusText.font = Font.boldSystemFont(11);<br \/>\nstatusText.textColor = isRunning ? theme.green : theme.red;<\/p>\n<p>widget.addSpacer();<\/p>\n<p>\/\/ === ROW 2: [uptime card] + [renewal card] ===<br \/>\nconst row2 = widget.addStack();<br \/>\nrow2.spacing = 8;<br \/>\n\/\/ &#8212; Uptime Card &#8212;<br \/>\nconst uptimeCard = row2.addStack();<br \/>\nuptimeCard.layoutVertically();<br \/>\nuptimeCard.backgroundColor = theme.card;<br \/>\nuptimeCard.cornerRadius = 12;<br \/>\nuptimeCard.setPadding(8, 12, 8, 12);<br \/>\nconst uptimeHeader = uptimeCard.addStack();<br \/>\nuptimeHeader.centerAlignContent();<br \/>\nconst serverSym = SFSymbol.named(&#8220;server.rack&#8221;);<br \/>\nserverSym.applyFont(Font.systemFont(11));<br \/>\nconst serverIcon = uptimeHeader.addImage(serverSym.image);<br \/>\nserverIcon.imageSize = new Size(13, 13);<br \/>\nserverIcon.tintColor = theme.secondary;<br \/>\nuptimeHeader.addSpacer(4);<br \/>\nconst serverLabel = uptimeHeader.addText(&#8220;\u670d\u52a1\u5668&#8221;);<br \/>\nserverLabel.font = Font.systemFont(11);<br \/>\nserverLabel.textColor = theme.secondary;<br \/>\nuptimeHeader.addSpacer();<br \/>\nuptimeCard.addSpacer();<br \/>\nif (vmDetails &amp;&amp; vmDetails.uptime) {<br \/>\nconst ut = formatUptime(vmDetails.uptime);<br \/>\nconst utDaysRow = uptimeCard.addStack();<br \/>\nutDaysRow.centerAlignContent();<br \/>\nconst utDays = utDaysRow.addText(`${ut.days}`);<br \/>\nutDays.font = Font.boldSystemFont(28);<br \/>\nutDays.textColor = theme.primary;<br \/>\nconst utDayUnit = utDaysRow.addText(` \u5929`);<br \/>\nutDayUnit.font = Font.systemFont(12);<br \/>\nutDayUnit.textColor = theme.secondary;<br \/>\nconst utDetail = uptimeCard.addText(`${ut.hours}h ${ut.mins}m`);<br \/>\nutDetail.font = Font.systemFont(10);<br \/>\nutDetail.textColor = theme.secondary;<br \/>\n} else {<br \/>\nconst naText = uptimeCard.addText(&#8220;N\/A&#8221;);<br \/>\nnaText.font = Font.boldSystemFont(24);<br \/>\nnaText.textColor = theme.secondary;<br \/>\n}<br \/>\nuptimeCard.addSpacer();<br \/>\n\/\/ &#8212; Renewal Card &#8212;<br \/>\nconst renewCard = row2.addStack();<br \/>\nrenewCard.layoutVertically();<br \/>\nrenewCard.backgroundColor = theme.card;<br \/>\nrenewCard.cornerRadius = 12;<br \/>\nrenewCard.setPadding(8, 12, 8, 12);<br \/>\nconst renewHeader = renewCard.addStack();<br \/>\nrenewHeader.centerAlignContent();<br \/>\nconst calSym = SFSymbol.named(&#8220;calendar&#8221;);<br \/>\ncalSym.applyFont(Font.systemFont(11));<br \/>\nconst calIcon = renewHeader.addImage(calSym.image);<br \/>\ncalIcon.imageSize = new Size(13, 13);<br \/>\ncalIcon.tintColor = theme.secondary;<br \/>\nrenewHeader.addSpacer(4);<br \/>\nconst renewLabel = renewHeader.addText(&#8220;\u7eed\u8d39&#8221;);<br \/>\nrenewLabel.font = Font.systemFont(11);<br \/>\nrenewLabel.textColor = theme.secondary;<br \/>\nrenewHeader.addSpacer();<br \/>\nrenewCard.addSpacer();<br \/>\nconst invoiceDateStr = productDetails.nextinvoicedate || &#8220;&#8221;;<br \/>\nconst invoiceDate = new Date(invoiceDateStr);<br \/>\nif (!isNaN(invoiceDate.getTime())) {<br \/>\nconst daysLeft = Math.ceil((invoiceDate.getTime() &#8211; Date.now()) \/ 86400000);<br \/>\nconst daysColor = daysLeft &lt;= 30 ? theme.red : theme.green;<br \/>\nconst daysRow = renewCard.addStack();<br \/>\ndaysRow.centerAlignContent();<br \/>\nconst daysVal = daysRow.addText(`${daysLeft}`);<br \/>\ndaysVal.font = Font.boldSystemFont(28);<br \/>\ndaysVal.textColor = daysColor;<br \/>\nconst daysUnit = daysRow.addText(` \u5929`);<br \/>\ndaysUnit.font = Font.systemFont(12);<br \/>\ndaysUnit.textColor = theme.secondary;<br \/>\nconst dateLabel = renewCard.addText(invoiceDateStr.split(&#8221; &#8220;)[0]);<br \/>\ndateLabel.font = Font.systemFont(10);<br \/>\ndateLabel.textColor = theme.secondary;<br \/>\n} else {<br \/>\nconst naText = renewCard.addText(&#8220;N\/A&#8221;);<br \/>\nnaText.font = Font.boldSystemFont(24);<br \/>\nnaText.textColor = theme.secondary;<br \/>\n}<br \/>\nrenewCard.addSpacer();<\/p>\n<p>widget.addSpacer();<\/p>\n<p>\/\/ === ROW 3: TRAFFIC CARD (full width) ===<br \/>\nconst trafficCard = widget.addStack();<br \/>\ntrafficCard.layoutVertically();<br \/>\ntrafficCard.backgroundColor = theme.card;<br \/>\ntrafficCard.cornerRadius = 12;<br \/>\ntrafficCard.setPadding(10, 12, 10, 12);<br \/>\n\/\/ traffic header<br \/>\nconst trafficHeader = trafficCard.addStack();<br \/>\ntrafficHeader.centerAlignContent();<br \/>\nconst globeSymbol = SFSymbol.named(&#8220;globe&#8221;);<br \/>\nglobeSymbol.applyFont(Font.systemFont(11));<br \/>\nconst globeImg = trafficHeader.addImage(globeSymbol.image);<br \/>\nglobeImg.imageSize = new Size(12, 12);<br \/>\nglobeImg.tintColor = theme.secondary;<br \/>\ntrafficHeader.addSpacer(3);<br \/>\nconst trafficLabel = trafficHeader.addText(&#8220;Traffic&#8221;);<br \/>\ntrafficLabel.font = Font.systemFont(11);<br \/>\ntrafficLabel.textColor = theme.secondary;<br \/>\ntrafficHeader.addSpacer();<br \/>\nconst usagePercent = Math.round(productDetails.bwusage \/ productDetails.bwlimit * 100);<br \/>\nconst usedGB = (productDetails.bwusage \/ 1024).toFixed(1);<br \/>\nconst totalGB = (productDetails.bwlimit \/ 1024).toFixed(1);<br \/>\nconst usageText = trafficHeader.addText(`${usedGB} \/ ${totalGB} GB`);<br \/>\nusageText.font = Font.boldSystemFont(11);<br \/>\nusageText.textColor = theme.primary;<br \/>\ntrafficCard.addSpacer(4);<br \/>\n\/\/ percentage row<br \/>\nconst trafficMainRow = trafficCard.addStack();<br \/>\ntrafficMainRow.centerAlignContent();<br \/>\nconst percentText = trafficMainRow.addText(`${usagePercent}%`);<br \/>\npercentText.font = Font.boldSystemFont(22);<br \/>\npercentText.textColor = usagePercent &gt;= 80 ? theme.red : theme.green;<br \/>\ntrafficMainRow.addSpacer();<br \/>\nconst remainGB = ((productDetails.bwlimit &#8211; productDetails.bwusage) \/ 1024).toFixed(1);<br \/>\nconst remainLabel = trafficMainRow.addText(`\u5269\u4f59 ${remainGB} GB`);<br \/>\nremainLabel.font = Font.systemFont(9);<br \/>\nremainLabel.textColor = theme.secondary;<br \/>\ntrafficCard.addSpacer(4);<br \/>\n\/\/ progress bar (full width)<br \/>\nconst progressBar = createProgressBarImage(<br \/>\nproductDetails.bwlimit, productDetails.bwusage,<br \/>\n300, 8, theme.green, theme.progressBg<br \/>\n);<br \/>\nconst barRow = trafficCard.addStack();<br \/>\nbarRow.size = new Size(0, 8);<br \/>\nconst progressImg = barRow.addImage(progressBar);<br \/>\nprogressImg.imageSize = new Size(300, 8);<br \/>\ntrafficCard.addSpacer(4);<br \/>\n\/\/ in\/out row<br \/>\nconst inOutRow = trafficCard.addStack();<br \/>\ninOutRow.centerAlignContent();<br \/>\nconst inArrow = inOutRow.addText(&#8220;\u2199&#8221;);<br \/>\ninArrow.font = Font.systemFont(10);<br \/>\ninArrow.textColor = theme.blue;<br \/>\ninOutRow.addSpacer(2);<br \/>\nconst inLabel = inOutRow.addText(`\u5165\u7ad9 ${formatMB(productDetails.bwusage_in || 0)}`);<br \/>\ninLabel.font = Font.systemFont(10);<br \/>\ninLabel.textColor = theme.secondary;<br \/>\ninOutRow.addSpacer();<br \/>\nconst outArrow = inOutRow.addText(&#8220;\u2197&#8221;);<br \/>\noutArrow.font = Font.systemFont(10);<br \/>\noutArrow.textColor = theme.orange;<br \/>\ninOutRow.addSpacer(2);<br \/>\nconst outLabel = inOutRow.addText(`\u51fa\u7ad9 ${formatMB(productDetails.bwusage_out || 0)}`);<br \/>\noutLabel.font = Font.systemFont(10);<br \/>\noutLabel.textColor = theme.secondary;<br \/>\n\/\/ daily avg + reset<br \/>\nconst regDate = new Date(productDetails.regdate);<br \/>\nif (!isNaN(regDate.getTime())) {<br \/>\ntrafficCard.addSpacer(3);<br \/>\nconst now = new Date();<br \/>\nconst regDay = regDate.getDate();<br \/>\nconst lastReset = new Date(now.getFullYear(), now.getMonth(), regDay);<br \/>\nif (lastReset &gt; now) lastReset.setMonth(lastReset.getMonth() &#8211; 1);<br \/>\nconst daysSinceReset = Math.max(1, Math.floor((now &#8211; lastReset) \/ 86400000));<br \/>\nconst dailyAvgMB = productDetails.bwusage \/ daysSinceReset;<br \/>\nconst nextReset = new Date(lastReset);<br \/>\nnextReset.setMonth(nextReset.getMonth() + 1);<br \/>\nconst daysUntilReset = Math.ceil((nextReset &#8211; now) \/ 86400000);<br \/>\nconst statsRow = trafficCard.addStack();<br \/>\nstatsRow.centerAlignContent();<br \/>\nconst avgLabel = statsRow.addText(`\u65e5\u5747 ${formatMB(dailyAvgMB)}`);<br \/>\navgLabel.font = Font.systemFont(10);<br \/>\navgLabel.textColor = theme.secondary;<br \/>\nstatsRow.addSpacer();<br \/>\nconst resetLabel = statsRow.addText(`${daysUntilReset} \u5929\u540e\u91cd\u7f6e`);<br \/>\nresetLabel.font = Font.systemFont(10);<br \/>\nresetLabel.textColor = theme.secondary;<br \/>\n}<\/p>\n<p>widget.addSpacer();<\/p>\n<p>\/\/ === ROW 4: [CPU chart] [RAM chart] [NET chart] + [detail card] ===<br \/>\nconst row3 = widget.addStack();<br \/>\nrow3.spacing = 6;<br \/>\n\/\/ &#8212; 3 chart cards &#8212;<br \/>\nconst chartsCol = row3.addStack();<br \/>\nchartsCol.layoutVertically();<br \/>\nchartsCol.spacing = 6;<br \/>\n\/\/ top row: CPU + RAM<br \/>\nconst chartRow1 = chartsCol.addStack();<br \/>\nchartRow1.spacing = 6;<br \/>\nfunction makeChartCard(parent, iconName, label, iconColor, datasets, minCount, chartColor, secondaryChartColor) {<br \/>\nconst card = parent.addStack();<br \/>\ncard.layoutVertically();<br \/>\ncard.backgroundColor = theme.card;<br \/>\ncard.cornerRadius = 10;<br \/>\ncard.setPadding(6, 6, 4, 6);<br \/>\nconst h = card.addStack();<br \/>\nh.centerAlignContent();<br \/>\nconst sym = SFSymbol.named(iconName);<br \/>\nsym.applyFont(Font.systemFont(9));<br \/>\nconst icon = h.addImage(sym.image);<br \/>\nicon.imageSize = new Size(10, 10);<br \/>\nicon.tintColor = iconColor;<br \/>\nh.addSpacer(2);<br \/>\nconst t = h.addText(label);<br \/>\nt.font = Font.systemFont(9);<br \/>\nt.textColor = theme.secondary;<br \/>\ncard.addSpacer(2);<br \/>\nif (datasets.length &gt;= minCount) {<br \/>\nconst chartData = minCount &gt;= 2<br \/>\n? [datasets[0].data, datasets[1].data]<br \/>\n: datasets[0].data;<br \/>\nconst chart = createLineChartImage(chartData, 80, 30, chartColor, secondaryChartColor);<br \/>\ncard.addImage(chart);<br \/>\n}<br \/>\n}<br \/>\nmakeChartCard(chartRow1, &#8220;cpu&#8221;, &#8220;CPU&#8221;, theme.orange, cpuData.datasets, 1, theme.blue);<br \/>\nmakeChartCard(chartRow1, &#8220;memorychip&#8221;, &#8220;RAM&#8221;, theme.green, memData.datasets, 1, new Color(&#8220;#1662a0&#8221;));<br \/>\n\/\/ bottom: NET (wider)<br \/>\nconst chartRow2 = chartsCol.addStack();<br \/>\nconst netCard = chartRow2.addStack();<br \/>\nnetCard.layoutVertically();<br \/>\nnetCard.backgroundColor = theme.card;<br \/>\nnetCard.cornerRadius = 10;<br \/>\nnetCard.setPadding(6, 6, 4, 6);<br \/>\nconst netH = netCard.addStack();<br \/>\nnetH.centerAlignContent();<br \/>\nconst netSym = SFSymbol.named(&#8220;antenna.radiowaves.left.and.right&#8221;);<br \/>\nnetSym.applyFont(Font.systemFont(9));<br \/>\nconst netIcon = netH.addImage(netSym.image);<br \/>\nnetIcon.imageSize = new Size(10, 10);<br \/>\nnetIcon.tintColor = theme.orange;<br \/>\nnetH.addSpacer(2);<br \/>\nconst netLabel = netH.addText(&#8220;NET&#8221;);<br \/>\nnetLabel.font = Font.systemFont(9);<br \/>\nnetLabel.textColor = theme.secondary;<br \/>\nnetCard.addSpacer(2);<br \/>\nif (netData.datasets &amp;&amp; netData.datasets.length &gt;= 2) {<br \/>\nconst netChart = createLineChartImage(<br \/>\n[netData.datasets[0].data, netData.datasets[1].data],<br \/>\n170, 30, new Color(&#8220;#1F6FD4&#8221;), new Color(&#8220;#16A085&#8221;)<br \/>\n);<br \/>\nnetCard.addImage(netChart);<br \/>\n}<br \/>\n\/\/ &#8212; Right: detail card (spans full height) &#8212;<br \/>\nconst detailCard = row3.addStack();<br \/>\ndetailCard.layoutVertically();<br \/>\ndetailCard.backgroundColor = theme.card;<br \/>\ndetailCard.cornerRadius = 12;<br \/>\ndetailCard.setPadding(8, 10, 8, 10);<br \/>\nfunction addDetailRow(parent, label, value, valColor) {<br \/>\nconst row = parent.addStack();<br \/>\nrow.centerAlignContent();<br \/>\nconst l = row.addText(label);<br \/>\nl.font = Font.systemFont(9);<br \/>\nl.textColor = theme.secondary;<br \/>\nrow.addSpacer();<br \/>\nconst v = row.addText(value);<br \/>\nv.font = Font.boldSystemFont(9);<br \/>\nv.textColor = valColor || theme.primary;<br \/>\nv.lineLimit = 1;<br \/>\nparent.addSpacer(3);<br \/>\n}<br \/>\naddDetailRow(detailCard, &#8220;\u6ce8\u518c&#8221;, productDetails.regdate ? productDetails.regdate.split(&#8221; &#8220;)[0] : &#8220;N\/A&#8221;);<br \/>\nif (!isNaN(invoiceDate.getTime())) {<br \/>\naddDetailRow(detailCard, &#8220;\u5230\u671f&#8221;, invoiceDateStr.split(&#8221; &#8220;)[0]);<br \/>\n}<br \/>\naddDetailRow(detailCard, &#8220;\u4ea7\u54c1&#8221;, product.name);<br \/>\n\/\/ CPU value<br \/>\nif (cpuData.datasets &amp;&amp; cpuData.datasets.length &gt; 0 &amp;&amp; cpuData.datasets[0].data.length &gt; 0) {<br \/>\nconst cpuVal = cpuData.datasets[0].data[cpuData.datasets[0].data.length &#8211; 1];<br \/>\naddDetailRow(detailCard, &#8220;CPU&#8221;, `${cpuVal.toFixed(1)}%`);<br \/>\n}<br \/>\n\/\/ Memory (bytes -&gt; percentage using maxmem)<br \/>\nif (vmDetails &amp;&amp; vmDetails.maxmem &amp;&amp; vmDetails.mem) {<br \/>\nconst memDisplay = `${(vmDetails.mem \/ vmDetails.maxmem * 100).toFixed(1)}%`;<br \/>\naddDetailRow(detailCard, &#8220;\u5185\u5b58&#8221;, memDisplay);<br \/>\n}<br \/>\n\/\/ NET speed<br \/>\nif (netData.datasets &amp;&amp; netData.datasets.length &gt;= 2 &amp;&amp; netData.datasets[0].data.length &gt; 0) {<br \/>\nconst fmtSpeed = (v) =&gt; {<br \/>\nif (v &gt;= 1024 * 1024) return `${(v \/ 1024 \/ 1024).toFixed(1)}M\/s`;<br \/>\nif (v &gt;= 1024) return `${(v \/ 1024).toFixed(0)}K\/s`;<br \/>\nreturn `${v.toFixed(0)}B\/s`;<br \/>\n};<br \/>\nconst lastIn = netData.datasets[0].data[netData.datasets[0].data.length &#8211; 1];<br \/>\nconst lastOut = netData.datasets[1].data[netData.datasets[1].data.length &#8211; 1];<br \/>\naddDetailRow(detailCard, &#8220;\u2199\u5165\u7ad9&#8221;, fmtSpeed(lastIn));<br \/>\naddDetailRow(detailCard, &#8220;\u2197\u51fa\u7ad9&#8221;, fmtSpeed(lastOut));<br \/>\n}<\/p>\n<p>Script.setWidget(widget);<br \/>\n}<br \/>\nasync function main() {<br \/>\nif (config.runsInApp) {<br \/>\nconst hasCookie = getCookie();<br \/>\nif (!hasCookie) {<br \/>\nawait handleSettingsMenu();<br \/>\nScript.complete();<br \/>\nreturn;<br \/>\n}<br \/>\nconst menu = new Alert();<br \/>\nmenu.title = &#8220;DMIT \u5c0f\u7ec4\u4ef6&#8221;;<br \/>\nmenu.addAction(&#8220;\u9884\u89c8\u5927\u53f7\u7ec4\u4ef6&#8221;);<br \/>\nmenu.addAction(&#8220;\u9884\u89c8\u4e2d\u53f7\u7ec4\u4ef6&#8221;);<br \/>\nmenu.addAction(&#8220;\u9884\u89c8\u5c0f\u53f7\u7ec4\u4ef6&#8221;);<br \/>\nmenu.addAction(&#8220;\u8bbe\u7f6e&#8221;);<br \/>\nmenu.addCancelAction(&#8220;\u53d6\u6d88&#8221;);<br \/>\nconst choice = await menu.present();<br \/>\nif (choice === 3) {<br \/>\nawait handleSettingsMenu();<br \/>\nScript.complete();<br \/>\nreturn;<br \/>\n}<br \/>\nif (choice === -1) {<br \/>\nScript.complete();<br \/>\nreturn;<br \/>\n}<br \/>\nlet products = getCachedProducts();<br \/>\nif (!products) {<br \/>\nproducts = await loadProducts();<br \/>\n}<br \/>\nif (products.length === 0) {<br \/>\nconst errAlert = new Alert();<br \/>\nerrAlert.title = &#8220;\u9519\u8bef&#8221;;<br \/>\nerrAlert.message = &#8220;\u672a\u627e\u5230\u4ea7\u54c1\u6570\u636e\uff0c\u8bf7\u68c0\u67e5 Cookie \u662f\u5426\u6709\u6548\u3002&#8221;;<br \/>\nerrAlert.addAction(&#8220;\u91cd\u65b0\u8bbe\u7f6e Cookie&#8221;);<br \/>\nerrAlert.addCancelAction(&#8220;\u53d6\u6d88&#8221;);<br \/>\nconst errChoice = await errAlert.present();<br \/>\nif (errChoice === 0) {<br \/>\nawait handleSettingsMenu();<br \/>\n}<br \/>\nScript.complete();<br \/>\nreturn;<br \/>\n}<br \/>\nconst product = pickProductWithFallback(products, productsIndex);<br \/>\nif (!product) {<br \/>\nconst noProductAlert = new Alert();<br \/>\nnoProductAlert.title = &#8220;\u9519\u8bef&#8221;;<br \/>\nnoProductAlert.message = &#8220;\u672a\u627e\u5230\u53ef\u7528\u4ea7\u54c1&#8221;;<br \/>\nnoProductAlert.addAction(&#8220;\u597d\u7684&#8221;);<br \/>\nawait noProductAlert.present();<br \/>\nScript.complete();<br \/>\nreturn;<br \/>\n}<br \/>\nconst widget = new ListWidget();<br \/>\nif (choice === 0) {<br \/>\nawait renderLarge(widget, product);<br \/>\nawait widget.presentLarge();<br \/>\n} else if (choice === 1) {<br \/>\nawait renderMedium(widget, product);<br \/>\nawait widget.presentMedium();<br \/>\n} else {<br \/>\nawait renderSmall(widget, product);<br \/>\nawait widget.presentSmall();<br \/>\n}<br \/>\nScript.complete();<br \/>\nreturn;<br \/>\n}<br \/>\nconst widget = new ListWidget();<br \/>\nconst cookie = getCookie();<br \/>\nif (!cookie) {<br \/>\nshowWidgetErrorAndFinish(widget, &#8220;\u8bf7\u5148\u8bbe\u7f6e Cookie&#8221;);<br \/>\nreturn;<br \/>\n}<br \/>\nlet products = getCachedProducts();<br \/>\nif (!products) {<br \/>\nproducts = await loadProducts();<br \/>\n}<br \/>\nif (products.length === 0) {<br \/>\nshowWidgetErrorAndFinish(widget, &#8220;\u672a\u627e\u5230\u4ea7\u54c1\u6570\u636e&#8221;);<br \/>\nreturn;<br \/>\n}<br \/>\nconst product = pickProductWithFallback(products, productsIndex);<br \/>\nif (!product) {<br \/>\nshowWidgetErrorAndFinish(widget, &#8220;\u672a\u627e\u5230\u53ef\u7528\u4ea7\u54c1&#8221;);<br \/>\nreturn;<br \/>\n}<br \/>\nswitch (config.widgetFamily) {<br \/>\ncase &#8220;accessoryCircular&#8221;:<br \/>\nawait renderAccessoryCircular(widget, product);<br \/>\nbreak;<br \/>\ncase &#8220;accessoryInline&#8221;:<br \/>\nawait renderAccessoryInline(widget, product);<br \/>\nbreak;<br \/>\ncase &#8220;accessoryRectangular&#8221;:<br \/>\nawait renderAccessoryRectangular(widget, product);<br \/>\nbreak;<br \/>\ncase &#8220;small&#8221;:<br \/>\nawait renderSmall(widget, product);<br \/>\nbreak;<br \/>\ncase &#8220;medium&#8221;:<br \/>\nawait renderMedium(widget, product);<br \/>\nbreak;<br \/>\ncase &#8220;large&#8221;:<br \/>\ncase &#8220;extraLarge&#8221;:<br \/>\nawait renderLarge(widget, product);<br \/>\nbreak;<br \/>\ndefault:<br \/>\nawait renderMedium(widget, product);<br \/>\nbreak;<br \/>\n}<br \/>\nScript.complete();<br \/>\n}<br \/>\nvoid main();<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u65e9\u4e0a\u770b\u89c1\u5927\u4f6c\u5206\u4eab\u7684 scriptable dmit\u5c0f\u7ec4\u4ef6 https:\/\/www.nodeseek.com\/post-661149-1 \u4f18\u5316\u4e86\u4e00\u4e0b\uff0c\u591a\u52a0\u4e86\u4e00\u4e9b\u4fe1\u606f\u548c\u5c0f\u529f\u80fd\uff0c\u9700\u8981\u53ef\u81ea\u53d6\uff0c\u56e0\u4e3a\u6211\u53ea\u6709\u4e00\u53f0\u8bbe\u5907\uff0c\u5355\u8bbe\u5907\u4f7f\u7528\u662f\u6ca1\u95ee\u9898\u7684\uff0c\u591a\u8bbe\u5907\u4e0d\u4fdd\u8bc1\u54c8&#8230;<\/p>\n","protected":false},"author":1,"featured_media":658816,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[128],"tags":[1134,981,1135],"topic":[],"class_list":["post-658815","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-128","tag-const","tag-dmit","tag-scriptname"],"_links":{"self":[{"href":"http:\/\/puo.cn\/index.php?rest_route=\/wp\/v2\/posts\/658815","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/puo.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/puo.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/puo.cn\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/puo.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=658815"}],"version-history":[{"count":1,"href":"http:\/\/puo.cn\/index.php?rest_route=\/wp\/v2\/posts\/658815\/revisions"}],"predecessor-version":[{"id":658825,"href":"http:\/\/puo.cn\/index.php?rest_route=\/wp\/v2\/posts\/658815\/revisions\/658825"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/puo.cn\/index.php?rest_route=\/wp\/v2\/media\/658816"}],"wp:attachment":[{"href":"http:\/\/puo.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=658815"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/puo.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=658815"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/puo.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=658815"},{"taxonomy":"topic","embeddable":true,"href":"http:\/\/puo.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftopic&post=658815"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}