@@ -66,6 +66,7 @@ class CmdAuth extends CmdBase implements UsageAware {
6666 commands. add(new LoginCmd ())
6767 commands. add(new LogoutCmd ())
6868 commands. add(new ConfigCmd ())
69+ commands. add(new StatusCmd ())
6970 }
7071
7172 void usage () {
@@ -387,7 +388,7 @@ class CmdAuth extends CmdBase implements UsageAware {
387388<head>
388389 <style>
389390 body {
390- font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
391+ font-family: Inter, sans-serif;
391392 margin: 0;
392393 padding: 0;
393394 display: flex;
@@ -396,13 +397,16 @@ class CmdAuth extends CmdBase implements UsageAware {
396397 min-height: 100vh;
397398 }
398399 .container {
399- text-align: center;
400400 padding: 2rem;
401401 background: white;
402- border-radius: 8px;
403- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
402+ border: 1px solid rgba(69, 63, 81, .2);
403+ }
404+ h1 {
405+ color: #1e293b;
406+ margin: 0 0 1rem;
407+ font-size: 21px;
408+ font-weight: 600;
404409 }
405- h1 { color: #1e293b; margin: 0 0 0.5rem; }
406410 p { color: #64748b; margin: 0; }
407411 </style>
408412</head>
@@ -412,7 +416,8 @@ class CmdAuth extends CmdBase implements UsageAware {
412416 <p>You can now close this window.</p>
413417 </div>
414418</body>
415- </html>""" )
419+ </html>
420+ """ )
416421 output. flush()
417422
418423 future. complete(code)
@@ -1019,4 +1024,169 @@ class CmdAuth extends CmdBase implements UsageAware {
10191024 result << ' '
10201025 }
10211026 }
1027+
1028+ class StatusCmd implements SubCmd {
1029+
1030+ @Override
1031+ String getName () { ' status' }
1032+
1033+ @Override
1034+ void apply (List<String > args ) {
1035+ if (args. size() > 0 ) {
1036+ throw new AbortOperationException (" Too many arguments for status command" )
1037+ }
1038+
1039+ def config = readConfig()
1040+
1041+ println " Nextflow Seqera Platform authentication status"
1042+ println " "
1043+
1044+ // API endpoint
1045+ def endpointInfo = getConfigValue(config, ' tower.endpoint' , ' TOWER_API_ENDPOINT' , ' https://api.cloud.seqera.io' )
1046+ println " API endpoint: ${ endpointInfo.value} (${ endpointInfo.source} )"
1047+
1048+ // API connection check
1049+ def apiConnectionOk = checkApiConnection(endpointInfo. value as String )
1050+ println " API connection check: ${ apiConnectionOk ? 'OK' : 'ERROR'} "
1051+
1052+ // Access token status
1053+ def tokenInfo = getConfigValue(config, ' tower.accessToken' , ' TOWER_ACCESS_TOKEN' )
1054+ if (tokenInfo. value) {
1055+ println " Access token: Configured (${ tokenInfo.source} )"
1056+ } else {
1057+ println " Access token: Not configured"
1058+ }
1059+
1060+ // Authentication check
1061+ if (tokenInfo. value) {
1062+ try {
1063+ def userInfo = callUserInfoApi(tokenInfo. value as String , endpointInfo. value as String )
1064+ def currentUser = userInfo. userName
1065+ println " Authentication: Success (${ currentUser} )"
1066+ } catch (Exception e) {
1067+ println " Authentication: Error (${ e} )"
1068+ }
1069+ } else {
1070+ println " Authentication: ERROR (no token)"
1071+ }
1072+
1073+ // Monitoring enabled
1074+ def enabledInfo = getConfigValue(config, ' tower.enabled' , null , ' false' )
1075+ def enabledValue = enabledInfo. value?. toString()?. toLowerCase() in [' true' , ' 1' , ' yes' ] ? ' Yes' : ' No'
1076+ println " Local workflow monitoring enabled: ${ enabledValue} (${ enabledInfo.source ?: 'default'} )"
1077+
1078+ // Default workspace
1079+ def workspaceInfo = getConfigValue(config, ' tower.workspaceId' , ' TOWER_WORKFLOW_ID' )
1080+ if (workspaceInfo. value) {
1081+ // Try to get workspace name from API if we have a token
1082+ def workspaceName = null
1083+ if (tokenInfo. value) {
1084+ workspaceName = getWorkspaceNameFromApi(tokenInfo. value as String , endpointInfo. value as String , workspaceInfo. value as String )
1085+ }
1086+
1087+ if (workspaceName) {
1088+ println " Default workspace: '${ workspaceName} ' [${ workspaceInfo.value} ] (${ workspaceInfo.source} )"
1089+ } else {
1090+ println " Default workspace: ${ workspaceInfo.value} (${ workspaceInfo.source} )"
1091+ }
1092+ } else {
1093+ println " Default workspace: Personal workspace (default)"
1094+ }
1095+ }
1096+
1097+ private String shortenPath (String path ) {
1098+ def userHome = System . getProperty(' user.home' )
1099+ if (path. startsWith(userHome)) {
1100+ return ' ~' + path. substring(userHome. length())
1101+ }
1102+ return path
1103+ }
1104+
1105+ private Map getConfigValue (Map config , String configKey , String envVarName , String defaultValue = null ) {
1106+ def configValue = config[configKey]
1107+ def envValue = envVarName ? System . getenv(envVarName) : null
1108+ def effectiveValue = configValue ?: envValue ?: defaultValue
1109+
1110+ def source = null
1111+ if (configValue) {
1112+ source = shortenPath(getConfigFile(). toString())
1113+ } else if (envValue) {
1114+ source = " env var \$ ${ envVarName} "
1115+ } else if (defaultValue) {
1116+ source = " default"
1117+ }
1118+
1119+ return [
1120+ value : effectiveValue,
1121+ source : source,
1122+ fromConfig : configValue != null ,
1123+ fromEnv : envValue != null ,
1124+ isDefault : ! configValue && ! envValue
1125+ ]
1126+ }
1127+
1128+ private String getWorkspaceNameFromApi (String accessToken , String endpoint , String workspaceId ) {
1129+ try {
1130+ // Get user info to get user ID
1131+ def userInfo = callUserInfoApi(accessToken, endpoint)
1132+ def userId = userInfo. id as String
1133+
1134+ // Get workspaces for the user
1135+ def workspacesUrl = " ${ endpoint} /user/${ userId} /workspaces"
1136+ def connection = new URL (workspacesUrl). openConnection() as HttpURLConnection
1137+ connection. requestMethod = ' GET'
1138+ connection. connectTimeout = 10000 // 10 second timeout
1139+ connection. readTimeout = 10000
1140+ connection. setRequestProperty(' Authorization' , " Bearer ${ accessToken} " )
1141+
1142+ if (connection. responseCode != 200 ) {
1143+ return null
1144+ }
1145+
1146+ def response = connection. inputStream. text
1147+ def json = new groovy.json.JsonSlurper (). parseText(response) as Map
1148+ def orgsAndWorkspaces = json. orgsAndWorkspaces as List
1149+
1150+ // Find the workspace with matching ID
1151+ def workspace = orgsAndWorkspaces. find { ((Map )it). workspaceId?. toString() == workspaceId }
1152+ if (workspace) {
1153+ def ws = workspace as Map
1154+ return " ${ ws.orgName} / ${ ws.workspaceFullName} "
1155+ }
1156+
1157+ return null
1158+ } catch (Exception e) {
1159+ return null
1160+ }
1161+ }
1162+
1163+ private boolean checkApiConnection (String endpoint ) {
1164+ try {
1165+ def serviceInfoUrl = " ${ endpoint} /service-info"
1166+ def connection = new URL (serviceInfoUrl). openConnection() as HttpURLConnection
1167+ connection. requestMethod = ' GET'
1168+ connection. connectTimeout = 10000 // 10 second timeout
1169+ connection. readTimeout = 10000
1170+
1171+ return connection. responseCode == 200
1172+ } catch (Exception e) {
1173+ return false
1174+ }
1175+ }
1176+
1177+
1178+ @Override
1179+ void usage (List<String > result ) {
1180+ result << ' Show authentication status and configuration'
1181+ result << " Usage: nextflow auth $name " . toString()
1182+ result << ' '
1183+ result << ' This command shows:'
1184+ result << ' - Authentication status (yes/no) and source'
1185+ result << ' - API endpoint and source'
1186+ result << ' - Monitoring enabled status and source'
1187+ result << ' - Default workspace and source'
1188+ result << ' - System health status (API connection and authentication)'
1189+ result << ' '
1190+ }
1191+ }
10221192}
0 commit comments