126 lines
3.3 KiB
Python
126 lines
3.3 KiB
Python
from fastapi import FastAPI, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import StreamingResponse
|
|
from pydantic import BaseModel
|
|
from contextlib import asynccontextmanager
|
|
from tn3270_service import TN3270Session
|
|
import asyncio
|
|
import logging
|
|
import json
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
session = None
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app):
|
|
global session
|
|
session = TN3270Session()
|
|
try:
|
|
session.connect()
|
|
logger.info("TN3270 session ready")
|
|
except Exception as e:
|
|
logger.error(f"Failed to connect: {e}")
|
|
yield
|
|
if session:
|
|
session.disconnect()
|
|
|
|
app = FastAPI(title="z/OS Web API", version="1.0.0", lifespan=lifespan)
|
|
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
|
|
|
|
class CommandRequest(BaseModel):
|
|
text: str
|
|
|
|
def check():
|
|
if not session or not session.connected:
|
|
raise HTTPException(503, "Not connected to z/OS")
|
|
|
|
@app.get("/health")
|
|
def health():
|
|
return {"status": "ok", "connected": session.connected if session else False}
|
|
|
|
@app.get("/screen")
|
|
def get_screen():
|
|
check()
|
|
return {"screen": session.get_screen()}
|
|
|
|
@app.post("/command")
|
|
def send_command(req: CommandRequest):
|
|
check()
|
|
return {"screen": session.send_command(req.text)}
|
|
|
|
@app.post("/enter")
|
|
def send_enter():
|
|
check()
|
|
return {"screen": session.send_enter()}
|
|
|
|
@app.post("/pf/{key}")
|
|
def send_pf(key: int):
|
|
check()
|
|
if not 1 <= key <= 24:
|
|
raise HTTPException(400, "PF key must be 1-24")
|
|
return {"screen": session.send_pf(key)}
|
|
|
|
@app.post("/tab")
|
|
def send_tab():
|
|
check()
|
|
return {"screen": session.send_tab()}
|
|
|
|
class KeyRequest(BaseModel):
|
|
key: str
|
|
|
|
class CharRequest(BaseModel):
|
|
char: str
|
|
|
|
@app.post("/key")
|
|
def send_key(req: KeyRequest):
|
|
check()
|
|
# Map key names to s3270 commands
|
|
key_map = {
|
|
"Tab": "Tab()",
|
|
"BackTab": "BackTab()",
|
|
"Backspace": "BackSpace()",
|
|
"Delete": "Delete()",
|
|
"Home": "Home()",
|
|
"End": "FieldEnd()",
|
|
"Insert": "Insert()",
|
|
"Clear": "Clear()",
|
|
"Attn": "Attn()",
|
|
"PA1": "PA(1)",
|
|
"PA2": "PA(2)",
|
|
}
|
|
s3270_key = key_map.get(req.key, req.key)
|
|
return {"screen": session.send_key(s3270_key)}
|
|
|
|
@app.post("/char")
|
|
def send_char(req: CharRequest):
|
|
check()
|
|
return {"screen": session.send_char(req.char)}
|
|
|
|
@app.get("/stream/screen")
|
|
async def stream_screen():
|
|
async def event_generator():
|
|
last_screen = ""
|
|
while True:
|
|
try:
|
|
if session and session.connected:
|
|
if not session.lock.locked():
|
|
screen = session.get_screen()
|
|
if screen != last_screen:
|
|
data = json.dumps({"screen": screen})
|
|
yield f"data: {data}\n\n"
|
|
last_screen = screen
|
|
await asyncio.sleep(0.5)
|
|
except Exception as e:
|
|
logger.error(f"Stream error: {e}")
|
|
break
|
|
return StreamingResponse(
|
|
event_generator(),
|
|
media_type="text/event-stream",
|
|
headers={
|
|
"Cache-Control": "no-cache",
|
|
"X-Accel-Buffering": "no",
|
|
}
|
|
)
|