@@ -32,7 +32,13 @@ import {
Compass ,
Check ,
EyeOff ,
Sliders
Sliders ,
Lock ,
Unlock ,
Key ,
ShieldAlert ,
ShieldCheck ,
LogOut
} from 'lucide-react' ;
import {
calculateLineOfSight ,
@@ -78,6 +84,66 @@ export const TacticalDefenseView: React.FC = () => {
const mapContainer = useRef < HTMLDivElement > ( null ) ;
const map = useRef < maplibregl.Map | null > ( null ) ;
// Tactical License & Access Clearance State
const [ authKey , setAuthKey ] = useState < string > ( ( ) = > {
return new URLSearchParams ( window . location . search ) . get ( 'key' ) ||
new URLSearchParams ( window . location . search ) . get ( 'api_key' ) ||
sessionStorage . getItem ( 'tactical_api_key' ) || '' ;
} ) ;
const [ authStatus , setAuthStatus ] = useState < 'validating' | 'authorized' | 'unauthorized' > ( 'validating' ) ;
const [ authInfo , setAuthInfo ] = useState < { tenantName? : string ; plan? : string ; keyName? : string } | null > ( null ) ;
const [ authError , setAuthError ] = useState < string > ( '' ) ;
const [ inputKey , setInputKey ] = useState < string > ( '' ) ;
const [ isVerifying , setIsVerifying ] = useState < boolean > ( false ) ;
const verifyTacticalLicense = async ( keyCandidate : string ) = > {
if ( ! keyCandidate || ! keyCandidate . trim ( ) ) {
setAuthStatus ( 'unauthorized' ) ;
return ;
}
setIsVerifying ( true ) ;
setAuthError ( '' ) ;
try {
const apiUrl = ( import . meta as any ) . env . VITE_API_URL || '/api' ;
const res = await fetch ( ` ${ apiUrl } /tactical/verify-license ` , {
headers : { 'x-api-key' : keyCandidate . trim ( ) }
} ) ;
if ( res . ok ) {
const data = await res . json ( ) ;
setAuthInfo ( data ) ;
setAuthKey ( keyCandidate . trim ( ) ) ;
sessionStorage . setItem ( 'tactical_api_key' , keyCandidate . trim ( ) ) ;
setAuthStatus ( 'authorized' ) ;
} else {
sessionStorage . removeItem ( 'tactical_api_key' ) ;
setAuthStatus ( 'unauthorized' ) ;
setAuthError ( 'مفتاح التصريح التكتيكي غير صالح، أو منتهي الصلاحية، أو تم إلغاؤه من قبل الإدارة.' ) ;
}
} catch ( e ) {
sessionStorage . removeItem ( 'tactical_api_key' ) ;
setAuthStatus ( 'unauthorized' ) ;
setAuthError ( 'تعذر التحقق من خادم التخويل التكتيكي. يرجى التأكد من الاتصال.' ) ;
} finally {
setIsVerifying ( false ) ;
}
} ;
useEffect ( ( ) = > {
if ( authKey ) {
verifyTacticalLicense ( authKey ) ;
} else {
setAuthStatus ( 'unauthorized' ) ;
}
} , [ ] ) ;
const handleRevokeOrLock = ( ) = > {
sessionStorage . removeItem ( 'tactical_api_key' ) ;
setAuthKey ( '' ) ;
setAuthStatus ( 'unauthorized' ) ;
setAuthInfo ( null ) ;
setAuthError ( '' ) ;
} ;
const [ mode , setMode ] = useState < TacticalMode > ( 'terrain' ) ;
const [ cursorPos , setCursorPos ] = useState ( { lat : 31.95 , lng : 35.93 , elev : 850 } ) ;
const [ activePlacement , setActivePlacement ] = useState < string | null > ( null ) ;
@@ -983,11 +1049,11 @@ export const TacticalDefenseView: React.FC = () => {
setArtilleryLoading ( true ) ;
try {
const apiUrl = ( import . meta as any ) . env . VITE_API_URL || '/api' ;
const apiKey = ( import . meta as any ) . env . VITE_API_KEY || 'zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX ' ;
const keyToSend = authKey || ( import . meta as any ) . env . VITE_API_KEY || '' ;
const res = await fetch ( ` ${ apiUrl } /tactical/artillery-fire-mission ` , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' , 'x-api-key' : apiKey } ,
headers : { 'Content-Type' : 'application/json' , 'x-api-key' : keyToSend } ,
body : JSON.stringify ( {
gunLat : gunPos [ 0 ] ,
gunLng : gunPos [ 1 ] ,
@@ -1026,10 +1092,10 @@ export const TacticalDefenseView: React.FC = () => {
setHlzLoading ( true ) ;
try {
const apiUrl = ( import . meta as any ) . env . VITE_API_URL || '/api' ;
const apiKey = ( import . meta as any ) . env . VITE_API_KEY || 'zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX ' ;
const keyToSend = authKey || ( import . meta as any ) . env . VITE_API_KEY || '' ;
const res = await fetch ( ` ${ apiUrl } /tactical/hlz-assessment?lat= ${ hlzCenter [ 0 ] } &lng= ${ hlzCenter [ 1 ] } &radius=4000 ` , {
headers : { 'x-api-key' : apiKey }
headers : { 'x-api-key' : keyToSend }
} ) ;
const data = await res . json ( ) ;
setHlzResult ( data ) ;
@@ -1199,11 +1265,57 @@ ${terrainResult.tacticalRecommendations.map((r, i) => `${i + 1}. ${r}`).join('\n
) ) }
< / div >
{ /* Realtime Cursor HUD */ }
< div style = { { display : 'flex' , alignItems : 'center' , gap : 12 , fontSize : '0.72rem' , fontFamily : 'monospace' , color : '#94a3b8' } } >
< div > LAT : < span style = { { color : '#38bdf8' } } > { cursorPos . lat } < / span > < / div >
< div > LNG : < span style = { { color : '#38bdf8' } } > { cursorPos . lng } < / span > < / div >
< div > ELEV : < span style = { { color : '#4ade80 ' } } > { cursorPos . elev } m < / span > < / div >
{ /* Realtime Cursor HUD & Clearance Status */ }
< div style = { { display : 'flex' , alignItems : 'center' , gap : 14 , fontSize : '0.72rem' , fontFamily : 'monospace' , color : '#94a3b8' } } >
< div style = { { display : 'flex' , alignItems : 'center' , gap : 8 } } >
< div > LAT : < span style = { { color : '#38bdf8' } } > { cursorPos . lat } < / span > < / div >
< div > LNG : < span style = { { color : '#38bdf8 ' } } > { cursorPos . lng } < / span > < / div >
< div > ELEV : < span style = { { color : '#4ade80' } } > { cursorPos . elev } m < / span > < / div >
< / div >
{ authStatus === 'authorized' && (
< div style = { { display : 'flex' , alignItems : 'center' , gap : 8 , borderRight : '1px solid rgba(255,255,255,0.15)' , paddingRight : 10 } } >
< div style = { {
display : 'flex' ,
alignItems : 'center' ,
gap : 5 ,
background : 'rgba(34, 197, 94, 0.15)' ,
border : '1px solid rgba(34, 197, 94, 0.35)' ,
color : '#4ade80' ,
padding : '3px 8px' ,
borderRadius : 6 ,
fontSize : '0.7rem' ,
fontWeight : 700 ,
fontFamily : 'inherit'
} } >
< ShieldCheck size = { 13 } / >
< span > ت ص ر ي ح : { authInfo ? . tenantName || 'Enterprise' } < / span >
< / div >
< button
onClick = { handleRevokeOrLock }
title = "قفل المنظومة التكتيكية / خروج"
style = { {
background : 'rgba(239, 68, 68, 0.15)' ,
border : '1px solid rgba(239, 68, 68, 0.3)' ,
color : '#f87171' ,
padding : '3px 8px' ,
borderRadius : 6 ,
fontSize : '0.7rem' ,
fontWeight : 700 ,
cursor : 'pointer' ,
display : 'flex' ,
alignItems : 'center' ,
gap : 4 ,
fontFamily : 'inherit' ,
transition : 'all 0.15s ease'
} }
>
< Lock size = { 12 } / >
< span > ق ف ل < / span >
< / button >
< / div >
) }
< / div >
< / header >
@@ -2781,6 +2893,176 @@ ${terrainResult.tacticalRecommendations.map((r, i) => `${i + 1}. ${r}`).join('\n
{ /* Tactical Map Container */ }
< div ref = { mapContainer } style = { { flex : 1 , height : '100%' , position : 'relative' } } / >
< / div >
{ /* Tactical License Authorization Modal Overlay */ }
{ authStatus !== 'authorized' && (
< div style = { {
position : 'fixed' ,
top : 0 ,
left : 0 ,
width : '100vw' ,
height : '100vh' ,
background : 'rgba(5, 8, 15, 0.94)' ,
backdropFilter : 'blur(25px)' ,
zIndex : 9999 ,
display : 'flex' ,
alignItems : 'center' ,
justifyContent : 'center' ,
direction : 'rtl' ,
padding : 20
} } >
< div style = { {
width : '100%' ,
maxWidth : 520 ,
background : 'linear-gradient(180deg, rgba(15, 23, 42, 0.98), rgba(11, 19, 38, 0.98))' ,
border : '1px solid rgba(56, 189, 248, 0.3)' ,
borderRadius : 16 ,
boxShadow : '0 0 50px rgba(14, 165, 233, 0.2), 0 25px 50px -12px rgba(0, 0, 0, 0.8)' ,
padding : '32px 28px' ,
display : 'flex' ,
flexDirection : 'column' ,
alignItems : 'center' ,
textAlign : 'center' ,
position : 'relative'
} } >
{ /* Top Tactical Glowing Badge */ }
< div style = { {
width : 64 ,
height : 64 ,
borderRadius : 20 ,
background : 'linear-gradient(135deg, rgba(14, 165, 233, 0.2), rgba(79, 70, 229, 0.3))' ,
border : '1px solid rgba(56, 189, 248, 0.4)' ,
display : 'flex' ,
alignItems : 'center' ,
justifyContent : 'center' ,
marginBottom : 18 ,
boxShadow : '0 0 25px rgba(56, 189, 248, 0.35)'
} } >
{ authStatus === 'validating' ? (
< Radio size = { 32 } color = "#38bdf8" / >
) : (
< ShieldAlert size = { 32 } color = "#38bdf8" / >
) }
< / div >
< h2 style = { { fontSize : '1.35rem' , fontWeight : 800 , color : '#f8fafc' , margin : '0 0 6px 0' } } >
م ن ظ و م ة ا ل ق ي ا د ة و ا ل س ي ط ر ة و ا ل ت ح ل ي ل ا ل ت ك ت ي ك ي
< / h2 >
< div style = { { fontSize : '0.82rem' , color : '#94a3b8' , marginBottom : 20 , lineHeight : 1.5 } } >
ب و ا ب ة ا ل ت خ و ي ل ا ل أ م ن ي ا ل ع س ك ر ي — ي ت ط ل ب ا س ت خ د ا م أ د و ا ت ا ل ت ح ل ي ل ا ل ط ب و غ ر ا ف ي و ر م ا ي ا ت ا ل م د ف ع ي ة و ح ق و ل ا ل أ ل غ ا م م ف ت ا ح ت ص ر ي ح م ع ت م د ( < span style = { { color : '#38bdf8' , fontFamily : 'monospace' } } > Enterprise Key < / span > ) .
< / div >
{ authError && (
< div style = { {
width : '100%' ,
background : 'rgba(239, 68, 68, 0.12)' ,
border : '1px solid rgba(239, 68, 68, 0.3)' ,
borderRadius : 8 ,
padding : '10px 14px' ,
color : '#f87171' ,
fontSize : '0.8rem' ,
fontWeight : 600 ,
marginBottom : 18 ,
textAlign : 'right' ,
display : 'flex' ,
alignItems : 'center' ,
gap : 8
} } >
< AlertTriangle size = { 16 } style = { { flexShrink : 0 } } / >
< span > { authError } < / span >
< / div >
) }
< form
onSubmit = { ( e ) = > {
e . preventDefault ( ) ;
verifyTacticalLicense ( inputKey ) ;
} }
style = { { width : '100%' , display : 'flex' , flexDirection : 'column' , gap : 14 } }
>
< div style = { { textAlign : 'right' } } >
< label style = { { display : 'block' , fontSize : '0.78rem' , fontWeight : 700 , color : '#cbd5e1' , marginBottom : 6 } } >
أ د خ ل م ف ت ا ح ا ل ت ر خ ي ص و ا ل ت ص ر ي ح ا ل ت ك ت ي ك ي :
< / label >
< div style = { { position : 'relative' } } >
< input
type = "password"
value = { inputKey }
onChange = { ( e ) = > setInputKey ( e . target . value ) }
placeholder = "in_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
autoFocus
style = { {
width : '100%' ,
background : 'rgba(15, 23, 42, 0.8)' ,
border : '1px solid rgba(255, 255, 255, 0.2)' ,
borderRadius : 10 ,
padding : '12px 14px' ,
color : '#ffffff' ,
fontSize : '0.9rem' ,
fontFamily : 'monospace' ,
outline : 'none' ,
boxSizing : 'border-box'
} }
/ >
< Key size = { 16 } color = "#64748b" style = { { position : 'absolute' , left : 14 , top : 14 } } / >
< / div >
< / div >
< button
type = "submit"
disabled = { isVerifying || ! inputKey . trim ( ) }
style = { {
width : '100%' ,
background : 'linear-gradient(135deg, #0284c7, #4f46e5)' ,
border : 'none' ,
borderRadius : 10 ,
padding : '12px' ,
color : '#ffffff' ,
fontSize : '0.88rem' ,
fontWeight : 800 ,
cursor : isVerifying || ! inputKey . trim ( ) ? 'not-allowed' : 'pointer' ,
opacity : isVerifying || ! inputKey . trim ( ) ? 0.6 : 1 ,
display : 'flex' ,
alignItems : 'center' ,
justifyContent : 'center' ,
gap : 8 ,
boxShadow : '0 4px 14px rgba(2, 132, 199, 0.4)' ,
transition : 'all 0.2s ease'
} }
>
{ isVerifying ? (
< >
< Activity size = { 16 } / >
< span > ج ا ر ي ا ل ت ح ق ق م ن ا ل ص ل ا ح ي ا ت و ا ل ت ص ر ي ح . . . < / span >
< / >
) : (
< >
< ShieldCheck size = { 16 } / >
< span > ت ف ع ي ل ا ل د خ و ل و ا ل ت ح ق ق م ن ا ل ص ل ا ح ي ة < / span >
< / >
) }
< / button >
< / form >
< div style = { { marginTop : 22 , paddingTop : 16 , borderTop : '1px solid rgba(255,255,255,0.08)' , width : '100%' , display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' , fontSize : '0.75rem' , color : '#64748b' } } >
< span > م ن ص ة ا ل خ ر ا ئ ط و ا ل أ ن ظ م ة ا ل ذ ك ي ة < / span >
< a
href = "/"
style = { {
color : '#38bdf8' ,
textDecoration : 'none' ,
fontWeight : 600 ,
display : 'flex' ,
alignItems : 'center' ,
gap : 4
} }
>
← ا ل ع و د ة إ ل ى ا ل خ ر ي ط ة ا ل ع ا م ة
< / a >
< / div >
< / div >
< / div >
) }
< / div >
) ;
} ;