ltcai 0.1.26 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/docs/CHANGELOG.md +23 -0
- package/docs/architecture.md +35 -0
- package/docs/kg-schema.md +210 -0
- package/docs/spec-vs-impl.md +132 -0
- package/knowledge_graph.py +29 -1
- package/package.json +3 -1
- package/server.py +43 -1
- package/static/account.html +237 -37
- package/static/admin.html +18 -1
- package/static/chat.html +533 -10
- package/static/css/tokens.css +169 -0
- package/static/graph.html +18 -1
- package/static/lattice-reference.css +1421 -0
- package/static/sw.js +3 -1
package/static/account.html
CHANGED
|
@@ -210,9 +210,9 @@
|
|
|
210
210
|
|
|
211
211
|
/* Lang picker */
|
|
212
212
|
.lang-wrap {
|
|
213
|
-
position:
|
|
214
|
-
top:
|
|
215
|
-
right:
|
|
213
|
+
position: absolute;
|
|
214
|
+
top: 12px;
|
|
215
|
+
right: 12px;
|
|
216
216
|
z-index: 10;
|
|
217
217
|
}
|
|
218
218
|
.lang-btn {
|
|
@@ -249,41 +249,220 @@
|
|
|
249
249
|
}
|
|
250
250
|
.lang-opt:hover { background: rgba(167,124,255,0.10); color: var(--text); }
|
|
251
251
|
.lang-opt.active { color: var(--accent); font-weight: 700; }
|
|
252
|
+
|
|
253
|
+
/* PPT reference theme: bright indigo sign-in with SSO and language switcher. */
|
|
254
|
+
:root {
|
|
255
|
+
--bg: #f7f3ff;
|
|
256
|
+
--text: #1f2140;
|
|
257
|
+
--faint: #8a86a8;
|
|
258
|
+
--muted: #66627f;
|
|
259
|
+
--accent: #6f4bf6;
|
|
260
|
+
--accent-2: #7b6dff;
|
|
261
|
+
--shadow: 0 26px 80px rgba(86, 70, 160, 0.22);
|
|
262
|
+
}
|
|
263
|
+
body {
|
|
264
|
+
background:
|
|
265
|
+
radial-gradient(circle at 74% 22%, rgba(111,75,246,0.16), transparent 28%),
|
|
266
|
+
radial-gradient(circle at 16% 18%, rgba(196,181,253,0.32), transparent 26%),
|
|
267
|
+
linear-gradient(135deg, #fbf9ff 0%, #f4efff 48%, #ffffff 100%);
|
|
268
|
+
}
|
|
269
|
+
body::before {
|
|
270
|
+
background:
|
|
271
|
+
radial-gradient(circle, rgba(123,109,255,0.28) 1px, transparent 1.8px),
|
|
272
|
+
linear-gradient(rgba(123,109,255,0.08) 1px, transparent 1px),
|
|
273
|
+
linear-gradient(90deg, rgba(123,109,255,0.06) 1px, transparent 1px);
|
|
274
|
+
mask-image: linear-gradient(180deg, rgba(0,0,0,0.18), rgba(0,0,0,0.02));
|
|
275
|
+
}
|
|
276
|
+
body::after {
|
|
277
|
+
background:
|
|
278
|
+
radial-gradient(ellipse at 8% 78%, rgba(142,111,255,0.24), transparent 34%),
|
|
279
|
+
linear-gradient(115deg, transparent 0 35%, rgba(111,75,246,0.09) 35.2%, transparent 35.6% 100%);
|
|
280
|
+
}
|
|
281
|
+
.login-shell {
|
|
282
|
+
width: min(920px, 100%);
|
|
283
|
+
display: grid;
|
|
284
|
+
grid-template-columns: minmax(280px, 400px) minmax(220px, 1fr);
|
|
285
|
+
align-items: center;
|
|
286
|
+
gap: 46px;
|
|
287
|
+
position: relative;
|
|
288
|
+
z-index: 1;
|
|
289
|
+
}
|
|
290
|
+
.brand-preview {
|
|
291
|
+
min-height: 360px;
|
|
292
|
+
border-radius: 34px;
|
|
293
|
+
background:
|
|
294
|
+
radial-gradient(circle at 54% 45%, rgba(111,75,246,0.24), transparent 18%),
|
|
295
|
+
linear-gradient(145deg, rgba(255,255,255,0.68), rgba(244,239,255,0.32));
|
|
296
|
+
border: 1px solid rgba(111,75,246,0.12);
|
|
297
|
+
box-shadow: inset 0 1px 0 rgba(255,255,255,0.85);
|
|
298
|
+
position: relative;
|
|
299
|
+
overflow: hidden;
|
|
300
|
+
}
|
|
301
|
+
.brand-preview::before {
|
|
302
|
+
content: '';
|
|
303
|
+
position: absolute;
|
|
304
|
+
inset: auto -12% 0 -8%;
|
|
305
|
+
height: 46%;
|
|
306
|
+
background: linear-gradient(135deg, rgba(111,75,246,0.24), rgba(255,255,255,0.12));
|
|
307
|
+
clip-path: polygon(0 62%, 18% 42%, 36% 54%, 55% 26%, 76% 44%, 100% 20%, 100% 100%, 0 100%);
|
|
308
|
+
filter: blur(1px);
|
|
309
|
+
}
|
|
310
|
+
.preview-node {
|
|
311
|
+
position: absolute;
|
|
312
|
+
width: 52px;
|
|
313
|
+
height: 52px;
|
|
314
|
+
border-radius: 17px;
|
|
315
|
+
background: #fff;
|
|
316
|
+
border: 1px solid rgba(111,75,246,0.18);
|
|
317
|
+
box-shadow: 0 18px 40px rgba(111,75,246,0.16);
|
|
318
|
+
}
|
|
319
|
+
.preview-node::after {
|
|
320
|
+
content: '';
|
|
321
|
+
position: absolute;
|
|
322
|
+
inset: 14px;
|
|
323
|
+
border-radius: 10px;
|
|
324
|
+
background: linear-gradient(135deg, #6f4bf6, #9f8cff);
|
|
325
|
+
}
|
|
326
|
+
.preview-node.n1 { top: 52px; left: 58px; }
|
|
327
|
+
.preview-node.n2 { top: 112px; right: 64px; }
|
|
328
|
+
.preview-node.n3 { left: 120px; bottom: 92px; }
|
|
329
|
+
.preview-node.n4 { right: 120px; bottom: 58px; }
|
|
330
|
+
.preview-line {
|
|
331
|
+
position: absolute;
|
|
332
|
+
height: 2px;
|
|
333
|
+
width: 150px;
|
|
334
|
+
background: linear-gradient(90deg, transparent, rgba(111,75,246,0.45), transparent);
|
|
335
|
+
transform-origin: left center;
|
|
336
|
+
}
|
|
337
|
+
.preview-line.l1 { top: 108px; left: 112px; transform: rotate(18deg); }
|
|
338
|
+
.preview-line.l2 { top: 186px; left: 176px; transform: rotate(112deg); }
|
|
339
|
+
.preview-line.l3 { bottom: 112px; left: 172px; transform: rotate(-13deg); }
|
|
340
|
+
.card {
|
|
341
|
+
background: rgba(255,255,255,0.86);
|
|
342
|
+
border: 1px solid rgba(111,75,246,0.13);
|
|
343
|
+
border-radius: 14px;
|
|
344
|
+
box-shadow: var(--shadow), inset 0 1px 0 rgba(255,255,255,0.9);
|
|
345
|
+
}
|
|
346
|
+
.card::before {
|
|
347
|
+
background: linear-gradient(90deg, transparent, rgba(111,75,246,0.65), rgba(123,109,255,0.45), transparent);
|
|
348
|
+
}
|
|
349
|
+
.logo {
|
|
350
|
+
background: linear-gradient(135deg, #6f4bf6 0%, #8d7aff 100%);
|
|
351
|
+
color: #fff;
|
|
352
|
+
box-shadow: 0 16px 34px rgba(111,75,246,0.28);
|
|
353
|
+
}
|
|
354
|
+
.title {
|
|
355
|
+
background: linear-gradient(135deg, #1f2140 35%, #6f4bf6 82%);
|
|
356
|
+
-webkit-background-clip: text;
|
|
357
|
+
background-clip: text;
|
|
358
|
+
}
|
|
359
|
+
.input {
|
|
360
|
+
background: #fbfaff;
|
|
361
|
+
border-color: rgba(111,75,246,0.16);
|
|
362
|
+
color: var(--text);
|
|
363
|
+
}
|
|
364
|
+
.input:focus {
|
|
365
|
+
border-color: rgba(111,75,246,0.52);
|
|
366
|
+
box-shadow: 0 0 0 4px rgba(111,75,246,0.10);
|
|
367
|
+
}
|
|
368
|
+
.submit {
|
|
369
|
+
background: linear-gradient(135deg, #6f4bf6, #7b6dff);
|
|
370
|
+
color: #fff;
|
|
371
|
+
box-shadow: 0 14px 30px rgba(111,75,246,0.24);
|
|
372
|
+
}
|
|
373
|
+
.submit:hover {
|
|
374
|
+
background: linear-gradient(135deg, #5f3ee6, #705fff);
|
|
375
|
+
box-shadow: 0 18px 38px rgba(111,75,246,0.30);
|
|
376
|
+
}
|
|
377
|
+
.sso-btn {
|
|
378
|
+
background: #fff;
|
|
379
|
+
border-color: rgba(111,75,246,0.15);
|
|
380
|
+
color: var(--text);
|
|
381
|
+
}
|
|
382
|
+
.sso-btn:hover {
|
|
383
|
+
background: #f6f2ff;
|
|
384
|
+
border-color: rgba(111,75,246,0.34);
|
|
385
|
+
}
|
|
386
|
+
.lang-btn {
|
|
387
|
+
background: rgba(255,255,255,0.86);
|
|
388
|
+
border-color: rgba(111,75,246,0.18);
|
|
389
|
+
color: var(--text);
|
|
390
|
+
box-shadow: 0 10px 28px rgba(86,70,160,0.12);
|
|
391
|
+
}
|
|
392
|
+
.lang-menu {
|
|
393
|
+
background: #fff;
|
|
394
|
+
border-color: rgba(111,75,246,0.15);
|
|
395
|
+
box-shadow: 0 18px 44px rgba(86,70,160,0.16);
|
|
396
|
+
}
|
|
397
|
+
@media (max-width: 760px) {
|
|
398
|
+
.login-shell { display: block; }
|
|
399
|
+
.brand-preview { display: none; }
|
|
400
|
+
}
|
|
252
401
|
</style>
|
|
402
|
+
<link rel="stylesheet" href="/static/lattice-reference.css">
|
|
253
403
|
</head>
|
|
254
|
-
<body>
|
|
404
|
+
<body class="lattice-ref-auth">
|
|
255
405
|
<div class="orb orb-1"></div>
|
|
256
406
|
<div class="orb orb-2"></div>
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
<
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
407
|
+
<div class="auth-titlebar" aria-hidden="true">
|
|
408
|
+
<div class="auth-window-brand"><i class="ti ti-cube-3d-sphere"></i><span>Lattice AI</span></div>
|
|
409
|
+
<div class="auth-window-controls"><span></span><span></span><span></span></div>
|
|
410
|
+
</div>
|
|
411
|
+
<div class="auth-wave auth-wave-left" aria-hidden="true"></div>
|
|
412
|
+
<div class="auth-wave auth-wave-right" aria-hidden="true"></div>
|
|
413
|
+
<div class="auth-network" aria-hidden="true">
|
|
414
|
+
<span></span><span></span><span></span><span></span><span></span><span></span>
|
|
264
415
|
</div>
|
|
265
416
|
|
|
417
|
+
<div class="login-shell">
|
|
418
|
+
<div class="brand-preview" aria-hidden="true">
|
|
419
|
+
<div class="preview-node n1"></div>
|
|
420
|
+
<div class="preview-node n2"></div>
|
|
421
|
+
<div class="preview-node n3"></div>
|
|
422
|
+
<div class="preview-node n4"></div>
|
|
423
|
+
<div class="preview-line l1"></div>
|
|
424
|
+
<div class="preview-line l2"></div>
|
|
425
|
+
<div class="preview-line l3"></div>
|
|
426
|
+
</div>
|
|
266
427
|
<div class="card">
|
|
428
|
+
<div class="lang-wrap">
|
|
429
|
+
<button class="lang-btn" id="lang-btn" onclick="toggleLang()">🌐 Language</button>
|
|
430
|
+
<div class="lang-menu" id="lang-menu">
|
|
431
|
+
<div class="lang-opt" id="opt-ko" onclick="setLang('ko')">🇰🇷 한국어</div>
|
|
432
|
+
<div class="lang-opt" id="opt-en" onclick="setLang('en')">🇺🇸 English</div>
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
267
435
|
<!-- Login form -->
|
|
268
436
|
<div id="login-section">
|
|
269
|
-
<div class="logo"
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
<
|
|
437
|
+
<div class="hero-logo">
|
|
438
|
+
<div class="hero-logo-mark"><i class="ti ti-cube-3d-sphere"></i></div>
|
|
439
|
+
<h2 class="title" id="login-title">Lattice AI</h2>
|
|
440
|
+
</div>
|
|
441
|
+
<p class="subtitle" id="login-sub">내 PC에서 시작하는<br>개인 AI 워크스페이스</p>
|
|
442
|
+
<label class="auth-field">
|
|
443
|
+
<i class="ti ti-mail"></i>
|
|
444
|
+
<input class="input" type="email" id="login-email" placeholder="이메일 주소">
|
|
445
|
+
</label>
|
|
446
|
+
<label class="auth-field">
|
|
447
|
+
<i class="ti ti-lock"></i>
|
|
448
|
+
<input class="input" type="password" id="login-pw" placeholder="비밀번호" onkeydown="if(event.key==='Enter')doLogin()">
|
|
449
|
+
<button type="button" class="field-eye" onclick="togglePasswordVisibility()" title="비밀번호 보기"><i class="ti ti-eye"></i></button>
|
|
450
|
+
</label>
|
|
274
451
|
<div class="msg" id="login-msg"></div>
|
|
275
452
|
<button class="submit" id="login-btn" onclick="doLogin()" data-ko="로그인" data-en="Log in">로그인</button>
|
|
276
|
-
<
|
|
277
|
-
|
|
278
|
-
<
|
|
279
|
-
|
|
280
|
-
<span
|
|
453
|
+
<button class="register-cta" id="go-register-link" onclick="showSection('register');return false;">회원가입</button>
|
|
454
|
+
<div id="sso-section">
|
|
455
|
+
<div class="sso-divider" id="sso-divider-text">조직 계정으로 로그인</div>
|
|
456
|
+
<button class="sso-btn sso-ms" onclick="doSSOLogin('microsoft')">
|
|
457
|
+
<span class="ms-logo" aria-hidden="true"><b></b><b></b><b></b><b></b></span>
|
|
458
|
+
<span id="sso-ms-label">Microsoft Entra ID로 계속하기</span>
|
|
459
|
+
</button>
|
|
460
|
+
<button class="sso-btn sso-okta" onclick="doSSOLogin('okta')">
|
|
461
|
+
<span class="okta-logo" aria-hidden="true"></span>
|
|
462
|
+
<span id="sso-okta-label">Okta SSO로 계속하기</span>
|
|
281
463
|
</button>
|
|
282
464
|
</div>
|
|
283
|
-
<
|
|
284
|
-
<span id="no-account-text">계정이 없으신가요?</span>
|
|
285
|
-
<a href="#" onclick="showSection('register');return false;" id="go-register-link">회원가입</a>
|
|
286
|
-
</p>
|
|
465
|
+
<button class="local-start" onclick="showSection('register');return false;"><i class="ti ti-device-desktop"></i> <span id="local-start-label">로컬 계정으로 시작</span></button>
|
|
287
466
|
</div>
|
|
288
467
|
|
|
289
468
|
<!-- Register form -->
|
|
@@ -304,6 +483,11 @@
|
|
|
304
483
|
</p>
|
|
305
484
|
</div>
|
|
306
485
|
</div>
|
|
486
|
+
</div>
|
|
487
|
+
<footer class="auth-footer">
|
|
488
|
+
<a href="#" onclick="return false;" id="help-link">도움말</a>
|
|
489
|
+
<a href="#" onclick="return false;" id="privacy-link">개인정보 처리방침</a>
|
|
490
|
+
</footer>
|
|
307
491
|
|
|
308
492
|
<script>
|
|
309
493
|
const API_BASE = window.location.protocol === 'file:' ? 'http://localhost:4825' : '';
|
|
@@ -315,7 +499,7 @@
|
|
|
315
499
|
// ── i18n ──────────────────────────────────────────────
|
|
316
500
|
const I18N = {
|
|
317
501
|
ko: {
|
|
318
|
-
login_title: 'Lattice AI', login_sub: '
|
|
502
|
+
login_title: 'Lattice AI', login_sub: '내 PC에서 시작하는<br>개인 AI 워크스페이스',
|
|
319
503
|
ph_email: '이메일 주소', ph_pw: '비밀번호', ph_new_pw: '비밀번호 (4자 이상)',
|
|
320
504
|
ph_pw_confirm: '비밀번호 확인', ph_name: '이름', ph_nick: '닉네임',
|
|
321
505
|
btn_login: '로그인', btn_register: '가입하기',
|
|
@@ -326,10 +510,13 @@
|
|
|
326
510
|
err_fill: '모든 항목을 입력해주세요.',
|
|
327
511
|
err_login_fail: '이메일 또는 비밀번호가 틀렸습니다.',
|
|
328
512
|
err_server: '서버 연결 실패',
|
|
329
|
-
sso_divider: '
|
|
513
|
+
sso_divider: '조직 계정으로 로그인', sso_btn: '로 로그인',
|
|
514
|
+
ms_sso: 'Microsoft Entra ID로 계속하기', okta_sso: 'Okta SSO로 계속하기',
|
|
515
|
+
local_start: '로컬 계정으로 시작', help: '도움말', privacy: '개인정보 처리방침',
|
|
516
|
+
sso_unavailable: 'SSO가 아직 설정되지 않았습니다. 로컬 계정으로 시작하거나 관리자에게 문의하세요.',
|
|
330
517
|
},
|
|
331
518
|
en: {
|
|
332
|
-
login_title: 'Lattice AI', login_sub: '
|
|
519
|
+
login_title: 'Lattice AI', login_sub: 'Your personal AI workspace<br>starts on this PC',
|
|
333
520
|
ph_email: 'Email address', ph_pw: 'Password', ph_new_pw: 'Password (min. 4 chars)',
|
|
334
521
|
ph_pw_confirm: 'Confirm password', ph_name: 'Full name', ph_nick: 'Nickname',
|
|
335
522
|
btn_login: 'Log in', btn_register: 'Sign up',
|
|
@@ -340,7 +527,10 @@
|
|
|
340
527
|
err_fill: 'Please fill in all fields.',
|
|
341
528
|
err_login_fail: 'Invalid email or password.',
|
|
342
529
|
err_server: 'Server connection failed',
|
|
343
|
-
sso_divider: '
|
|
530
|
+
sso_divider: 'Sign in with organization account', sso_btn: 'Sign in with',
|
|
531
|
+
ms_sso: 'Continue with Microsoft Entra ID', okta_sso: 'Continue with Okta SSO',
|
|
532
|
+
local_start: 'Start with a local account', help: 'Help', privacy: 'Privacy Policy',
|
|
533
|
+
sso_unavailable: 'SSO is not configured yet. Start with a local account or contact your administrator.',
|
|
344
534
|
}
|
|
345
535
|
};
|
|
346
536
|
|
|
@@ -349,12 +539,11 @@
|
|
|
349
539
|
|
|
350
540
|
function applyI18n() {
|
|
351
541
|
document.getElementById('login-title').textContent = t('login_title');
|
|
352
|
-
document.getElementById('login-sub').
|
|
542
|
+
document.getElementById('login-sub').innerHTML = t('login_sub');
|
|
353
543
|
document.getElementById('reg-title').textContent = t('reg_title');
|
|
354
544
|
document.getElementById('reg-sub').textContent = t('reg_sub');
|
|
355
545
|
document.getElementById('login-btn').textContent = t('btn_login');
|
|
356
546
|
document.getElementById('reg-btn').textContent = t('btn_register');
|
|
357
|
-
document.getElementById('no-account-text').textContent = t('no_account');
|
|
358
547
|
document.getElementById('go-register-link').textContent = t('go_register');
|
|
359
548
|
document.getElementById('have-account-text').textContent = t('have_account');
|
|
360
549
|
document.getElementById('go-login-link').textContent = t('go_login');
|
|
@@ -365,11 +554,12 @@
|
|
|
365
554
|
document.getElementById('reg-pw2').placeholder = t('ph_pw_confirm');
|
|
366
555
|
document.getElementById('reg-name').placeholder = t('ph_name');
|
|
367
556
|
document.getElementById('reg-nick').placeholder = t('ph_nick');
|
|
368
|
-
document.getElementById('lang-label').textContent = lang === 'ko' ? '한국어' : 'English';
|
|
369
557
|
document.getElementById('sso-divider-text').textContent = t('sso_divider');
|
|
370
|
-
|
|
371
|
-
document.getElementById('sso-
|
|
372
|
-
|
|
558
|
+
document.getElementById('sso-ms-label').textContent = t('ms_sso');
|
|
559
|
+
document.getElementById('sso-okta-label').textContent = t('okta_sso');
|
|
560
|
+
document.getElementById('local-start-label').textContent = t('local_start');
|
|
561
|
+
document.getElementById('help-link').textContent = t('help');
|
|
562
|
+
document.getElementById('privacy-link').textContent = t('privacy');
|
|
373
563
|
['ko', 'en'].forEach(l => {
|
|
374
564
|
const el = document.getElementById(`opt-${l}`);
|
|
375
565
|
if (el) el.classList.toggle('active', l === lang);
|
|
@@ -382,17 +572,27 @@
|
|
|
382
572
|
if (!res.ok) return;
|
|
383
573
|
const cfg = await res.json();
|
|
384
574
|
if (cfg.enabled) {
|
|
575
|
+
window._ssoEnabled = true;
|
|
385
576
|
window._ssoProviderName = cfg.provider_name;
|
|
386
|
-
document.getElementById('sso-section').style.display = '';
|
|
387
577
|
applyI18n();
|
|
388
578
|
}
|
|
389
579
|
} catch {}
|
|
390
580
|
}
|
|
391
581
|
|
|
392
|
-
function doSSOLogin() {
|
|
582
|
+
function doSSOLogin(provider) {
|
|
583
|
+
if (!window._ssoEnabled) {
|
|
584
|
+
setMsg('login-msg', t('sso_unavailable'));
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
if (provider) sessionStorage.setItem('ltcai_sso_provider_hint', provider);
|
|
393
588
|
window.location.href = '/auth/sso/login';
|
|
394
589
|
}
|
|
395
590
|
|
|
591
|
+
function togglePasswordVisibility() {
|
|
592
|
+
const input = document.getElementById('login-pw');
|
|
593
|
+
input.type = input.type === 'password' ? 'text' : 'password';
|
|
594
|
+
}
|
|
595
|
+
|
|
396
596
|
function toggleLang() {
|
|
397
597
|
const m = document.getElementById('lang-menu');
|
|
398
598
|
m.classList.toggle('open');
|
package/static/admin.html
CHANGED
|
@@ -595,10 +595,27 @@
|
|
|
595
595
|
.lang-option:hover { background: rgba(167,124,255,0.10); color: var(--text); }
|
|
596
596
|
.lang-option.active { color: var(--accent); font-weight: 600; }
|
|
597
597
|
</style>
|
|
598
|
+
<link rel="stylesheet" href="/static/lattice-reference.css">
|
|
598
599
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
599
600
|
</head>
|
|
600
601
|
|
|
601
|
-
<body>
|
|
602
|
+
<body class="lattice-ref-admin">
|
|
603
|
+
<aside class="reference-rail admin-rail">
|
|
604
|
+
<div class="rail-brand"><i class="ti ti-shield-lock"></i><strong>LATTICE AI</strong><span>Administrator</span></div>
|
|
605
|
+
<nav>
|
|
606
|
+
<a class="active" href="/admin"><i class="ti ti-layout-dashboard"></i> 대시보드</a>
|
|
607
|
+
<a href="/admin"><i class="ti ti-users"></i> 사용자 관리</a>
|
|
608
|
+
<a href="/admin"><i class="ti ti-key"></i> 권한 관리</a>
|
|
609
|
+
<a href="/admin"><i class="ti ti-lock-access"></i> SSO 관리</a>
|
|
610
|
+
<a href="/admin"><i class="ti ti-shield-check"></i> 보안 모니터링</a>
|
|
611
|
+
<a href="/admin"><i class="ti ti-report-search"></i> 감사 로그</a>
|
|
612
|
+
<a href="/chat"><i class="ti ti-message-circle"></i> 채팅으로</a>
|
|
613
|
+
</nav>
|
|
614
|
+
<div class="rail-project">
|
|
615
|
+
<strong>admin@lattice.ai</strong>
|
|
616
|
+
<span>시스템 관리자</span>
|
|
617
|
+
</div>
|
|
618
|
+
</aside>
|
|
602
619
|
<div class="page">
|
|
603
620
|
<header class="topbar">
|
|
604
621
|
<div class="brand">
|