mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-07 21:13:27 -05:00
Chats and agents can check a PiKVM (kvmd) device and, when a person confirms each call, press the power buttons of the machine it controls and use its keyboard. Read tools: get-info (model, versions, no serial or host name), get-power-state (ATX LEDs) and get-keyboard-state. Write tools: power-action (on, off, reset; off_hard, reset_hard and long_press only with a per-device switch), type-text (bounded, no control characters, refuses key, token and password-list shapes) and send-key (a short allowlist, no Windows or Meta chords). All three declare approval always. The preview reads the power state; apply reads it again and rejects a stale operation. The add-on journals before it sends, never retries and reports unknown when the connection drops. One connection is one device: the address is the single resource of the connection and the one sealed credential holds user:password, split at the first colon. A one-time code is not supported. The add-on speaks only to that address, refuses redirects, uses no proxy and accepts a self-signed certificate only through a #sha256= pin in the address. Typing, the text length and hard power actions are per-device switches that an administrator sets on the add-on page; all start off. Snapshots, mass storage, GPIO and the mouse are left out. The add-on ships a skill, a connection page with a Test button and approval cards, the package files, the library entry and the docs, and joins the package workflow and the case inventory. PiKVM API reference: https://docs.pikvm.org/api/ Assisted-by: Claude:claude-sonnet-5-5 go node
80 lines
2.8 KiB
Go
80 lines
2.8 KiB
Go
// SPDX-License-Identifier: MIT
|
|
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"sort"
|
|
|
|
"github.com/mudler/LocalAI/addons/pikvm/internal/kvmd"
|
|
"github.com/mudler/LocalAI/addons/pikvm/internal/settings"
|
|
"github.com/mudler/LocalAI/pkg/addons/contract"
|
|
)
|
|
|
|
func (r *Runtime) isAdmin(ctx context.Context, env contract.Envelope) bool {
|
|
return r.Admin != nil && r.Admin.IsAdmin(ctx, env.Request.OperationID)
|
|
}
|
|
|
|
// getSettings is the page's read of the per-device switches. Anyone who may
|
|
// open the page sees them; only an administrator sees them as editable.
|
|
func (r *Runtime) getSettings(ctx context.Context, env contract.Envelope) (contract.Result, error) {
|
|
all, err := r.Settings.All()
|
|
keys := make([]string, 0, len(all))
|
|
for k := range all {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
rows := make([]map[string]any, 0, len(keys))
|
|
for _, k := range keys {
|
|
d := all[k]
|
|
rows = append(rows, map[string]any{"device": k, "allowHardPower": d.AllowHardPower, "allowTyping": d.AllowTyping, "maxTypeChars": d.MaxTypeChars})
|
|
}
|
|
return finish(map[string]any{
|
|
"devices": rows, "admin": r.isAdmin(ctx, env), "broken": err != nil,
|
|
"defaults": map[string]any{"allowHardPower": false, "allowTyping": false, "maxTypeChars": settings.DefaultTypeChars},
|
|
"limits": map[string]any{"maxTypeChars": settings.MaxTypeChars},
|
|
"keys": AllowedKeys(),
|
|
})
|
|
}
|
|
|
|
type setSettingsArgs struct {
|
|
Device string `json:"device"`
|
|
AllowHardPower *bool `json:"allowHardPower"`
|
|
AllowTyping *bool `json:"allowTyping"`
|
|
MaxTypeChars *int `json:"maxTypeChars"`
|
|
}
|
|
|
|
// setSettings changes the fields the page names for one device. Only an
|
|
// administrator may call it.
|
|
func (r *Runtime) setSettings(ctx context.Context, env contract.Envelope, args map[string]json.RawMessage) (contract.Result, error) {
|
|
if !r.isAdmin(ctx, env) {
|
|
return contract.Result{}, bad("only an administrator can change these settings")
|
|
}
|
|
var a setSettingsArgs
|
|
if err := decodeArgs(args, &a); err != nil {
|
|
return contract.Result{}, err
|
|
}
|
|
dev, err := kvmd.ParseDevice(a.Device)
|
|
if err != nil {
|
|
return contract.Result{}, bad("%s", kvmd.Describe(err))
|
|
}
|
|
cur, _ := r.Settings.For(dev.Key())
|
|
if a.AllowHardPower != nil {
|
|
cur.AllowHardPower = *a.AllowHardPower
|
|
}
|
|
if a.AllowTyping != nil {
|
|
cur.AllowTyping = *a.AllowTyping
|
|
}
|
|
if a.MaxTypeChars != nil {
|
|
if *a.MaxTypeChars < 1 || *a.MaxTypeChars > settings.MaxTypeChars {
|
|
return contract.Result{}, bad("maxTypeChars is between 1 and %d", settings.MaxTypeChars)
|
|
}
|
|
cur.MaxTypeChars = *a.MaxTypeChars
|
|
}
|
|
saved, err := r.Settings.Set(dev.Key(), cur)
|
|
if err != nil {
|
|
return contract.Result{}, bad("%s", err.Error())
|
|
}
|
|
return finish(map[string]any{"saved": true, "device": dev.Key(), "allowHardPower": saved.AllowHardPower, "allowTyping": saved.AllowTyping, "maxTypeChars": saved.MaxTypeChars})
|
|
}
|