Compare commits
1 Commits
main
...
487eb57e28
| Author | SHA1 | Date | |
|---|---|---|---|
| 487eb57e28 |
+60
-16
@@ -26,6 +26,14 @@ func Open(driver, dsn string) (*Store, error) {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
drv = driverName(driver)
|
drv = driverName(driver)
|
||||||
|
if drv == "sqlite" {
|
||||||
|
dsn = appendSQLitePragmas(dsn, map[string]string{
|
||||||
|
"busy_timeout": "10000",
|
||||||
|
"journal_mode": "WAL",
|
||||||
|
"foreign_keys": "1",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fmt.Printf("CONNECTING TO [[[%s]]]\n", dsn)
|
||||||
db, err = sql.Open(drv, dsn)
|
db, err = sql.Open(drv, dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
goto oops
|
goto oops
|
||||||
@@ -41,22 +49,6 @@ func Open(driver, dsn string) (*Store, error) {
|
|||||||
goto oops
|
goto oops
|
||||||
}
|
}
|
||||||
|
|
||||||
if drv == "sqlite" {
|
|
||||||
_, err = db.Exec(`PRAGMA busy_timeout = 10000`)
|
|
||||||
if err != nil {
|
|
||||||
goto oops
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec(`PRAGMA journal_mode = WAL`)
|
|
||||||
if err != nil {
|
|
||||||
goto oops
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec(`PRAGMA foreign_keys = 1`)
|
|
||||||
if err != nil {
|
|
||||||
goto oops
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &Store{DB: db}, nil
|
return &Store{DB: db}, nil
|
||||||
|
|
||||||
oops:
|
oops:
|
||||||
@@ -66,6 +58,58 @@ oops:
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// appendSQLitePragmas appends _pragma=name(value) URI parameters to the DSN
|
||||||
|
// for any pragma not already present. A plain file path is promoted to a
|
||||||
|
// file: URI so it can carry query parameters.
|
||||||
|
func appendSQLitePragmas(dsn string, pragmas map[string]string) string {
|
||||||
|
var base string
|
||||||
|
var existing string
|
||||||
|
var sep string
|
||||||
|
var idx int
|
||||||
|
var name string
|
||||||
|
var value string
|
||||||
|
var result string
|
||||||
|
|
||||||
|
if strings.HasPrefix(dsn, "file:") {
|
||||||
|
idx = strings.Index(dsn, "?")
|
||||||
|
if idx < 0 {
|
||||||
|
base = dsn
|
||||||
|
existing = ""
|
||||||
|
} else {
|
||||||
|
base = dsn[:idx]
|
||||||
|
existing = dsn[idx+1:]
|
||||||
|
}
|
||||||
|
} else if strings.Contains(dsn, "?") {
|
||||||
|
idx = strings.Index(dsn, "?")
|
||||||
|
base = dsn[:idx]
|
||||||
|
existing = dsn[idx+1:]
|
||||||
|
} else if dsn != ":memory:" {
|
||||||
|
base = "file:" + dsn
|
||||||
|
existing = ""
|
||||||
|
} else {
|
||||||
|
base = dsn
|
||||||
|
existing = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
result = base
|
||||||
|
if existing != "" {
|
||||||
|
result = base + "?" + existing
|
||||||
|
sep = "&"
|
||||||
|
} else {
|
||||||
|
sep = "?"
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, value = range pragmas {
|
||||||
|
// [NOTE] this existence check isn't accurate but is practically good
|
||||||
|
if strings.Contains(existing, "_pragma="+name+"(") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = result + sep + "_pragma=" + name + "(" + value + ")"
|
||||||
|
sep = "&"
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func driverName(driver string) string {
|
func driverName(driver string) string {
|
||||||
switch strings.ToLower(strings.TrimSpace(driver)) {
|
switch strings.ToLower(strings.TrimSpace(driver)) {
|
||||||
case "sqlite", "sqlite3":
|
case "sqlite", "sqlite3":
|
||||||
|
|||||||
@@ -277,6 +277,15 @@ function SessionTerminalPanel(props: SessionPanelProps) {
|
|||||||
syncedSessionIDsRef.current = syncedSessionIDs
|
syncedSessionIDsRef.current = syncedSessionIDs
|
||||||
}, [syncedSessionIDs])
|
}, [syncedSessionIDs])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status !== 'pending' && status !== 'connecting') { return }
|
||||||
|
const timer = window.setTimeout(() => {
|
||||||
|
setError('Connection timed out. Click Connect to try again.')
|
||||||
|
setStatus('error')
|
||||||
|
}, 30000)
|
||||||
|
return () => { window.clearTimeout(timer) }
|
||||||
|
}, [status, sessionID])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let animationFrameID: number
|
let animationFrameID: number
|
||||||
let settleTimerOne: number
|
let settleTimerOne: number
|
||||||
@@ -853,6 +862,8 @@ export default function SSHSessionPage() {
|
|||||||
const [streamWSCheck, setStreamWSCheck] = useState<number>(0)
|
const [streamWSCheck, setStreamWSCheck] = useState<number>(0)
|
||||||
const streamHandlersRef = useRef<Record<string, SessionStreamHandlers>>({})
|
const streamHandlersRef = useRef<Record<string, SessionStreamHandlers>>({})
|
||||||
const attachedSessionIDsRef = useRef<Record<string, boolean>>({})
|
const attachedSessionIDsRef = useRef<Record<string, boolean>>({})
|
||||||
|
const wsReconnectAttemptRef = useRef<number>(0)
|
||||||
|
const wsReconnectTimerRef = useRef<number>(0)
|
||||||
const stackedLayout = useMediaQuery(theme.breakpoints.down('lg'))
|
const stackedLayout = useMediaQuery(theme.breakpoints.down('lg'))
|
||||||
const historyDialogMode: boolean = useMediaQuery(theme.breakpoints.down('md'))
|
const historyDialogMode: boolean = useMediaQuery(theme.breakpoints.down('md'))
|
||||||
const rowCount = useMemo(() => {
|
const rowCount = useMemo(() => {
|
||||||
@@ -1334,6 +1345,7 @@ export default function SSHSessionPage() {
|
|||||||
streamWSRef.current = ws
|
streamWSRef.current = ws
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
let i: number
|
let i: number
|
||||||
|
wsReconnectAttemptRef.current = 0
|
||||||
attachedSessionIDsRef.current = {}
|
attachedSessionIDsRef.current = {}
|
||||||
for (i = 0; i < openSessionIDs.length; i += 1) {
|
for (i = 0; i < openSessionIDs.length; i += 1) {
|
||||||
attachStreamSession(openSessionIDs[i])
|
attachStreamSession(openSessionIDs[i])
|
||||||
@@ -1374,6 +1386,12 @@ export default function SSHSessionPage() {
|
|||||||
handlers = streamHandlersRef.current[ids[i]]
|
handlers = streamHandlersRef.current[ids[i]]
|
||||||
if (handlers) handlers.onTransportClosed('SSH workspace stream closed')
|
if (handlers) handlers.onTransportClosed('SSH workspace stream closed')
|
||||||
}
|
}
|
||||||
|
// schedule reconnect with exponential backoff; immediate on first attempt
|
||||||
|
const delay: number = Math.min(Math.pow(2, wsReconnectAttemptRef.current + 1) * 1000, 30000)
|
||||||
|
wsReconnectAttemptRef.current += 1
|
||||||
|
wsReconnectTimerRef.current = window.setTimeout(() => {
|
||||||
|
setStreamWSCheck((prev) => prev + 1)
|
||||||
|
}, delay)
|
||||||
}
|
}
|
||||||
ws.onerror = () => {
|
ws.onerror = () => {
|
||||||
setPageError((prev) => prev || 'SSH workspace stream error')
|
setPageError((prev) => prev || 'SSH workspace stream error')
|
||||||
@@ -1383,6 +1401,10 @@ export default function SSHSessionPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// close the workspace websocket upon unmount
|
// close the workspace websocket upon unmount
|
||||||
return () => {
|
return () => {
|
||||||
|
if (wsReconnectTimerRef.current) {
|
||||||
|
window.clearTimeout(wsReconnectTimerRef.current)
|
||||||
|
wsReconnectTimerRef.current = 0
|
||||||
|
}
|
||||||
if (streamWSRef.current) {
|
if (streamWSRef.current) {
|
||||||
streamWSRef.current.close()
|
streamWSRef.current.close()
|
||||||
streamWSRef.current = null
|
streamWSRef.current = null
|
||||||
|
|||||||
Reference in New Issue
Block a user