The view shown in the zone list and on the zone page now links to that
view. The zone page and changeset page get breadcrumbs, and the zone list
keeps its filters in the address so going back restores them. Views can
be given a colour (Views > Edit, `jarl view color`, PUT /views/{id}/color)
so zones of the same view are easy to spot; the colour is UI-only, kept
outside changesets, and needs no deploy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
276 lines
9.2 KiB
Go
276 lines
9.2 KiB
Go
// Package api defines the JSON types of Jarl's HTTP API, shared by the
|
|
// server and the Go client used by the CLI.
|
|
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"jarl/internal/model"
|
|
"jarl/internal/nscheck"
|
|
)
|
|
|
|
// Version is the API path prefix.
|
|
const Prefix = "/api/v1"
|
|
|
|
// Zone is a zone as returned by the API.
|
|
type Zone struct {
|
|
model.Zone
|
|
Display string `json:"display_name"`
|
|
View string `json:"view,omitempty"`
|
|
Records int `json:"record_count"`
|
|
ZoneFile string `json:"zone_file,omitempty"` // the file named loads
|
|
RName string `json:"rname_email,omitempty"`
|
|
// Pending is set when listing with a changeset: add, change or delete.
|
|
Pending string `json:"pending,omitempty"`
|
|
}
|
|
|
|
// Record is a record as returned by the API.
|
|
type Record struct {
|
|
model.Record
|
|
Zone string `json:"zone"`
|
|
View string `json:"view,omitempty"`
|
|
RelativeName string `json:"relative_name"`
|
|
EffectiveTTL uint32 `json:"effective_ttl"`
|
|
// Text is the decoded value of TXT and SPF records.
|
|
Text *string `json:"text,omitempty"`
|
|
// Pending is set when listing with a changeset: add, change or delete.
|
|
Pending string `json:"pending,omitempty"`
|
|
}
|
|
|
|
// View is a view with its zone count.
|
|
type View struct {
|
|
model.View
|
|
Zones int `json:"zone_count"`
|
|
Color string `json:"color,omitempty"` // web UI colour, see model.ViewColors
|
|
}
|
|
|
|
// ViewColor sets the colour the web UI shows a view in.
|
|
type ViewColor struct {
|
|
Color string `json:"color"`
|
|
}
|
|
|
|
// ACL is an ACL with the objects that use it.
|
|
type ACL struct {
|
|
model.ACL
|
|
UsedBy []string `json:"used_by"`
|
|
}
|
|
|
|
// TSIGKey is a key; Secret is only filled when asked for and allowed.
|
|
type TSIGKey struct {
|
|
model.TSIGKey
|
|
UsedBy []string `json:"used_by"`
|
|
}
|
|
|
|
// Option is an option with display helpers.
|
|
type Option struct {
|
|
model.Option
|
|
Where string `json:"where"` // e.g. "view internal"
|
|
Description string `json:"description,omitempty"`
|
|
Status string `json:"status,omitempty"`
|
|
}
|
|
|
|
// User is a user with formatted grants.
|
|
type User struct {
|
|
model.User
|
|
GrantStrings []string `json:"grant_strings"`
|
|
}
|
|
|
|
// Token is an API token. Secret is only set when the token is created.
|
|
type Token struct {
|
|
model.Token
|
|
GrantStrings []string `json:"grant_strings,omitempty"`
|
|
Secret string `json:"token,omitempty"`
|
|
}
|
|
|
|
// Whoami describes the authenticated principal.
|
|
type Whoami struct {
|
|
User User `json:"user"`
|
|
Method string `json:"method"`
|
|
Token *Token `json:"token,omitempty"`
|
|
CSRFToken string `json:"csrf_token,omitempty"`
|
|
Permissions []string `json:"permissions"`
|
|
Admin bool `json:"admin"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
// LoginRequest logs in.
|
|
type LoginRequest struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
// ErrorBody is the error response.
|
|
type ErrorBody struct {
|
|
Error ErrorInfo `json:"error"`
|
|
}
|
|
|
|
// ErrorInfo describes an error.
|
|
type ErrorInfo struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
Op int `json:"op,omitempty"`
|
|
Problems json.RawMessage `json:"problems,omitempty"`
|
|
Check json.RawMessage `json:"check,omitempty"`
|
|
Result json.RawMessage `json:"result,omitempty"`
|
|
}
|
|
|
|
// ChangeRequest is a request in a batch (POST /apply or changeset ops).
|
|
type ChangeRequest struct {
|
|
Op string `json:"op"`
|
|
ID int64 `json:"id,omitempty"`
|
|
BaseVersion int `json:"base_version,omitempty"`
|
|
Data json.RawMessage `json:"data,omitempty"`
|
|
}
|
|
|
|
// BatchRequest applies (or stages) several requests at once.
|
|
type BatchRequest struct {
|
|
Description string `json:"description,omitempty"`
|
|
Ops []ChangeRequest `json:"ops"`
|
|
DryRun bool `json:"dry_run,omitempty"`
|
|
Force bool `json:"force,omitempty"`
|
|
// Apply (for POST /changesets) applies the new changeset at once.
|
|
Apply bool `json:"apply,omitempty"`
|
|
}
|
|
|
|
// MutationResponse is returned by endpoints that change the configuration.
|
|
type MutationResponse struct {
|
|
Applied bool `json:"applied"`
|
|
Staged bool `json:"staged,omitempty"`
|
|
DryRun bool `json:"dry_run,omitempty"`
|
|
ChangesetID int64 `json:"changeset_id,omitempty"`
|
|
Result json.RawMessage `json:"result,omitempty"`
|
|
Changeset json.RawMessage `json:"changeset,omitempty"`
|
|
Added []model.Op `json:"added,omitempty"`
|
|
Warnings []string `json:"warnings,omitempty"`
|
|
Problems json.RawMessage `json:"problems,omitempty"`
|
|
Object json.RawMessage `json:"object,omitempty"`
|
|
}
|
|
|
|
// Changeset is a changeset as returned by the API.
|
|
type Changeset struct {
|
|
model.Changeset
|
|
Undoable bool `json:"undoable"`
|
|
}
|
|
|
|
// StatusResponse is GET /status.
|
|
type StatusResponse struct {
|
|
Version string `json:"version"`
|
|
Engine json.RawMessage `json:"engine"`
|
|
Time time.Time `json:"time"`
|
|
// ExpiringTokens are tokens that expire within TokenExpiryWarning or
|
|
// expired less than that long ago (not revoked): every user's for
|
|
// administrators, otherwise the caller's own.
|
|
ExpiringTokens []Token `json:"expiring_tokens"`
|
|
// Backup is the state of database backups (administrators only).
|
|
Backup *BackupStatus `json:"backup,omitempty"`
|
|
}
|
|
|
|
// TokenExpiresHeader is sent with responses to a token that expires
|
|
// soon (model.TokenExpiryWarning), holding its expiry time (RFC 3339). The
|
|
// CLI warns its user when it sees it.
|
|
const TokenExpiresHeader = "Jarl-Token-Expires"
|
|
|
|
// BackupStatus describes the newest database backup, as recorded by
|
|
// 'jarld backup'.
|
|
type BackupStatus struct {
|
|
LastAt *time.Time `json:"last_at,omitempty"`
|
|
LastFile string `json:"last_file,omitempty"`
|
|
LastSize int64 `json:"last_size,omitempty"`
|
|
LastError string `json:"last_error,omitempty"`
|
|
// LastErrorAt is when the newest failed backup ran (only reported
|
|
// when it is newer than the last success).
|
|
LastErrorAt *time.Time `json:"last_error_at,omitempty"`
|
|
Dir string `json:"dir"`
|
|
// WarnAfter (seconds) is how old the newest backup may get before
|
|
// Stale is set (0: never).
|
|
WarnAfter int64 `json:"warn_after"`
|
|
Stale bool `json:"stale"`
|
|
}
|
|
|
|
// SearchResult is one search hit.
|
|
type SearchResult struct {
|
|
Kind string `json:"kind"` // zone, record, acl, view, key
|
|
ID int64 `json:"id"`
|
|
ZoneID int64 `json:"zone_id,omitempty"`
|
|
Label string `json:"label"`
|
|
Detail string `json:"detail,omitempty"`
|
|
}
|
|
|
|
// PasswordChange changes a password.
|
|
type PasswordChange struct {
|
|
Current string `json:"current_password,omitempty"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
// UserInput creates or updates a user.
|
|
type UserInput struct {
|
|
Username *string `json:"username,omitempty"`
|
|
Password *string `json:"password,omitempty"`
|
|
Admin *bool `json:"admin,omitempty"`
|
|
Grants *[]model.Grant `json:"grants,omitempty"`
|
|
// GrantStrings are grants in compact form ("records:write zones=x").
|
|
GrantStrings *[]string `json:"grant_strings,omitempty"`
|
|
Role *string `json:"role,omitempty"` // a role template name
|
|
Disabled *bool `json:"disabled,omitempty"`
|
|
Comment *string `json:"comment,omitempty"`
|
|
}
|
|
|
|
// TokenInput creates a token.
|
|
type TokenInput struct {
|
|
Name string `json:"name"`
|
|
Grants *[]model.Grant `json:"grants,omitempty"`
|
|
GrantStrings *[]string `json:"grant_strings,omitempty"`
|
|
Role string `json:"role,omitempty"`
|
|
ExpiresIn string `json:"expires_in,omitempty"` // e.g. "90d", "720h"
|
|
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
|
}
|
|
|
|
// TokenRotation gives a token a new secret (POST /tokens/{id}/rotate).
|
|
// Every field is optional.
|
|
type TokenRotation struct {
|
|
// Grace keeps the old secret working this long (e.g. "1h", "2d"; at
|
|
// most 7d), so clients can be updated without an outage. Default:
|
|
// the old secret stops at once.
|
|
Grace string `json:"grace,omitempty"`
|
|
// The new expiry. Default: a token that expires gets its lifetime
|
|
// again, counted from now; one that doesn't, still doesn't.
|
|
ExpiresIn string `json:"expires_in,omitempty"`
|
|
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
|
NoExpiry bool `json:"no_expiry,omitempty"`
|
|
}
|
|
|
|
// ZoneImport imports zone file text into a zone.
|
|
type ZoneImport struct {
|
|
Text string `json:"text"`
|
|
// Mode "merge" adds missing records; "replace" makes the zone equal
|
|
// to the file (deleting other records).
|
|
Mode string `json:"mode"`
|
|
DryRun bool `json:"dry_run,omitempty"`
|
|
// UseSOA also takes the SOA timers and default TTL from the file.
|
|
UseSOA bool `json:"use_soa,omitempty"`
|
|
}
|
|
|
|
// SerialsResponse is GET /zones/{zone}/serials.
|
|
type SerialsResponse struct {
|
|
Zone string `json:"zone"`
|
|
View string `json:"view,omitempty"`
|
|
Type string `json:"type"`
|
|
// Serial is what this server serves: named's loaded serial, or for a
|
|
// primary zone Jarl's when named can't be asked (SerialSource says
|
|
// which).
|
|
Serial *uint32 `json:"serial"`
|
|
SerialSource string `json:"serial_source,omitempty"`
|
|
Servers []nscheck.Result `json:"servers"`
|
|
Notes []string `json:"notes"`
|
|
CheckedAt time.Time `json:"checked_at"`
|
|
}
|
|
|
|
// DelegationResponse is GET /zones/{zone}/delegation.
|
|
type DelegationResponse struct {
|
|
*nscheck.DelegationReport
|
|
View string `json:"view,omitempty"`
|
|
Notes []string `json:"notes"`
|
|
CheckedAt time.Time `json:"checked_at"`
|
|
}
|