File size: 8,283 Bytes
bfa4d7e
 
 
 
 
 
 
7a5c6c2
893770e
15120f2
7a5c6c2
 
893770e
5414557
e621e7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1bb97ca
e621e7d
 
 
 
 
 
 
 
 
 
 
 
2bbdb67
 
 
 
bfa4d7e
1bb97ca
bfa4d7e
 
 
 
 
 
 
 
 
 
7a5c6c2
bfa4d7e
 
 
 
893770e
 
bfa4d7e
 
 
 
 
 
 
 
893770e
 
7a5c6c2
 
 
 
 
 
 
 
 
 
 
e621e7d
7a5c6c2
bfa4d7e
7a5c6c2
893770e
 
15120f2
893770e
15120f2
893770e
 
 
 
bfa4d7e
893770e
7a5c6c2
11b19c0
8048413
7a5c6c2
8048413
7a5c6c2
8048413
893770e
8048413
1505a4a
7a5c6c2
15120f2
8048413
 
893770e
15120f2
1505a4a
7a5c6c2
893770e
 
 
7a5c6c2
e621e7d
893770e
 
 
 
11b19c0
b49b34a
bfa4d7e
893770e
 
 
2bbdb67
893770e
 
 
2bbdb67
893770e
 
 
e621e7d
893770e
 
 
7a5c6c2
 
893770e
7a5c6c2
893770e
 
 
4a90c95
 
893770e
 
bfa4d7e
016615c
bfa4d7e
893770e
 
 
 
15120f2
 
 
 
bfa4d7e
2bbdb67
5414557
2bbdb67
bfa4d7e
15120f2
5414557
 
15120f2
d52e04f
5414557
 
15120f2
fbb7e2a
d52e04f
15120f2
fbb7e2a
0021ec1
fbb7e2a
065cc17
2c51539
15120f2
 
e621e7d
 
339df26
e621e7d
 
 
 
 
893770e
 
15120f2
bfa4d7e
893770e
7a5c6c2
b49b34a
15120f2
7a5c6c2
8d74528
bfa4d7e
4a90c95
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import os
import time
import requests
import gradio as gr
import openstack
from openstack import exceptions

# GLOBAL SHARED STATE
active_user_count = 0
is_comfit_alive = False
clean_link = "comfit-copilot.mch250095.projects.jetstream-cloud.org:3000"
full_url = f"http://{clean_link}"

# Styled HTML for a flashing neon green block with black text
GREEN_FLASHING_LINK = f"""
<style>
@keyframes flash {{
    0% {{ opacity: 1; }}
    50% {{ opacity: 0.4; }}
    100% {{ opacity: 1; }}
}}
.flashing-btn {{
    display: inline-block;
    padding: 20px 40px;
    font-size: 26px;
    font-weight: 900;
    text-decoration: none;
    color: black !important;
    background-color: #39FF14;
    border: 5px solid black;
    border-radius: 4px;
    text-align: center;
    animation: flash 1s infinite;
    box-shadow: 0 0 20px #39FF14;
    transition: transform 0.2s;
}}
.flashing-btn:hover {{
    animation: none;
    transform: scale(1.05);
    opacity: 1;
}}
</style>
<div style="text-align: center; margin: 30px 0;">
    <a href="{full_url}" target="_blank" class="flashing-btn">
        CLICK HERE FOR COMFIT COPILOT
    </a>
</div>
"""

def get_conn():
    """Authenticates with Jetstream2 (OpenStack) using Application Credentials."""
    return openstack.connect(
        auth_url=os.getenv("OS_AUTH_URL"),
        application_credential_id=os.getenv("OS_APPLICATION_CREDENTIAL_ID"),
        application_credential_secret=os.getenv("OS_APPLICATION_CREDENTIAL_SECRET"),
        auth_type="v3applicationcredential",
        region_name="IU",
        interface="public"
    )

def get_current_status():
    global active_user_count, is_comfit_alive
    try:
        conn = get_conn()
        instance_id = os.getenv("OS_INSTANCE_ID")
        server = conn.compute.find_server(instance_id)
        
        status_text = "❌ Error: Instance Not Found"
        if server:
            status_map = {
                "ACTIVE": "🟒 RUNNING (ACTIVE)",
                "SHELVED_OFFLOADED": "πŸ’€ SLEEPING (SHELVED)",
                "SHUTOFF": "πŸ”΄ POWERED OFF",
                "BUILD": "πŸ› οΈ STARTING...",
                "SHELVING": "⏳ SAVING STATE..."
            }
            status_text = status_map.get(server.status, f"❓ STATE: {server.status}")
        
        if "RUNNING" in status_text:
            try:
                if requests.get(full_url, timeout=2).status_code < 500:
                    is_comfit_alive = True
                else:
                    is_comfit_alive = False
            except:
                is_comfit_alive = False
        else:
            is_comfit_alive = False

        link_update = gr.update(value=GREEN_FLASHING_LINK if is_comfit_alive else "", visible=is_comfit_alive)
        return status_text, f"πŸ‘₯ Active Users: {active_user_count}", link_update
    except Exception as e:
        return f"❌ Error: {str(e)}", f"πŸ‘₯ Active Users: {active_user_count}", gr.update()

def user_joined():
    global active_user_count
    active_user_count += 1
    return f"πŸ‘₯ Active Users: {active_user_count}"

def user_left():
    global active_user_count
    active_user_count = max(0, active_user_count - 1)

def manage_instance():
    global is_comfit_alive
    try:
        conn = get_conn()
        instance_id = os.getenv("OS_INSTANCE_ID")
        server = conn.compute.find_server(instance_id)
        
        if not server:
            yield ("❌ Error: Instance Not Found", gr.update(visible=False))
            return

        if server.status != "ACTIVE":
            yield ("⏳ Milestone 1/3: Un-shelving Begin...", gr.update(visible=False))
            if server.status == "SHELVED_OFFLOADED":
                conn.compute.unshelve_server(server)
            conn.compute.wait_for_server(server, status='ACTIVE', wait=600)
            yield ("βœ… Milestone 2/3: Unshelved! VM is now ACTIVE.", gr.update(visible=False))

        yield ("πŸ”„ Milestone 3/3: Connecting to ComFit...", gr.update(visible=False))
        for i in range(24): 
            try:
                if requests.get(full_url, timeout=3).status_code < 500:
                    is_comfit_alive = True
                    yield ("πŸŽ‰ Success! ComFit is ready.", gr.update(value=GREEN_FLASHING_LINK, visible=True))
                    return
            except: pass
            yield (f"πŸ”„ Milestone 3/3: Software booting ({i+1}/24)...", gr.update(visible=False))
            time.sleep(5)
    except Exception as e:
        yield (f"❌ Error: {str(e)}", gr.update(visible=False))

combined_js = """
<script>
let idleTime = 0;
const MAX_IDLE = 10;
function playNotification(status) {
    if (status.includes("RUNNING")) {
        var audio = new Audio('https://cdn.freesound.org');
        audio.play().catch(e => console.log("Audio play blocked."));
    }
}
function resetTimer() { idleTime = 0; }
window.onmousemove = resetTimer; window.onmousedown = resetTimer; window.onclick = resetTimer; window.onkeypress = resetTimer;
setInterval(function() {
    idleTime++;
    if (idleTime >= MAX_IDLE) {
        alert("Session Expired due to inactivity. Please refresh.");
        window.location.reload();
    }
}, 60000); 
</script>
"""

# FIX 1: Removed theme=gr.themes.Soft() from here
with gr.Blocks() as demo:
    gr.HTML(combined_js)
    session_tracker = gr.State(value="active", delete_callback=user_left)

    gr.Markdown("# ComFit Copilot (Comfort & Fit Copilot) By Innovision")
    
    with gr.Row():
        live_status = gr.Textbox(label="Live Jetstream2 Status (5s refresh)", interactive=False)
        user_display = gr.Textbox(label="Space Activity", interactive=False)

    with gr.Row():
        activate_btn = gr.Button("πŸš€ Activate / Access ComFit Copilot", variant="primary")
        refresh_btn = gr.Button("πŸ”„ Force Refresh Status", variant="secondary")
    
    status_label = gr.Label(value="Ready")
    
    # Flashing link box
    link_box = gr.HTML(visible=False)

    gr.Markdown("---")
    # CENTER ALIGNED INSTRUCTIONS HEADER
    gr.Markdown("<center><h3>πŸ“– Instructions for Visitors</h3></center>")
    gr.Markdown("""
    **Access or Wake Up**  
    - If a flashing **Green Button** is already visible (above these instructions), simply click it to open ComFit Copilot.
    - If no flashing green button is visible, click **Activate / Access ComFit Copilot** button (top left). This will trigger the un-shelving process, which may take up to 2+ mins.
    
    **Note A: Track Milestones**  
    The control panel will cycle through three milestones: Un-shelving, VM Activation, and Software Booting. Please wait while the cloud prepares your environment.
    
    **Note B: Automatic Session Timeout**  
    If you do not interact with this page(or next page - ComFit Copilot interface) for **10 minutes**, your session will expire. 
    
    **Note C: What to do after clicking on flashing green button**  
    Once you sign-up using your email on ComFit Copilot page (next page), a button will pop-up showing that you need to confirm email. You can safely ignore this step(no need to open your inbox) and continue with login using information you just used for sign-up. Gmail/Microsoft 3rd party sign-up may not work for some users.
    """)

    # Logo Footer Section
    gr.Markdown("---")
    gr.Markdown("<center><h3>Thanks to our supporters & collaborators:</h3></center>")
    with gr.Row():
        gr.Image("logo1.png", show_label=False, interactive=False, container=False, scale=1)
        gr.Image("logo2.png", show_label=False, interactive=False, container=False, scale=1)
        gr.Image("logo3.png", show_label=False, interactive=False, container=False, scale=1)

    live_status.change(None, inputs=live_status, js="(s) => playNotification(s)")
    activate_btn.click(fn=manage_instance, outputs=[status_label, link_box])
    refresh_btn.click(fn=get_current_status, outputs=[live_status, user_display, link_box])

    timer = gr.Timer(5)
    timer.tick(fn=get_current_status, outputs=[live_status, user_display, link_box])
    
    demo.load(fn=user_joined, outputs=user_display)
    demo.load(fn=get_current_status, outputs=[live_status, user_display, link_box])

if __name__ == "__main__":
    # FIX 2: Added theme=gr.themes.Soft() here inside launch()
    demo.launch(theme=gr.themes.Soft())