Browse Source

Clean up dirty worktree on Gondor

Breandan Dezendorf 13 giờ trước cách đây
mục cha
commit
21a97d3be4

+ 2 - 1
dezendorf/applications/golink/cmd/main/main.go

@@ -8,7 +8,7 @@ import (
 
 	"github.com/julienschmidt/httprouter"
 	core "gogs.dezendorf.net/breandan/monorepo/dezendorf/applications/golink/internal/core"
-	db "gogs.dezendorf.net/breandan/monorepo/dezendorf/applications/golink/internal/database"
+	// db "gogs.dezendorf.net/breandan/monorepo/dezendorf/applications/golink/internal/database"
 	routes "gogs.dezendorf.net/breandan/monorepo/dezendorf/applications/golink/internal/routes"
 )
 
@@ -21,6 +21,7 @@ func init() {
 
 func main() {
 	flag.Parse()
+	// call from core 
 	core.DepImport()
 	if *server {
 		fmt.Printf("Starting linkserver on port :%s", *serverPort)

+ 1 - 0
dezendorf/applications/offset-fixer/BUILD

@@ -5,3 +5,4 @@ py_binary(
   srcs = ["offset-fixer.py"],
   visibility = ["//visibility:public"],
 )
+

+ 12 - 0
dezendorf/applications/powerctl/main.go

@@ -0,0 +1,12 @@
+package main
+
+import (
+	"fmt"
+	"log"
+	g "github.com/gosnmp/gosnmp"
+)
+
+
+func main(){
+  println("main.go is here to go!")
+}

+ 186 - 0
dezendorf/applications/powerctl/python/1

@@ -0,0 +1,186 @@
+from flask import Flask, request, jsonify, render_template_string
+from pysnmp.hlapi import *
+
+app = Flask(__name__)
+
+def snmp_get(ip, community, oid):
+    iterator = getCmd(
+        SnmpEngine(),
+        CommunityData(community, mpModel=1),  # mpModel=1 for SNMP v2c
+        UdpTransportTarget((ip, 161)),
+        ContextData(),
+        ObjectType(ObjectIdentity(oid))
+    )
+
+    errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
+
+    if errorIndication:
+        return {"error": str(errorIndication)}, 500
+    elif errorStatus:
+        return {"error": str(errorStatus.prettyPrint())}, 500
+    else:
+        for varBind in varBinds:
+            return {"value": varBind.prettyPrint().split("=")[-1].strip()}
+
+def snmp_set(ip, community, oid, value, value_type):
+    type_map = {
+        'Integer': Integer,
+        'OctetString': OctetString,
+        'IpAddress': IpAddress,
+        'Counter32': Counter32,
+        'Gauge32': Gauge32,
+        'TimeTicks': TimeTicks
+    }
+    
+    if value_type not in type_map:
+        return {"error": f"Unsupported value type: {value_type}"}, 400
+
+    data_type = type_map[value_type]
+    
+    iterator = setCmd(
+        SnmpEngine(),
+        CommunityData(community, mpModel=1),
+        UdpTransportTarget((ip, 161)),
+        ContextData(),
+        ObjectType(ObjectIdentity(oid), data_type(value))
+    )
+
+    errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
+
+    if errorIndication:
+        return {"error": str(errorIndication)}, 500
+    elif errorStatus:
+        return {"error": str(errorStatus.prettyPrint())}, 500
+    else:
+        return {"status": "Set request successful"}
+
+def snmp_get_table(ip, community, base_oid):
+    table = []
+    iterator = nextCmd(
+        SnmpEngine(),
+        CommunityData(community, mpModel=1),
+        UdpTransportTarget((ip, 161)),
+        ContextData(),
+        ObjectType(ObjectIdentity(base_oid)),
+        lexicographicMode=False
+    )
+
+    for errorIndication, errorStatus, errorIndex, varBinds in iterator:
+        if errorIndication:
+            return {"error": str(errorIndication)}, 500
+        elif errorStatus:
+            return {"error": str(errorStatus.prettyPrint())}, 500
+        else:
+            row = {str(varBind[0]): varBind[1].prettyPrint() for varBind in varBinds}
+            table.append(row)
+
+    return table
+
+@app.route('/')
+def index():
+    # Main HTML page with a simple web UI
+    return render_template_string("""
+        <!DOCTYPE html>
+        <html lang="en">
+        <head>
+            <meta charset="UTF-8">
+            <title>SNMP Web Interface</title>
+            <style>
+                body { font-family: Arial, sans-serif; margin: 20px; }
+                input, select, button { margin: 5px; padding: 5px; }
+                .result { margin-top: 15px; padding: 10px; background: #f9f9f9; border: 1px solid #ddd; }
+                table { width: 100%; border-collapse: collapse; margin-top: 20px; }
+                table, th, td { border: 1px solid #ddd; padding: 8px; }
+                th { background-color: #f2f2f2; }
+            </style>
+        </head>
+        <body>
+            <h1>SNMP Web Interface</h1>
+            
+            <!-- SNMP TABLE Form -->
+            <h2>SNMP TABLE</h2>
+            <input type="text" id="table_ip" placeholder="apc-pdu-01.dezendorf.net">
+            <input type="text" id="table_community" placeholder="Community" value="t33cHm">
+            <input type="text" id="table_oid" placeholder="Base OID">
+            <button onclick="performTable()">Get Table</button>
+            <div id="table_result" class="result"></div>
+
+            <script>
+                function performTable() {
+                    const ip = document.getElementById('table_ip').value;
+                    const community = document.getElementById('table_community').value;
+                    const oid = document.getElementById('table_oid').value;
+                    
+                    fetch(`/snmp/table?ip=${ip}&community=${community}&base_oid=${oid}`)
+                        .then(response => response.json())
+                        .then(data => {
+                            if (data.error) {
+                                document.getElementById('table_result').innerHTML = `<div>${data.error}</div>`;
+                            } else {
+                                let tableHtml = '<table><thead><tr><th>OID</th><th>Value</th></tr></thead><tbody>';
+                                data.forEach(row => {
+                                    Object.entries(row).forEach(([oid, value]) => {
+                                        tableHtml += `<tr><td>${oid}</td><td contenteditable="true" onblur="updateCell('${ip}', '${community}', '${oid}', this.innerText)">${value}</td></tr>`;
+                                    });
+                                });
+                                tableHtml += '</tbody></table>';
+                                document.getElementById('table_result').innerHTML = tableHtml;
+                            }
+                        });
+                }
+
+                function updateCell(ip, community, oid, value) {
+                    fetch(`/snmp/set`, {
+                        method: 'POST',
+                        headers: { 'Content-Type': 'application/json' },
+                        body: JSON.stringify({ ip, community, oid, value, value_type: "OctetString" })
+                    })
+                    .then(response => response.json())
+                    .then(data => {
+                        if (data.error) {
+                            alert("Failed to update: " + data.error);
+                        } else {
+                            alert("Update successful!");
+                        }
+                    });
+                }
+            </script>
+        </body>
+        </html>
+    """)
+
+@app.route('/snmp/table', methods=['GET'])
+def api_snmp_get_table():
+    ip = request.args.get('ip')
+    community = request.args.get('community', 'public')
+    base_oid = request.args.get('base_oid')
+    
+    if not ip or not base_oid:
+        return {"error": "Missing required parameters: ip, base_oid"}, 400
+
+    result = snmp_get_table(ip, community, base_oid)
+    return jsonify(result)
+
+@app.route('/snmp/set', methods=['POST'])
+def api_snmp_set():
+    data = request.json
+    ip = data.get('ip')
+    community = data.get('community', 'public')
+    oid = data.get('oid')
+    value = data.get('value')
+    value_type = data.get('value_type', 'OctetString')
+    
+    if not ip or not oid or value is None:
+        return {"error": "Missing required parameters: ip, oid, value"}, 400
+
+    result = snmp_set(ip, community, oid, value, value_type)
+    return jsonify(result)
+
+if __name__ == '__main__':
+#    app.run(host='0.0.0.0', port=5032
+    table = snmp_get_table("apc-pdu-01.dezendorf.net", "t33cHm3", "1.3.6.1.4.1.318.1.1.4.4.2.1")
+    print(table)
+    for i in table:
+        print(i)
+
+

+ 181 - 0
dezendorf/applications/powerctl/python/pow.py

@@ -0,0 +1,181 @@
+from flask import Flask, request, jsonify, render_template_string
+from pysnmp.hlapi import *
+
+app = Flask(__name__)
+
+def snmp_get(ip, community, oid):
+    iterator = getCmd(
+        SnmpEngine(),
+        CommunityData(community, mpModel=1),  # mpModel=1 for SNMP v2c
+        UdpTransportTarget((ip, 161)),
+        ContextData(),
+        ObjectType(ObjectIdentity(oid))
+    )
+
+    errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
+
+    if errorIndication:
+        return {"error": str(errorIndication)}, 500
+    elif errorStatus:
+        return {"error": str(errorStatus.prettyPrint())}, 500
+    else:
+        for varBind in varBinds:
+            return {"value": varBind.prettyPrint().split("=")[-1].strip()}
+
+def snmp_set(ip, community, oid, value, value_type):
+    type_map = {
+        'Integer': Integer,
+        'OctetString': OctetString,
+        'IpAddress': IpAddress,
+        'Counter32': Counter32,
+        'Gauge32': Gauge32,
+        'TimeTicks': TimeTicks
+    }
+    
+    if value_type not in type_map:
+        return {"error": f"Unsupported value type: {value_type}"}, 400
+
+    data_type = type_map[value_type]
+    
+    iterator = setCmd(
+        SnmpEngine(),
+        CommunityData(community, mpModel=1),
+        UdpTransportTarget((ip, 161)),
+        ContextData(),
+        ObjectType(ObjectIdentity(oid), data_type(value))
+    )
+
+    errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
+
+    if errorIndication:
+        return {"error": str(errorIndication)}, 500
+    elif errorStatus:
+        return {"error": str(errorStatus.prettyPrint())}, 500
+    else:
+        return {"status": "Set request successful"}
+
+def snmp_get_table(ip, community, base_oid):
+    table = []
+    iterator = nextCmd(
+        SnmpEngine(),
+        CommunityData(community, mpModel=1),
+        UdpTransportTarget((ip, 161)),
+        ContextData(),
+        ObjectType(ObjectIdentity(base_oid)),
+        lexicographicMode=False
+    )
+
+    for errorIndication, errorStatus, errorIndex, varBinds in iterator:
+        if errorIndication:
+            return {"error": str(errorIndication)}, 500
+        elif errorStatus:
+            return {"error": str(errorStatus.prettyPrint())}, 500
+        else:
+            row = {str(str(varBind[0]).split('.')[-2], str(varBind[0]).split('.')[-1]): varBind[1].prettyPrint() for varBind in varBinds}
+            table.append(row)
+
+    return table
+
+@app.route('/')
+def index():
+    # Main HTML page with a simple web UI
+    return render_template_string("""
+        <!DOCTYPE html>
+        <html lang="en">
+        <head>
+            <meta charset="UTF-8">
+            <title>SNMP Web Interface</title>
+            <style>
+                body { font-family: Arial, sans-serif; margin: 20px; }
+                input, select, button { margin: 5px; padding: 5px; }
+                .result { margin-top: 15px; padding: 10px; background: #f9f9f9; border: 1px solid #ddd; }
+                table { width: 100%; border-collapse: collapse; margin-top: 20px; }
+                table, th, td { border: 1px solid #ddd; padding: 8px; }
+                th { background-color: #f2f2f2; }
+            </style>
+        </head>
+        <body>
+            <h1>SNMP Web Interface</h1>
+            
+            <!-- SNMP TABLE Form -->
+            <h2>SNMP TABLE</h2>
+            <input type="text" id="table_ip" placeholder="apc-pdu-01.dezendorf.net" value="apc-pdu-01.dezendorf.net">
+            <input type="text" id="table_community" placeholder="Community" value="t33cHm">
+            <input type="text" id="table_oid" placeholder="1.3.6.1.4.1.318.1.1.4.4.2.1" value="1.3.6.1.4.1.318.1.1.4.4.2.1">
+            <button onclick="performTable()">Get Table</button>
+            <div id="table_result" class="result"></div>
+
+            <script>
+                function performTable() {
+                    const ip = document.getElementById('table_ip').value;
+                    const community = document.getElementById('table_community').value;
+                    const oid = document.getElementById('table_oid').value;
+                    
+                    fetch(`/snmp/table?ip=${ip}&community=${community}&base_oid=${oid}`)
+                        .then(response => response.json())
+                        .then(data => {
+                            if (data.error) {
+                                document.getElementById('table_result').innerHTML = `<div>${data.error}</div>`;
+                            } else {
+                                let tableHtml = '<table><thead><tr><th>OID</th><th>Value</th></tr></thead><tbody>';
+                                data.forEach(row => {
+                                    Object.entries(row).forEach(([oid, value]) => {
+                                        tableHtml += `<tr><td>${oid}</td><td contenteditable="true" onblur="updateCell('${ip}', '${community}', '${oid}', this.innerText)">${value}</td></tr>`;
+                                    });
+                                });
+                                tableHtml += '</tbody></table>';
+                                document.getElementById('table_result').innerHTML = tableHtml;
+                            }
+                        });
+                }
+
+                function updateCell(ip, community, oid, value) {
+                    fetch(`/snmp/set`, {
+                        method: 'POST',
+                        headers: { 'Content-Type': 'application/json' },
+                        body: JSON.stringify({ ip, community, oid, value, value_type: "OctetString" })
+                    })
+                    .then(response => response.json())
+                    .then(data => {
+                        if (data.error) {
+                            alert("Failed to update: " + data.error);
+                        } else {
+                            alert("Update successful!");
+                        }
+                    });
+                }
+            </script>
+        </body>
+        </html>
+    """)
+
+@app.route('/snmp/table', methods=['GET'])
+def api_snmp_get_table():
+    ip = request.args.get('ip')
+    community = request.args.get('community', 'public')
+    base_oid = request.args.get('base_oid')
+    
+    if not ip or not base_oid:
+        return {"error": "Missing required parameters: ip, base_oid"}, 400
+
+    result = snmp_get_table(ip, community, base_oid)
+    return jsonify(result)
+
+@app.route('/snmp/set', methods=['POST'])
+def api_snmp_set():
+    data = request.json
+    ip = data.get('ip')
+    community = data.get('community', 'public')
+    oid = data.get('oid')
+    value = data.get('value')
+    value_type = data.get('value_type', 'OctetString')
+    
+    if not ip or not oid or value is None:
+        return {"error": "Missing required parameters: ip, oid, value"}, 400
+
+    result = snmp_set(ip, community, oid, value, value_type)
+    return jsonify(result)
+
+if __name__ == '__main__':
+    app.run(host='0.0.0.0', port=5032
+)

+ 214 - 0
dezendorf/applications/powerctl/python/pow1.py

@@ -0,0 +1,214 @@
+from flask import Flask, request, jsonify, render_template_string
+from pysnmp.hlapi import *
+
+app = Flask(__name__)
+
+def snmp_get(ip, community, oid):
+    iterator = getCmd(
+        SnmpEngine(),
+        CommunityData(community, mpModel=1),  # mpModel=1 for SNMP v2c
+        UdpTransportTarget((ip, 161)),
+        ContextData(),
+        ObjectType(ObjectIdentity(oid))
+    )
+
+    errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
+
+    if errorIndication:
+        return {"error": str(errorIndication)}, 500
+    elif errorStatus:
+        return {"error": str(errorStatus.prettyPrint())}, 500
+    else:
+        for varBind in varBinds:
+            return {"value": varBind.prettyPrint().split("=")[-1].strip()}
+
+def snmp_set(ip, community, oid, value, value_type):
+    type_map = {
+        'Integer': Integer,
+        'OctetString': OctetString,
+        'IpAddress': IpAddress,
+        'Counter32': Counter32,
+        'Gauge32': Gauge32,
+        'TimeTicks': TimeTicks
+    }
+    
+    if value_type not in type_map:
+        return {"error": f"Unsupported value type: {value_type}"}, 400
+
+    data_type = type_map[value_type]
+    
+    iterator = setCmd(
+        SnmpEngine(),
+        CommunityData(community, mpModel=1),
+        UdpTransportTarget((ip, 161)),
+        ContextData(),
+        ObjectType(ObjectIdentity(oid), data_type(value))
+    )
+
+    errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
+
+    if errorIndication:
+        return {"error": str(errorIndication)}, 500
+    elif errorStatus:
+        return {"error": str(errorStatus.prettyPrint())}, 500
+    else:
+        return {"status": "Set request successful"}
+
+def snmp_get_table(ip, community, base_oid):
+    table = []
+    iterator = nextCmd(
+        SnmpEngine(),
+        CommunityData(community, mpModel=1),
+        UdpTransportTarget((ip, 161)),
+        ContextData(),
+        ObjectType(ObjectIdentity(base_oid)),
+        lexicographicMode=False
+    )
+
+    for errorIndication, errorStatus, errorIndex, varBinds in iterator:
+        if errorIndication:
+            return {"error": str(errorIndication)}, 500
+        elif errorStatus:
+            return {"error": str(errorStatus.prettyPrint())}, 500
+        else:
+            row = {str(varBind[0]): varBind[1].prettyPrint() for varBind in varBinds}
+            table.append(row)
+
+    return table
+
+@app.route('/')
+def index():
+    # The main HTML page with a simple web UI
+    return render_template_string("""
+        <!DOCTYPE html>
+        <html lang="en">
+        <head>
+            <meta charset="UTF-8">
+            <title>SNMP Web Interface</title>
+            <style>
+                body { font-family: Arial, sans-serif; margin: 20px; }
+                input, select, button { margin: 5px; padding: 5px; }
+                .result { margin-top: 15px; padding: 10px; background: #f9f9f9; border: 1px solid #ddd; }
+            </style>
+        </head>
+        <body>
+            <h1>SNMP Web Interface</h1>
+            
+            <!-- SNMP GET Form -->
+            <h2>SNMP GET</h2>
+            <input type="text" id="get_ip" placeholder="IP Address">
+            <input type="text" id="get_community" placeholder="Community" value="public">
+            <input type="text" id="get_oid" placeholder="OID">
+            <button onclick="performGet()">Get</button>
+            <div id="get_result" class="result"></div>
+            
+            <!-- SNMP SET Form -->
+            <h2>SNMP SET</h2>
+            <input type="text" id="set_ip" placeholder="IP Address">
+            <input type="text" id="set_community" placeholder="Community" value="public">
+            <input type="text" id="set_oid" placeholder="OID">
+            <input type="text" id="set_value" placeholder="Value">
+            <select id="set_type">
+                <option value="OctetString">OctetString</option>
+                <option value="Integer">Integer</option>
+                <option value="IpAddress">IpAddress</option>
+            </select>
+            <button onclick="performSet()">Set</button>
+            <div id="set_result" class="result"></div>
+            
+            <!-- SNMP TABLE Form -->
+            <h2>SNMP TABLE</h2>
+            <input type="text" id="table_ip" placeholder="IP Address">
+            <input type="text" id="table_community" placeholder="Community" value="public">
+            <input type="text" id="table_oid" placeholder="Base OID">
+            <button onclick="performTable()">Get Table</button>
+            <div id="table_result" class="result"></div>
+
+            <script>
+                function performGet() {
+                    const ip = document.getElementById('get_ip').value;
+                    const community = document.getElementById('get_community').value;
+                    const oid = document.getElementById('get_oid').value;
+                    
+                    fetch(`/snmp/get?ip=${ip}&community=${community}&oid=${oid}`)
+                        .then(response => response.json())
+                        .then(data => {
+                            document.getElementById('get_result').textContent = JSON.stringify(data, null, 2);
+                        });
+                }
+
+                function performSet() {
+                    const ip = document.getElementById('set_ip').value;
+                    const community = document.getElementById('set_community').value;
+                    const oid = document.getElementById('set_oid').value;
+                    const value = document.getElementById('set_value').value;
+                    const type = document.getElementById('set_type').value;
+                    
+                    fetch(`/snmp/set`, {
+                        method: 'POST',
+                        headers: { 'Content-Type': 'application/json' },
+                        body: JSON.stringify({ ip, community, oid, value, value_type: type })
+                    })
+                    .then(response => response.json())
+                    .then(data => {
+                        document.getElementById('set_result').textContent = JSON.stringify(data, null, 2);
+                    });
+                }
+
+                function performTable() {
+                    const ip = document.getElementById('table_ip').value;
+                    const community = document.getElementById('table_community').value;
+                    const oid = document.getElementById('table_oid').value;
+                    
+                    fetch(`/snmp/table?ip=${ip}&community=${community}&base_oid=${oid}`)
+                        .then(response => response.json())
+                        .then(data => {
+                            document.getElementById('table_result').textContent = JSON.stringify(data, null, 2);
+                        });
+                }
+            </script>
+        </body>
+        </html>
+    """)
+
+@app.route('/snmp/get', methods=['GET'])
+def api_snmp_get():
+    ip = request.args.get('ip')
+    community = request.args.get('community', 'public')
+    oid = request.args.get('oid')
+    
+    if not ip or not oid:
+        return {"error": "Missing required parameters: ip, oid"}, 400
+    
+    result = snmp_get(ip, community, oid)
+    return jsonify(result)
+
+@app.route('/snmp/set', methods=['POST'])
+def api_snmp_set():
+    data = request.json
+    ip = data.get('ip')
+    community = data.get('community', 'public')
+    oid = data.get('oid')
+    value = data.get('value')
+    value_type = data.get('value_type', 'OctetString')
+    
+    if not ip or not oid or value is None:
+        return {"error": "Missing required parameters: ip, oid, value"}, 400
+
+    result = snmp_set(ip, community, oid, value, value_type)
+    return jsonify(result)
+
+@app.route('/snmp/table', methods=['GET'])
+def api_snmp_get_table():
+    ip = request.args.get('ip')
+    community = request.args.get('community', 'public')
+    base_oid = request.args.get('base_oid')
+    
+    if not ip or not base_oid:
+        return {"error": "Missing required parameters: ip, base_oid"}, 400
+
+    result = snmp_get_table(ip, community, base_oid)
+    return jsonify(result)
+
+if __name__ == '__main__':
+    app.run(host='0.0.0.0', port=5032)

+ 0 - 0
dezendorf/applications/powerctl/powerctl → dezendorf/applications/powerctl/python/powerctl


+ 234 - 0
dezendorf/applications/powerctl/python/snmp_pdu.py

@@ -0,0 +1,234 @@
+from flask import Flask, request, jsonify, render_template_string
+from pysnmp.hlapi import *
+
+app = Flask(__name__)
+
+def snmp_get(ip, community, oid):
+    iterator = getCmd(
+        SnmpEngine(),
+        CommunityData(community, mpModel=1),  # mpModel=1 for SNMP v2c
+        UdpTransportTarget((ip, 161)),
+        ContextData(),
+        ObjectType(ObjectIdentity(oid))
+    )
+
+    errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
+
+    if errorIndication:
+        return {"error": str(errorIndication)}, 500
+    elif errorStatus:
+        return {"error": str(errorStatus.prettyPrint())}, 500
+    else:
+        for varBind in varBinds:
+            return {"value": varBind.prettyPrint().split("=")[-1].strip()}
+
+def snmp_set(ip, community, oid, value, value_type):
+    type_map = {
+        'Integer': Integer,
+        'OctetString': OctetString,
+        'IpAddress': IpAddress,
+        'Counter32': Counter32,
+        'Gauge32': Gauge32,
+        'String': String,
+        'TimeTicks': TimeTicks
+    }
+    
+    if value_type not in type_map:
+        return {"error": f"Unsupported value type: {value_type}"}, 400
+
+    data_type = type_map[value_type]
+    
+    iterator = setCmd(
+        SnmpEngine(),
+        CommunityData(community, mpModel=1),
+        UdpTransportTarget((ip, 161)),
+        ContextData(),
+        ObjectType(ObjectIdentity(oid), data_type(value))
+    )
+
+    errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
+
+    if errorIndication:
+        return {"error": str(errorIndication)}, 500
+    elif errorStatus:
+        return {"error": str(errorStatus.prettyPrint())}, 500
+    else:
+        return {"status": "Set request successful"}
+
+def snmp_get_table(ip, community, base_oid):
+    table = []
+    tableBetter = {}
+    iterator = nextCmd(
+        SnmpEngine(),
+        CommunityData(community, mpModel=1),
+        UdpTransportTarget((ip, 161)),
+        ContextData(),
+        ObjectType(ObjectIdentity(base_oid)),
+        lexicographicMode=False
+    )
+
+    for errorIndication, errorStatus, errorIndex, varBinds in iterator:
+        if errorIndication:
+            return {"error": str(errorIndication)}, 500
+        elif errorStatus:
+            return {"error": str(errorStatus.prettyPrint())}, 500
+        else:
+            for varBind in varBinds:
+                tableBetter[str(varBind[0])] = str(varBind[1].prettyPrint())
+
+    return tableBetter
+
+@app.route('/')
+def index():
+    # Main HTML page with a simple web UI
+    return render_template_string("""
+        <!DOCTYPE html>
+        <html lang="en">
+        <head>
+            <meta charset="UTF-8">
+            <title>SNMP Web Interface</title>
+            <style>
+                body { font-family: Arial, sans-serif; margin: 20px; }
+                input, select, button { margin: 5px; padding: 5px; }
+                .result { margin-top: 15px; padding: 10px; background: #f9f9f9; border: 1px solid #ddd; }
+                table { width: 100%; border-collapse: collapse; margin-top: 20px; }
+                table, th, td { border: 1px solid #ddd; padding: 8px; }
+                th { background-color: #f2f2f2; }
+            </style>
+        </head>
+        <body>
+            <h1>SNMP Web Interface</h1>
+            
+            <!-- SNMP TABLE Form -->
+            <h2>SNMP TABLE</h2>
+            <input type="text" id="table_ip" placeholder="apc-pdu-01.dezendorf.net">
+            <input type="text" id="table_community" placeholder="Community" value="t33cHm">
+            <input type="text" id="table_oid" placeholder="Base OID">
+            <button onclick="performTable()">Get Table</button>
+            <div id="table_result" class="result"></div>
+
+            <script>
+                function performTable() {
+                    const ip = document.getElementById('table_ip').value;
+                    const community = document.getElementById('table_community').value;
+                    const oid = document.getElementById('table_oid').value;
+                    
+                    fetch(`/snmp/table?ip=${ip}&community=${community}&base_oid=${oid}`)
+                        .then(response => response.json())
+                        .then(data => {
+                            if (data.error) {
+                                document.getElementById('table_result').innerHTML = `<div>${data.error}</div>`;
+                            } else {
+                                let tableHtml = '<table><thead><tr><th>OID</th><th>Value</th></tr></thead><tbody>';
+                                data.forEach(row => {
+                                    Object.entries(row).forEach(([oid, value]) => {
+                                        tableHtml += `<tr><td>${oid}</td><td contenteditable="true" onblur="updateCell('${ip}', '${community}', '${oid}', this.innerText)">${value}</td></tr>`;
+                                    });
+                                });
+                                tableHtml += '</tbody></table>';
+                                document.getElementById('table_result').innerHTML = tableHtml;
+                            }
+                        });
+                }
+
+                function updateCell(ip, community, oid, value) {
+                    fetch(`/snmp/set`, {
+                        method: 'POST',
+                        headers: { 'Content-Type': 'application/json' },
+                        body: JSON.stringify({ ip, community, oid, value, value_type: "OctetString" })
+                    })
+                    .then(response => response.json())
+                    .then(data => {
+                        if (data.error) {
+                            alert("Failed to update: " + data.error);
+                        } else {
+                            alert("Update successful!");
+                        }
+                    });
+                }
+            </script>
+        </body>
+        </html>
+    """)
+
+@app.route('/snmp/table', methods=['GET'])
+def api_snmp_get_table():
+    ip = request.args.get('ip')
+    community = request.args.get('community', 'public')
+    base_oid = request.args.get('base_oid')
+    
+    if not ip or not base_oid:
+        return {"error": "Missing required parameters: ip, base_oid"}, 400
+
+    result = snmp_get_table(ip, community, base_oid)
+    return jsonify(result)
+
+@app.route('/snmp/set', methods=['POST'])
+def api_snmp_set():
+    data = request.json
+    ip = data.get('ip')
+    community = data.get('community', 'public')
+    oid = data.get('oid')
+    value = data.get('value')
+    value_type = data.get('value_type', 'OctetString')
+    
+    if not ip or not oid or value is None:
+        return {"error": "Missing required parameters: ip, oid, value"}, 400
+
+    result = snmp_set(ip, community, oid, value, value_type)
+    return jsonify(result)
+
+class PDU:
+    def __init__(self, hostName, portNumber, community):
+        self.hostName = hostName
+        self.community = "t33cHm3"
+        self.port = 161
+
+    def getHost(self):
+        return self.hostName
+
+    def setHost(self, hostname):
+        self.hostName = hostName
+        return self.hostName
+
+
+class PDUPort:
+    def __init__(self, pdu, portNumber, portName, powerState):
+
+    def setPortName(self, portName):
+        self.portName = portName
+        try:
+            snmp_set(self, self.community, <oid>, self.name, 'string')
+        else:
+            return False
+        return True
+
+    def setPDU(self, pdu):
+        self.pdu = pdu
+        return pdu.hostname
+
+    def setPowerState(self, powerState):
+        self.powerState = powerState
+        return self.powerState
+
+    def set
+
+    def getPortName(self):
+        return self.portName
+
+    def getPortNumber(self):
+        return self.portNumber
+
+    def getPowerState(self):
+        return self.powerState
+
+
+
+if __name__ == '__main__':
+#    app.run(host='0.0.0.0', port=5032
+    table = snmp_get_table("apc-pdu-01.dezendorf.net", "t33cHm3", "1.3.6.1.4.1.318.1.1.4.4.2.1")
+    for k in table:
+        k1 = k.replace("1.3.6.1.4.1.318.1.1.4.4.2.1.", "")
+        print(f"{k1}: {table[k]}")
+
+

+ 16 - 0
dezendorf/applications/subtitles/BUILD

@@ -0,0 +1,16 @@
+load("@rules_python//python:defs.bzl", "py_library", "py_binary")
+
+py_binary(
+  name = "subtitles",
+  srcs = ["subtitles.py"],
+  deps = [
+    "@pypi//flask",
+    "@pypi//FlaskFlask-SQLAlchemy",
+    "@pypi//flask-Marshmallow",
+    "@pypi//Flask-Migrate",
+    "@pypi//mysqlclient",
+    "@pypi//marshmallow-sqlalchemy",
+    "@pypi//watchdog",
+  ],
+  visibility = ["//visibility:public"],
+)

+ 3 - 3
dezendorf/applications/subtitles/requirements.txt

@@ -1,7 +1,7 @@
-
+flask
 Flask-SQLAlchemy
 flask-Marshmallow
 Flask-Migrate
 mysqlclient
-marshmallow-sqlalchemy
-watchdog
+#marshmallow-sqlalchemy
+watchdog

+ 7 - 0
dezendorf/applications/subtitles/requirements_lock.txt

@@ -0,0 +1,7 @@
+flask
+Flask-SQLAlchemy
+flask-Marshmallow
+Flask-Migrate
+mysqlclient
+marshmallow-sqlalchemy
+watchdog

+ 0 - 0
dezendorf/applications/temp-set/BUILD


+ 204 - 0
dezendorf/applications/temp-set/Creating a complete solution that satisf

@@ -0,0 +1,204 @@
+Creating a complete solution that satisfies your requirements involves several steps, including setting up the SNMP communication, creating RESTful endpoints, managing cache, implementing persistence, and integrating with HomeKit. Below is an outline of how you could accomplish this using Go. Due to the complexity, this will be a high-level guide with code snippets for key parts.
+
+Let's break this task into manageable pieces:
+
+### 1. Set Up Go Modules and Dependencies
+
+First, initialize a new Go module and install the required packages.
+```bash
+go mod init apc-pdu-server
+go get github.com/gosnmp/gosnmp
+go get github.com/gorilla/mux
+go get github.com/dustin/go-json
+```
+
+### 2. Define a Basic Structure
+
+Create a basic structure to manage PDUs and ports:
+```go
+package main
+
+import (
+    "fmt"
+    "net/http"
+    "github.com/gorilla/mux"
+    "github.com/gosnmp/gosnmp"
+    "sync"
+)
+
+type PDU struct {
+    IP   string
+    Community string
+}
+
+type PortStatus struct {
+    Name   string
+    Status bool
+}
+
+var (
+    pduList = map[string]*PDU{
+        // "PDU ID": PDU struct
+    }
+    portCache = make(map[string]*PortStatus)
+    cacheLock = sync.RWMutex{}
+)
+```
+
+### 3. Create SNMP Functions
+
+Implement functions for getting and setting port names and statuses using SNMP:
+```go
+func getPortName(pdu *PDU, port int) (string, error) {
+    // Implement SNMP get for port name
+    target := pdu.IP
+    g := gosnmp.NewGoSNMP(target, pdu.Community, gosnmp.Version2c, 5)
+    err := g.Connect()
+    if err != nil {
+        return "", err
+    }
+    defer g.Conn.Close()
+
+    // Example OID for port name
+    result, err := g.Get([]string{fmt.Sprintf("1.3.6.1.2.1.31.1.1.1.1.%d", port)})
+    if err != nil {
+        return "", err
+    }
+    if len(result.Variables) > 0 {
+        return string(result.Variables[0].Value.([]byte)), nil
+    }
+    return "", nil
+}
+
+func setPortName(pdu *PDU, port int, name string) error {
+    // Implement SNMP set for port name
+    return nil
+}
+
+func getPowerStatus(pdu *PDU, port int) (bool, error) {
+    // Implement SNMP get for power status
+    return false, nil
+}
+
+func setPowerStatus(pdu *PDU, port int, status bool) error {
+    // Implement SNMP set for power status
+    return nil
+}
+```
+
+### 4. Implement REST API
+
+Set up the router with HTTP handlers:
+```go
+func getPortNameHandler(w http.ResponseWriter, r *http.Request) {
+    vars := mux.Vars(r)
+    pduID := vars["pduId"]
+    port := vars["port"]
+
+    // Fetch from cache
+    cacheLock.RLock()
+    cachedStatus, exists := portCache[fmt.Sprintf("%s_%s", pduID, port)]
+    cacheLock.RUnlock()
+    if exists {
+        // Return cached value
+        w.Write([]byte(cachedStatus.Name))
+        return
+    }
+
+    // Query device
+    pdu, exists := pduList[pduID]
+    if !exists {
+        http.Error(w, "PDU not found", http.StatusNotFound)
+        return
+    }
+
+    name, err := getPortName(pdu, port)
+    if err != nil {
+        http.Error(w, "Error fetching port name", http.StatusInternalServerError)
+        return
+    }
+    
+    // Cache the result
+    cacheLock.Lock()
+    portCache[fmt.Sprintf("%s_%s", pduID, port)] = &PortStatus{Name: name, Status: false}
+    cacheLock.Unlock()
+
+    w.Write([]byte(name))
+}
+
+func main() {
+    r := mux.NewRouter()
+    r.HandleFunc("/api/pdu/{pduId}/port/{port}/name", getPortNameHandler).Methods("GET")
+
+    http.Handle("/", r)
+    http.ListenAndServe(":8080", nil)
+}
+```
+
+### 5. Cache Management and Persistence
+
+Implement methods for saving and loading cache from disk.
+```go
+import (
+    "encoding/json"
+    "io/ioutil"
+    "os"
+)
+
+// Save cache to file
+func saveCacheToDisk() error {
+    data, err := json.Marshal(portCache)
+    if err != nil {
+        return err
+    }
+    return ioutil.WriteFile("cache.json", data, 0644)
+}
+
+// Load cache from file
+func loadCacheFromDisk() error {
+    file, err := os.Open("cache.json")
+    if err != nil {
+        return err
+    }
+    defer file.Close()
+
+    byteValue, _ := ioutil.ReadAll(file)
+    return json.Unmarshal(byteValue, &portCache)
+}
+```
+
+### 6. Web Interface with Bootstrap.js
+
+This involves creating an HTML page with JavaScript that interfaces with your REST API. This includes using AJAX calls to your API and using HTML/CSS for a user-friendly UI.
+
+### 7. HomeKit Integration
+
+For HomeKit integration, you can use a third-party Go library like go-homekit or similar, but keep in mind the complexity and certifications required for full HomeKit compliance.
+### 8. Running and Testing
+
+Save the cache before shutdown and load it on startup.
+```go
+func main() {
+    // Load cache at startup
+    err := loadCacheFromDisk()
+    if err != nil {
+        fmt.Println("Failed to load cache:", err)
+    }
+
+    r := mux.NewRouter()
+    r.HandleFunc("/api/pdu/{pduId}/port/{port}/name", getPortNameHandler).Methods("GET")
+
+    // Other endpoints...
+
+    http.Handle("/", r)
+    defer saveCacheToDisk()
+
+    http.ListenAndServe(":8080", nil)
+}
+```
+
+### Final Steps
+
+Ensure you handle errors properly and secure your API. Further, for any SNMP set operations, handle permissions and authentication correctly. Implement the remaining endpoints and complete the HTTP handlers similarly to getPortNameHandler.
+
+This outline serves as a solid foundation, but streaming data, real-time updates, and HomeKit might require deeper integrations which can be developed iteratively following this structure.

+ 25 - 0
dezendorf/applications/temp-set/Dockerfile

@@ -0,0 +1,25 @@
+# Use a lightweight Python base
+FROM python:3.11-slim
+
+# Set working directory
+WORKDIR /app
+
+# Install Flask
+RUN pip install --no-cache-dir flask
+
+# Copy the app into the container
+COPY app.py /app/app.py
+
+# Set environment variables
+# Default value file location inside container
+ENV VALUE_FILE=/data/pct
+ENV PORT=8080
+
+# Create the /data directory where the file will be mounted
+RUN mkdir -p /data
+
+# Expose port
+EXPOSE 8080
+
+# Run the server
+CMD ["python", "app.py"]

+ 11 - 0
dezendorf/applications/temp-set/Write a restful webserver in golang that

@@ -0,0 +1,11 @@
+Write a restful webserver in golang that can query multiple APC PDU's snmp interface to get and set various values.
+The program should be able to:
+  get the name of a port
+  set the name of a port
+  get the power status of the port
+  set the power status of the port
+  build a cache to reduce latency
+  persist the cache file to disk so on next startup these values exist
+  expose the API as a REST API
+  expose the API via a bootstrap.js web interface
+  expose the API via HomeKit

+ 163 - 0
dezendorf/applications/temp-set/app.py

@@ -0,0 +1,163 @@
+#!/usr/bin/env python3
+import os
+import tempfile
+import threading
+import time
+from flask import Flask, jsonify, request, abort, render_template_string
+
+app = Flask(__name__)
+
+VALUE_FILE = "/data/pct"
+_write_lock = threading.Lock()
+
+
+# --- Helpers ---
+def _clamp_to_range(val: int) -> int:
+    return max(0, min(99, int(val)))
+
+
+def _ensure_file_exists():
+    if not os.path.exists(VALUE_FILE):
+        _atomic_write("0")
+
+
+def _read_value() -> int:
+    with open(VALUE_FILE, "r", encoding="utf-8") as f:
+        raw = f.read().strip()
+    if raw == "":
+        return 0
+    return _clamp_to_range(int(raw))
+
+
+def _atomic_write(text: str):
+    dir_name = os.path.dirname(os.path.abspath(VALUE_FILE)) or "."
+    os.makedirs(dir_name, exist_ok=True)
+    fd, tmp_path = tempfile.mkstemp(prefix=".value-", dir=dir_name, text=True)
+    with open(VALUE_FILE, "w") as tmp:
+        tmp.write(text)
+        tmp.flush()
+        os.fsync(tmp.fileno())
+
+
+def _get_mtime() -> float:
+    try:
+        return os.path.getmtime(VALUE_FILE)
+    except FileNotFoundError:
+        return 0.0
+
+
+def _fmt_time(mtime: float) -> str:
+    if not mtime:
+        return "never"
+    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(mtime))
+
+
+# --- Routes ---
+@app.route("/")
+def index():
+    _ensure_file_exists()
+    val = _read_value()
+    mtime = _get_mtime()
+
+    html = """
+<!doctype html>
+<html>
+<head>
+  <meta charset="utf-8"/>
+  <title>Astoria Manual Control</title>
+  <style>
+    body { font-family: sans-serif; margin: 2em; }
+    .value { font-size: 64px; margin-bottom: 20px; }
+    button { font-size: 24px; margin: 6px; padding: 12px 20px; }
+  </style>
+</head>
+<body>
+  <h1>Astoria Manual Control</h1>
+  <div class="value" id="val">{{value}}</div>
+  <div id="mtime">Last updated: {{mtime}}</div>
+  <div>
+    <button onclick="setVal(0)">0</button>
+    <button onclick="setVal(7)">7</button>
+    <button onclick="setVal(33)">33</button>
+    <button onclick="setVal(99)">99</button>
+  </div>
+  <div>
+    <input type="number" id="num" min="0" max="99" value="{{value}}"/>
+    <button onclick="saveNum()">Save</button>
+  </div>
+
+<script>
+let lastMtime = {{mtime_num}};
+
+async function waitForChange() {
+  try {
+    const resp = await fetch("/wait?since=" + lastMtime);
+    if (!resp.ok) throw new Error("HTTP " + resp.status);
+    const data = await resp.json();
+    document.getElementById("val").textContent = data.value;
+    document.getElementById("mtime").textContent = "Last updated: " + data.mtime_human;
+    document.getElementById("num").value = data.value;
+    lastMtime = data.mtime;
+  } catch (e) {
+    console.error("Polling failed:", e);
+  }
+  waitForChange(); // restart long poll
+}
+
+async function setVal(v) {
+  await fetch("/set", {
+    method: "POST",
+    headers: { "Content-Type": "application/json" },
+    body: JSON.stringify({ value: v })
+  });
+}
+
+function saveNum() {
+  const v = parseInt(document.getElementById("num").value);
+  setVal(v);
+}
+
+waitForChange();
+</script>
+</body>
+</html>
+    """
+    return render_template_string(html, value=val, mtime=_fmt_time(mtime), mtime_num=mtime)
+
+
+@app.route("/set", methods=["POST"])
+def set_value():
+    if not request.is_json:
+        abort(400, "Expected JSON")
+    body = request.get_json(silent=True) or {}
+    if "value" not in body:
+        abort(400, "Missing value")
+    try:
+        new_val = _clamp_to_range(int(body["value"]))
+    except Exception:
+        abort(400, "Value must be int 0–99")
+    with _write_lock:
+        _atomic_write(str(new_val))
+    mtime = _get_mtime()
+    return jsonify(value=new_val, mtime=mtime, mtime_human=_fmt_time(mtime))
+
+
+@app.route("/wait")
+def wait_for_change():
+    """Long poll: wait until file changes after `since` timestamp."""
+    since = float(request.args.get("since", "0"))
+    timeout = 30  # max seconds to wait
+    start = time.time()
+    while time.time() - start < timeout:
+        cur_mtime = _get_mtime()
+        if cur_mtime > since:
+            val = _read_value()
+            return jsonify(value=val, mtime=cur_mtime, mtime_human=_fmt_time(cur_mtime))
+        time.sleep(0.2)  # avoid busy loop
+    # timeout, respond with no change
+    return jsonify(value=_read_value(), mtime=_get_mtime(), mtime_human=_fmt_time(_get_mtime()))
+
+
+if __name__ == "__main__":
+    _ensure_file_exists()
+    app.run(host="0.0.0.0", port=8080, debug=True)

+ 0 - 288
dezendorf/applications/temp-set/temp-set.py

@@ -1,288 +0,0 @@
-#!/usr/bin/env python3
-import os
-import tempfile
-import threading
-from flask import Flask, jsonify, request, abort, render_template_string
-
-app = Flask(__name__)
-
-# --- Configuration ---
-VALUE_FILE = os.environ.get("VALUE_FILE", "value.txt")
-POLL_INTERVAL_MS = int(os.environ.get("POLL_INTERVAL_MS", "20000"))  # client refresh rate
-_write_lock = threading.Lock()
-
-
-# --- Helpers ---
-def _clamp_to_range(val: int) -> int:
-    return max(0, min(99, int(val)))
-
-
-def _ensure_file_exists():
-    """Create the value file with default '0' if missing or invalid."""
-    if not os.path.exists(VALUE_FILE):
-        _atomic_write("0")
-        return
-    try:
-        _ = _read_value()
-    except Exception:
-        _atomic_write("0")
-
-
-def _read_value() -> int:
-    """Read the current numeric value from disk."""
-    with open(VALUE_FILE, "r", encoding="utf-8") as f:
-        raw = f.read().strip()
-    # Allow blank/new files to recover to 0
-    if raw == "":
-        return 0
-    n = int(raw)
-    return _clamp_to_range(n)
-
-
-def _atomic_write(text: str):
-    """Atomically write text to VALUE_FILE."""
-    dir_name = os.path.dirname(os.path.abspath(VALUE_FILE)) or "."
-    os.makedirs(dir_name, exist_ok=True)
-    fd, tmp_path = tempfile.mkstemp(prefix=".value-", dir=dir_name, text=True)
-    try:
-        with os.fdopen(fd, "w", encoding="utf-8") as tmp:
-            tmp.write(text)
-            tmp.flush()
-            os.fsync(tmp.fileno())
-        os.replace(tmp_path, VALUE_FILE)  # atomic on POSIX/Windows
-    finally:
-        # If replace succeeded, tmp_path no longer exists; ignore errors.
-        try:
-            if os.path.exists(tmp_path):
-                os.remove(tmp_path)
-        except Exception:
-            pass
-
-
-def _get_mtime() -> float:
-    try:
-        return os.path.getmtime(VALUE_FILE)
-    except FileNotFoundError:
-        return 0.0
-
-
-# --- Routes ---
-@app.route("/", methods=["GET"])
-def index():
-    _ensure_file_exists()
-    val = _read_value()
-    mtime = _get_mtime()
-
-    # Single-file template for convenience
-    html = """
-<!doctype html>
-<html lang="en">
-<head>
-  <meta charset="utf-8" />
-  <meta name="viewport" content="width=device-width,initial-scale=1" />
-  <title>Tiny Value Server</title>
-  <style>
-    :root { --accent: #2b7cff; --bg:#0b0d12; --card:#121722; --text:#e8eefc; --muted:#9db0d7; }
-    body { margin:0; font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; color:var(--text); background:linear-gradient(180deg, #0b0d12 0%, #0e1320 100%); }
-    .wrap { max-width: 880px; margin: 40px auto; padding: 24px; background: var(--card); border-radius: 16px; box-shadow: 0 10px 40px rgba(0,0,0,0.35); }
-    h1 { margin: 0 0 8px; font-size: 1.5rem; }
-    .sub { color: var(--muted); margin-bottom: 24px; }
-    .value-display { font-size: clamp(48px, 9vw, 96px); font-weight: 800; text-align:center; letter-spacing: 2px; margin: 14px 0 6px; }
-    .mtime { text-align:center; color: var(--muted); font-size: 0.9rem; margin-bottom: 18px; }
-    .buttons { display:grid; grid-template-columns: repeat(4, minmax(100px,1fr)); gap:12px; margin: 12px 0 22px;}
-    button.big {
-      font-size: 24px; font-weight: 700; padding: 18px 8px; border-radius: 12px; border:1px solid #2a3245; background:#172033; color:var(--text);
-      cursor:pointer; transition: transform 0.06s ease, background 0.2s ease, box-shadow 0.2s ease;
-      box-shadow: inset 0 0 0 1px #2a3245, 0 8px 20px rgba(0,0,0,0.35);
-    }
-    button.big:hover { transform: translateY(-1px); background:#1a2742; }
-    button.primary { background: linear-gradient(180deg, #2b7cff, #2a5bff); border:none; color:white; }
-    .control-row { display:flex; gap:12px; align-items:center; flex-wrap:wrap; }
-    input[type="number"] {
-      width: 100px; font-size: 18px; padding: 10px 12px; border-radius: 10px; border:1px solid #2a3245; background:#0d121c; color:var(--text);
-    }
-    input[type="range"] { width: min(100%, 520px); }
-    .save { padding: 12px 16px; border-radius: 10px; border:0; background: var(--accent); color:white; font-weight:700; cursor:pointer; }
-    .status { min-height: 1.4em; color: var(--muted); }
-    .footer { margin-top: 24px; color: var(--muted); font-size: 0.9rem; }
-    .grid { display:grid; grid-template-columns: 1fr; gap:18px; }
-    @media (min-width: 720px) {
-      .grid { grid-template-columns: 1.2fr 1fr; align-items: center; }
-    }
-  </style>
-</head>
-<body>
-  <div class="wrap">
-    <h1>Tiny Value Server</h1>
-    <div class="sub">Stores a single integer 0–99 in <code>{{value_file}}</code>. Updates live if the file changes.</div>
-
-    <div class="grid">
-      <div>
-        <div class="value-display" id="valueDisplay">{{value}}</div>
-        <div class="mtime" id="mtime">last updated: {{mtime_human}}</div>
-
-        <div class="buttons">
-          <button class="big" data-value="0">0</button>
-          <button class="big" data-value="7">7</button>
-          <button class="big" data-value="33">33</button>
-          <button class="big primary" data-value="99">99</button>
-        </div>
-      </div>
-
-      <div>
-        <label for="valueRange">Set a custom value (0–99)</label>
-        <div class="control-row">
-          <input id="valueNumber" type="number" inputmode="numeric" min="0" max="99" value="{{value}}">
-          <input id="valueRange" type="range" min="0" max="99" value="{{value}}">
-          <button id="saveBtn" class="save">Save</button>
-        </div>
-        <div id="status" class="status"></div>
-        <div class="footer">Auto-refresh every {{poll_ms}} ms.</div>
-      </div>
-    </div>
-  </div>
-
-<script>
-const POLL_MS = {{poll_ms}};
-const valueDisplay = document.getElementById("valueDisplay");
-const mtimeEl = document.getElementById("mtime");
-const statusEl = document.getElementById("status");
-const num = document.getElementById("valueNumber");
-const rng = document.getElementById("valueRange");
-const saveBtn = document.getElementById("saveBtn");
-
-function clamp(n) {
-  n = Number(n);
-  if (!Number.isFinite(n)) return 0;
-  return Math.max(0, Math.min(99, Math.trunc(n)));
-}
-
-function setStatus(msg, ok=true) {
-  statusEl.textContent = msg || "";
-  statusEl.style.color = ok ? "#9db0d7" : "#ff9aa2";
-}
-
-function syncInputs(v) {
-  num.value = v;
-  rng.value = v;
-}
-
-async function fetchValue() {
-  try {
-    const r = await fetch("/value", { cache: "no-store" });
-    if (!r.ok) throw new Error("HTTP " + r.status);
-    const data = await r.json();
-    valueDisplay.textContent = data.value;
-    mtimeEl.textContent = "last updated: " + data.mtime_human;
-    // Only pull slider if server changed
-    if (Number(num.value) !== data.value) {
-      syncInputs(data.value);
-    }
-  } catch (e) {
-    setStatus("Failed to refresh: " + e.message, false);
-  }
-}
-
-async function postValue(v) {
-  try {
-    const r = await fetch("/set", {
-      method: "POST",
-      headers: { "Content-Type": "application/json" },
-      body: JSON.stringify({ value: v })
-    });
-    if (!r.ok) {
-      const t = await r.text();
-      throw new Error("HTTP " + r.status + " " + t);
-    }
-    const data = await r.json();
-    setStatus("Saved.");
-    valueDisplay.textContent = data.value;
-    mtimeEl.textContent = "last updated: " + data.mtime_human;
-    syncInputs(data.value);
-  } catch (e) {
-    setStatus("Save failed: " + e.message, false);
-  }
-}
-
-document.querySelectorAll("button.big").forEach(btn => {
-  btn.addEventListener("click", () => {
-    const v = clamp(btn.dataset.value);
-    syncInputs(v);
-    postValue(v);
-  });
-});
-
-num.addEventListener("input", () => {
-  syncInputs(clamp(num.value));
-});
-rng.addEventListener("input", () => {
-  syncInputs(clamp(rng.value));
-});
-
-saveBtn.addEventListener("click", () => {
-  const v = clamp(num.value);
-  syncInputs(v);
-  postValue(v);
-});
-
-// initial and polling
-fetchValue();
-setInterval(fetchValue, POLL_MS);
-</script>
-</body>
-</html>
-    """
-    return render_template_string(
-        html,
-        value_file=os.path.abspath(VALUE_FILE),
-        value=val,
-        mtime_human=_fmt_time(mtime),
-        poll_ms=POLL_INTERVAL_MS,
-    )
-
-
-@app.route("/value", methods=["GET"])
-def get_value():
-    _ensure_file_exists()
-    try:
-        val = _read_value()
-    except Exception:
-        # If the file is corrupt or unreadable, reset to 0
-        with _write_lock:
-            _atomic_write("0")
-        val = 0
-    mtime = _get_mtime()
-    return jsonify({"value": val, "mtime": mtime, "mtime_human": _fmt_time(mtime)})
-
-
-@app.route("/set", methods=["POST"])
-def set_value():
-    if not request.is_json:
-        abort(400, "Expected application/json body")
-    body = request.get_json(silent=True) or {}
-    if "value" not in body:
-        abort(400, "Missing 'value'")
-    try:
-        new_val = _clamp_to_range(int(body["value"]))
-    except Exception:
-        abort(400, "Value must be an integer 0–99")
-
-    with _write_lock:
-        _atomic_write(str(new_val))
-
-    mtime = _get_mtime()
-    return jsonify({"ok": True, "value": new_val, "mtime": mtime, "mtime_human": _fmt_time(mtime)})
-
-
-# --- Utilities ---
-import time
-def _fmt_time(mtime: float) -> str:
-    if not mtime:
-        return "never"
-    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(mtime))
-
-
-# --- Entry point ---
-if __name__ == "__main__":
-    _ensure_file_exists()
-    app.run(host="0.0.0.0", port=int(os.environ.get("PORT", "8087")), debug=True)

+ 1 - 0
dezendorf/applications/temp-set/value.txt

@@ -0,0 +1 @@
+99