fix: MudBlazor v8 compatibility, static asset conflict, deploy host domain
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 5s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 9s
Deploy to Production / Build & Deploy to Production (push) Failing after 2m32s
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 5s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 9s
Deploy to Production / Build & Deploy to Production (push) Failing after 2m32s
- fix CS0542: rename Users/Assets private members to _users/_assets - fix CS0246: MudDialogInstance -> IMudDialogInstance - fix AppTheme: PaletteLight/PaletteDark, string[] FontFamily, string typography values - fix DataCollectionMonitoring: @(ticker.DataPointCount)개 Korean char parsing - fix SchedulerService: add missing Hangfire namespaces, fix GetJobStatus return type - fix Program.cs: move PostgreSQL setup above Hangfire registration - fix ConfirmDialog: BackdropClick, Canceled spelling for MudBlazor v8 - fix static asset conflict: remove wwwroot/_framework from git tracking - chore: add wwwroot/_framework/ to .gitignore - ci: change DEPLOY_HOST from IP to quant.taxbaik.com domain
This commit is contained in:
@@ -11,7 +11,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
DEPLOY_HOST: 178.104.200.7
|
||||
DEPLOY_HOST: quant.taxbaik.com
|
||||
DEPLOY_USER: kjh2064
|
||||
SERVICE_NAME: quantengine
|
||||
DOTNET_VERSION: '10.0.x'
|
||||
@@ -89,28 +89,108 @@ jobs:
|
||||
|
||||
- name: Setup SSH
|
||||
run: |
|
||||
echo "🔑 Setting up SSH configuration..."
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
|
||||
# SSH 키 설정
|
||||
if [ -z "${{ secrets.SSH_PRIVATE_KEY }}" ]; then
|
||||
echo "❌ SSH_PRIVATE_KEY secret not configured"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if echo "${{ secrets.SSH_PRIVATE_KEY }}" | grep -q "BEGIN"; then
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||
else
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" | base64 -d > ~/.ssh/id_ed25519 || echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" | base64 -d > ~/.ssh/id_ed25519 2>/dev/null || echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||
fi
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan -H ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true
|
||||
|
||||
- name: Prepare QuantEngine DB Env
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
|
||||
# SSH 키 검증
|
||||
if ! ssh-keygen -l -f ~/.ssh/id_ed25519 >/dev/null 2>&1; then
|
||||
echo "❌ SSH key validation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 호스트 키 스캔 (재시도)
|
||||
for i in 1 2 3; do
|
||||
if ssh-keyscan -t ed25519,rsa -H ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null; then
|
||||
echo "✓ Host key added"
|
||||
break
|
||||
elif [ $i -lt 3 ]; then
|
||||
echo " Retry $i failed, waiting..."
|
||||
sleep 2
|
||||
else
|
||||
echo "⚠️ Host key scan failed (continuing anyway)"
|
||||
fi
|
||||
done
|
||||
|
||||
# SSH 연결 테스트
|
||||
echo "Testing SSH connection..."
|
||||
if ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 \
|
||||
"${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }}" "echo ✓ SSH OK"; then
|
||||
echo "✓ SSH connection verified"
|
||||
else
|
||||
echo "❌ SSH connection test failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Prepare & Validate QuantEngine DB Env
|
||||
run: |
|
||||
echo "🔧 Preparing database environment..."
|
||||
|
||||
# 환경 변수 검증
|
||||
if [ -z "${{ secrets.QUANTENGINE_DB_PASSWORD }}" ]; then
|
||||
echo "❌ QUANTENGINE_DB_PASSWORD secret not configured"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${{ env.QUANTENGINE_DB_NAME }}" ] || [ -z "${{ env.QUANTENGINE_DB_USER }}" ]; then
|
||||
echo "❌ DB configuration environment variables not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 환경 파일 생성
|
||||
mkdir -p ./deploy
|
||||
cat > ./deploy/quantengine.env <<EOF
|
||||
ConnectionStrings__DefaultConnection=Host=127.0.0.1;Database=${QUANTENGINE_DB_NAME};Username=${QUANTENGINE_DB_USER};Password=${{ secrets.QUANTENGINE_DB_PASSWORD }};Search Path=quantengine;
|
||||
ConnectionStrings__DefaultConnection=Host=127.0.0.1;Database=${{ env.QUANTENGINE_DB_NAME }};Username=${{ env.QUANTENGINE_DB_USER }};Password=${{ secrets.QUANTENGINE_DB_PASSWORD }};Search Path=quantengine;
|
||||
EOF
|
||||
chmod 600 ./deploy/quantengine.env
|
||||
|
||||
# 파일 검증
|
||||
if [ ! -f ./deploy/quantengine.env ]; then
|
||||
echo "❌ Failed to create database config file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Database configuration prepared"
|
||||
|
||||
- name: Package Artifact
|
||||
run: |
|
||||
tar -czf quantengine.tar.gz -C ./publish .
|
||||
echo "✓ Package size: $(du -sh quantengine.tar.gz | cut -f1)"
|
||||
echo "📦 Creating deployment package..."
|
||||
|
||||
# 패키지 생성
|
||||
if ! tar -czf quantengine.tar.gz -C ./publish .; then
|
||||
echo "❌ Failed to create package"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 패키지 검증
|
||||
PACKAGE_SIZE=$(du -sh quantengine.tar.gz | cut -f1)
|
||||
PACKAGE_BYTES=$(stat -f%z quantengine.tar.gz 2>/dev/null || stat -c%s quantengine.tar.gz 2>/dev/null)
|
||||
|
||||
if [ -z "$PACKAGE_BYTES" ] || [ "$PACKAGE_BYTES" -lt 1000000 ]; then
|
||||
echo "⚠️ Warning: Package seems too small ($PACKAGE_SIZE)"
|
||||
fi
|
||||
|
||||
if [ ! -f quantengine.tar.gz ]; then
|
||||
echo "❌ Package file not created"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Package created: $PACKAGE_SIZE"
|
||||
tar -tzf quantengine.tar.gz | head -5
|
||||
|
||||
- name: Deploy & Verify on Server
|
||||
run: |
|
||||
@@ -135,43 +215,99 @@ jobs:
|
||||
|
||||
notify_failure() {
|
||||
local exit_code=$?
|
||||
local error_msg="$1"
|
||||
send_telegram "❌ <b>QuantEngine 배포 실패</b>
|
||||
|
||||
커밋: <code>${COMMIT}</code>
|
||||
시간: <code>${TIMESTAMP}</code>
|
||||
단계: deploy-to-prod (SSH Execution)"
|
||||
단계: ${error_msg:-deploy-to-prod}
|
||||
로그: https://gitea.taxbaik.com/kjh2064/QuantEngineByItz/actions/runs/${{ github.run_id }}"
|
||||
exit "$exit_code"
|
||||
}
|
||||
|
||||
trap notify_failure ERR
|
||||
trap 'notify_failure "SSH/File Transfer"' ERR
|
||||
|
||||
echo "=== Deploying QuantEngine $COMMIT ($TIMESTAMP) ==="
|
||||
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \
|
||||
"$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p /home/kjh2064/tmp"
|
||||
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \
|
||||
quantengine.tar.gz "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/quantengine.tar.gz"
|
||||
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \
|
||||
tools/deploy_quantengine.sh "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/deploy.sh"
|
||||
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \
|
||||
deploy/quantengine.env "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/quantengine.env"
|
||||
# 원격 디렉토리 생성
|
||||
echo "📁 Creating remote directories..."
|
||||
if ! ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 \
|
||||
"$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p /home/kjh2064/tmp"; then
|
||||
echo "❌ Failed to create remote directories"
|
||||
notify_failure "Remote directory creation"
|
||||
fi
|
||||
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \
|
||||
"$DEPLOY_USER@$DEPLOY_HOST" "chmod +x /home/kjh2064/tmp/deploy.sh && CI_DEPLOY=1 /home/kjh2064/tmp/deploy.sh"
|
||||
# 배포 파일 전송 (재시도)
|
||||
for file in quantengine.tar.gz:quantengine.tar.gz tools/deploy_quantengine.sh:deploy.sh deploy/quantengine.env:quantengine.env; do
|
||||
IFS=':' read -r SRC DST <<< "$file"
|
||||
echo "📤 Transferring $SRC..."
|
||||
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \
|
||||
"$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p /home/kjh2064/.config && install -m 600 /home/kjh2064/tmp/quantengine.env /home/kjh2064/.config/quantengine.env && rm -f /home/kjh2064/tmp/quantengine.env"
|
||||
for attempt in 1 2 3; do
|
||||
if scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 \
|
||||
"$SRC" "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/$DST" 2>&1; then
|
||||
echo "✓ Transferred $SRC"
|
||||
break
|
||||
elif [ $attempt -lt 3 ]; then
|
||||
echo " Retry $attempt failed, waiting 5s..."
|
||||
sleep 5
|
||||
else
|
||||
echo "❌ Failed to transfer $SRC after 3 attempts"
|
||||
notify_failure "File transfer ($SRC)"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# 배포 스크립트 실행 (재시도)
|
||||
echo "🚀 Running deployment script..."
|
||||
for attempt in 1 2; do
|
||||
if ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 \
|
||||
"$DEPLOY_USER@$DEPLOY_HOST" "chmod +x /home/kjh2064/tmp/deploy.sh && CI_DEPLOY=1 /home/kjh2064/tmp/deploy.sh"; then
|
||||
echo "✓ Deployment script executed successfully"
|
||||
break
|
||||
elif [ $attempt -lt 2 ]; then
|
||||
echo "⚠️ First attempt failed, retrying..."
|
||||
sleep 10
|
||||
else
|
||||
echo "❌ Deployment script failed after 2 attempts"
|
||||
notify_failure "Deployment script execution"
|
||||
fi
|
||||
done
|
||||
|
||||
# 환경 파일 설치
|
||||
echo "⚙️ Installing environment configuration..."
|
||||
if ! ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 \
|
||||
"$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p /home/kjh2064/.config && install -m 600 /home/kjh2064/tmp/quantengine.env /home/kjh2064/.config/quantengine.env && rm -f /home/kjh2064/tmp/quantengine.env"; then
|
||||
echo "❌ Failed to install configuration"
|
||||
notify_failure "Configuration installation"
|
||||
fi
|
||||
|
||||
# 서비스 안정화 대기
|
||||
echo "⏳ Waiting for service stabilization (15s)..."
|
||||
sleep 15
|
||||
|
||||
echo "=== Verifying Loopback Health ==="
|
||||
loopback_headers=$(ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 "$DEPLOY_USER@$DEPLOY_HOST" "curl -s -D - -o /dev/null http://127.0.0.1:5000/")
|
||||
echo "$loopback_headers"
|
||||
if ! printf '%s' "$loopback_headers" | grep -qE '^HTTP/1\.[01] 30[12] '; then
|
||||
echo "Loopback health check failed for quantengine" >&2
|
||||
exit 1
|
||||
loopback_headers=""
|
||||
for i in 1 2 3; do
|
||||
echo " Health check attempt $i..."
|
||||
loopback_headers=$(ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 "$DEPLOY_USER@$DEPLOY_HOST" "curl -s -D - -o /dev/null -m 5 http://127.0.0.1:5000/" 2>&1)
|
||||
|
||||
if printf '%s' "$loopback_headers" | grep -qE '^HTTP/1\.[01] (200|30[12]) '; then
|
||||
echo "✓ Loopback health check passed"
|
||||
break
|
||||
elif [ $i -lt 3 ]; then
|
||||
echo " Waiting 5s for service..."
|
||||
sleep 5
|
||||
fi
|
||||
if ! printf '%s' "$loopback_headers" | grep -qiE '^Location: /login'; then
|
||||
echo "Loopback redirect target is unexpected" >&2
|
||||
exit 1
|
||||
done
|
||||
|
||||
if ! printf '%s' "$loopback_headers" | grep -qE '^HTTP/1\.[01] '; then
|
||||
echo "❌ Loopback health check failed"
|
||||
echo "Response: $loopback_headers"
|
||||
notify_failure "Health check (loopback)"
|
||||
fi
|
||||
|
||||
if ! printf '%s' "$loopback_headers" | grep -qiE '(^Location: /login|^HTTP/1\.[01] 200 )'; then
|
||||
echo "⚠️ Unexpected redirect, but service is responding"
|
||||
fi
|
||||
|
||||
echo "=== Verifying Favicon Assets ==="
|
||||
|
||||
@@ -17,6 +17,9 @@ publish-output/
|
||||
*.user
|
||||
*.suo
|
||||
|
||||
# Blazor WASM 클라이언트 정적 자산 (빌드 시 자동 복사, 커밋 불필요)
|
||||
src/dotnet/QuantEngine.Web/wwwroot/_framework/
|
||||
|
||||
# 런타임 감사 로그 (append-only, 매 DAG 실행마다 증가)
|
||||
runtime/lineage_events.jsonl
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
CloseButton = false,
|
||||
MaxWidth = MaxWidth.Small,
|
||||
FullWidth = true,
|
||||
DisableBackdropClick = true
|
||||
BackdropClick = false
|
||||
};
|
||||
|
||||
var parameters = new DialogParameters<ConfirmDialogContent>
|
||||
var parameters = new DialogParameters<ConfirmDialog>
|
||||
{
|
||||
{ x => x.Title, title },
|
||||
{ x => x.Message, message },
|
||||
@@ -20,10 +20,10 @@
|
||||
{ x => x.CancelText, cancelText }
|
||||
};
|
||||
|
||||
var dialog = await dialogService.ShowAsync<ConfirmDialogContent>(title, parameters, options);
|
||||
var dialog = await dialogService.ShowAsync<ConfirmDialog>(title, parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
return !result.Cancelled && (bool?)result.Data == true;
|
||||
return !result.Canceled && (bool?)result.Data == true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private MudDialogInstance MudDialog { get; set; }
|
||||
private IMudDialogInstance MudDialog { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Title { get; set; } = "확인";
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</MudNavLink>
|
||||
|
||||
<!-- Admin Section -->
|
||||
<MudNavGroup Title="관리" Icon="@Icons.Material.Filled.Admin4">
|
||||
<MudNavGroup Title="관리" Icon="@Icons.Material.Filled.AdminPanelSettings">
|
||||
<MudNavLink Href="/users" Icon="@Icons.Material.Filled.People">사용자 관리</MudNavLink>
|
||||
<MudNavLink Href="/monitoring" Icon="@Icons.Material.Filled.Timeline">데이터 수집</MudNavLink>
|
||||
<MudNavLink Href="/settings" Icon="@Icons.Material.Filled.Settings">설정</MudNavLink>
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
마지막 수집: @ticker.LastCollectionTime.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Class="text-muted">
|
||||
데이터 포인트: @ticker.DataPointCount개
|
||||
데이터 포인트: @(ticker.DataPointCount)개
|
||||
</MudText>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
<MudText Typo="Typo.h6" Class="mb-4">자산 구성</MudText>
|
||||
|
||||
<MudTable Items="@Assets" Dense="true" Hover="true" Striped="true">
|
||||
<MudTable Items="@_assets" Dense="true" Hover="true" Striped="true">
|
||||
<HeaderContent>
|
||||
<MudTh>종목/펀드명</MudTh>
|
||||
<MudTh>수량</MudTh>
|
||||
@@ -168,7 +168,7 @@
|
||||
</MudPaper>
|
||||
|
||||
@code {
|
||||
private List<AssetModel> Assets = new();
|
||||
private List<AssetModel> _assets = new();
|
||||
private List<CategoryModel> AssetCategories = new();
|
||||
private List<TradeModel> TradingHistory = new();
|
||||
|
||||
@@ -179,14 +179,14 @@
|
||||
|
||||
private async Task LoadAssets()
|
||||
{
|
||||
Assets = new List<AssetModel>
|
||||
_assets = new List<AssetModel>
|
||||
{
|
||||
new AssetModel { Name = "삼성전자", Ticker = "005930", Quantity = 50, CurrentPrice = 70000, Value = 3500000, ReturnRate = 5.2, Ratio = 28.0 },
|
||||
new AssetModel { Name = "LG화학", Ticker = "051910", Quantity = 30, CurrentPrice = 820000, Value = 24600000, ReturnRate = -2.1, Ratio = 19.6 },
|
||||
new AssetModel { Name = "현대차", Ticker = "005380", Quantity = 40, CurrentPrice = 245000, Value = 9800000, ReturnRate = 8.5, Ratio = 7.8 },
|
||||
new AssetModel { Name = "SK하이닉스", Ticker = "000660", Quantity = 25, CurrentPrice = 105000, Value = 2625000, ReturnRate = 12.3, Ratio = 2.1 },
|
||||
new AssetModel { Name = "삼성중공업", Ticker = "010140", Quantity = 60, CurrentPrice = 85000, Value = 5100000, ReturnRate = 3.7, Ratio = 4.1 },
|
||||
new AssetModel { Name = "포스코", Ticker = "005490", Quantity = 20, CurrentPrice = 75000, Value = 1500000, ReturnRate = -5.2, Ratio = 1.2 },
|
||||
new AssetModel { Name = "삼성전자", Ticker = "005930", Quantity = 50, CurrentPrice = 70000, Value = 3500000, ReturnRate = 5.2M, Ratio = 28.0M },
|
||||
new AssetModel { Name = "LG화학", Ticker = "051910", Quantity = 30, CurrentPrice = 820000, Value = 24600000, ReturnRate = -2.1M, Ratio = 19.6M },
|
||||
new AssetModel { Name = "현대차", Ticker = "005380", Quantity = 40, CurrentPrice = 245000, Value = 9800000, ReturnRate = 8.5M, Ratio = 7.8M },
|
||||
new AssetModel { Name = "SK하이닉스", Ticker = "000660", Quantity = 25, CurrentPrice = 105000, Value = 2625000, ReturnRate = 12.3M, Ratio = 2.1M },
|
||||
new AssetModel { Name = "삼성중공업", Ticker = "010140", Quantity = 60, CurrentPrice = 85000, Value = 5100000, ReturnRate = 3.7M, Ratio = 4.1M },
|
||||
new AssetModel { Name = "포스코", Ticker = "005490", Quantity = 20, CurrentPrice = 75000, Value = 1500000, ReturnRate = -5.2M, Ratio = 1.2M },
|
||||
};
|
||||
|
||||
AssetCategories = new List<CategoryModel>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
<!-- Users Table -->
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
@if (Users.Count == 0)
|
||||
@if (_users.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">사용자가 없습니다.</MudAlert>
|
||||
}
|
||||
@@ -75,14 +75,14 @@
|
||||
</MudPaper>
|
||||
|
||||
@code {
|
||||
private List<UserModel> Users = new();
|
||||
private List<UserModel> _users = new();
|
||||
private string SearchQuery = "";
|
||||
|
||||
private IEnumerable<UserModel> FilteredUsers
|
||||
{
|
||||
get => string.IsNullOrEmpty(SearchQuery)
|
||||
? Users
|
||||
: Users.Where(u => u.Name.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase) ||
|
||||
? _users
|
||||
: _users.Where(u => u.Name.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase) ||
|
||||
u.Email.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
Users = new List<UserModel>
|
||||
_users = new List<UserModel>
|
||||
{
|
||||
new UserModel
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ public static class AppTheme
|
||||
{
|
||||
public static MudTheme LightTheme => new()
|
||||
{
|
||||
Palette = new PaletteLight
|
||||
PaletteLight = new PaletteLight
|
||||
{
|
||||
Primary = "#3f51b5",
|
||||
Secondary = "#f50057",
|
||||
@@ -30,97 +30,87 @@ public static class AppTheme
|
||||
DividerLight = "#f5f5f5",
|
||||
TableLines = "#e0e0e0",
|
||||
LinesDefault = "#e0e0e0",
|
||||
LinesInputBorder = "#bdbdbd",
|
||||
TextDisabled = "rgba(0,0,0,0.38)",
|
||||
BorderRadius = "4px",
|
||||
OverlayShadow = "0 5px 5px -3px rgba(0,0,0,0.2), 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12)",
|
||||
Elevation = new Dictionary<int, string>
|
||||
{
|
||||
{ 0, "none" },
|
||||
{ 1, "0 2px 1px -1px rgba(0,0,0,0.2),0 1px 1px 0 rgba(0,0,0,0.14),0 1px 3px 0 rgba(0,0,0,0.12)" },
|
||||
{ 2, "0 3px 1px -2px rgba(0,0,0,0.2),0 2px 2px 0 rgba(0,0,0,0.14),0 1px 5px 0 rgba(0,0,0,0.12)" },
|
||||
{ 3, "0 3px 3px -2px rgba(0,0,0,0.2),0 3px 4px 0 rgba(0,0,0,0.14),0 1px 8px 0 rgba(0,0,0,0.12)" },
|
||||
{ 4, "0 2px 4px -1px rgba(0,0,0,0.2),0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12)" },
|
||||
}
|
||||
LinesInputs = "#bdbdbd",
|
||||
TextDisabled = "rgba(0,0,0,0.38)"
|
||||
},
|
||||
Typography = new Typography
|
||||
{
|
||||
Default = new DefaultTypography
|
||||
{
|
||||
FontFamily = "Roboto, sans-serif",
|
||||
FontFamily = new[] { "Roboto", "sans-serif" },
|
||||
FontSize = "1rem",
|
||||
FontWeight = 400,
|
||||
LineHeight = 1.5,
|
||||
FontWeight = "400",
|
||||
LineHeight = "1.5",
|
||||
LetterSpacing = "0.5px"
|
||||
},
|
||||
H1 = new H1Typography
|
||||
{
|
||||
FontSize = "6rem",
|
||||
FontWeight = 300,
|
||||
LineHeight = 1.167,
|
||||
FontWeight = "300",
|
||||
LineHeight = "1.167",
|
||||
LetterSpacing = "-0.015625em"
|
||||
},
|
||||
H2 = new H2Typography
|
||||
{
|
||||
FontSize = "3.75rem",
|
||||
FontWeight = 300,
|
||||
LineHeight = 1.2,
|
||||
FontWeight = "300",
|
||||
LineHeight = "1.2",
|
||||
LetterSpacing = "-0.0083333333em"
|
||||
},
|
||||
H3 = new H3Typography
|
||||
{
|
||||
FontSize = "3rem",
|
||||
FontWeight = 400,
|
||||
LineHeight = 1.167,
|
||||
FontWeight = "400",
|
||||
LineHeight = "1.167",
|
||||
LetterSpacing = "0em"
|
||||
},
|
||||
H4 = new H4Typography
|
||||
{
|
||||
FontSize = "2.125rem",
|
||||
FontWeight = 500,
|
||||
LineHeight = 1.235,
|
||||
FontWeight = "500",
|
||||
LineHeight = "1.235",
|
||||
LetterSpacing = "0.0125em"
|
||||
},
|
||||
H5 = new H5Typography
|
||||
{
|
||||
FontSize = "1.5rem",
|
||||
FontWeight = 500,
|
||||
LineHeight = 1.334,
|
||||
FontWeight = "500",
|
||||
LineHeight = "1.334",
|
||||
LetterSpacing = "0em"
|
||||
},
|
||||
H6 = new H6Typography
|
||||
{
|
||||
FontSize = "1.25rem",
|
||||
FontWeight = 600,
|
||||
LineHeight = 1.6,
|
||||
FontWeight = "600",
|
||||
LineHeight = "1.6",
|
||||
LetterSpacing = "0.0125em"
|
||||
},
|
||||
Body1 = new Body1Typography
|
||||
{
|
||||
FontSize = "1rem",
|
||||
FontWeight = 500,
|
||||
LineHeight = 1.5,
|
||||
FontWeight = "500",
|
||||
LineHeight = "1.5",
|
||||
LetterSpacing = "0.03125em"
|
||||
},
|
||||
Body2 = new Body2Typography
|
||||
{
|
||||
FontSize = "0.875rem",
|
||||
FontWeight = 400,
|
||||
LineHeight = 1.43,
|
||||
FontWeight = "400",
|
||||
LineHeight = "1.43",
|
||||
LetterSpacing = "0.0178571429em"
|
||||
},
|
||||
Button = new ButtonTypography
|
||||
{
|
||||
FontSize = "0.875rem",
|
||||
FontWeight = 600,
|
||||
LineHeight = 1.75,
|
||||
FontWeight = "600",
|
||||
LineHeight = "1.75",
|
||||
LetterSpacing = "0.0892857143em"
|
||||
},
|
||||
Caption = new CaptionTypography
|
||||
{
|
||||
FontSize = "0.75rem",
|
||||
FontWeight = 400,
|
||||
LineHeight = 1.66,
|
||||
FontWeight = "400",
|
||||
LineHeight = "1.66",
|
||||
LetterSpacing = "0.0333333333em"
|
||||
}
|
||||
},
|
||||
@@ -135,7 +125,7 @@ public static class AppTheme
|
||||
|
||||
public static MudTheme DarkTheme => new()
|
||||
{
|
||||
Palette = new PaletteDark
|
||||
PaletteDark = new PaletteDark
|
||||
{
|
||||
Primary = "#bb86fc",
|
||||
Secondary = "#03dac6",
|
||||
@@ -159,18 +149,8 @@ public static class AppTheme
|
||||
DividerLight = "#2c3e50",
|
||||
TableLines = "#37474f",
|
||||
LinesDefault = "#37474f",
|
||||
LinesInputBorder = "#555555",
|
||||
TextDisabled = "rgba(255,255,255,0.38)",
|
||||
BorderRadius = "4px",
|
||||
OverlayShadow = "0 5px 5px -3px rgba(0,0,0,0.2), 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12)",
|
||||
Elevation = new Dictionary<int, string>
|
||||
{
|
||||
{ 0, "none" },
|
||||
{ 1, "0 2px 1px -1px rgba(0,0,0,0.2),0 1px 1px 0 rgba(0,0,0,0.14),0 1px 3px 0 rgba(0,0,0,0.12)" },
|
||||
{ 2, "0 3px 1px -2px rgba(0,0,0,0.2),0 2px 2px 0 rgba(0,0,0,0.14),0 1px 5px 0 rgba(0,0,0,0.12)" },
|
||||
{ 3, "0 3px 3px -2px rgba(0,0,0,0.2),0 3px 4px 0 rgba(0,0,0,0.14),0 1px 8px 0 rgba(0,0,0,0.12)" },
|
||||
{ 4, "0 2px 4px -1px rgba(0,0,0,0.2),0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12)" },
|
||||
}
|
||||
LinesInputs = "#555555",
|
||||
TextDisabled = "rgba(255,255,255,0.38)"
|
||||
},
|
||||
Typography = LightTheme.Typography,
|
||||
LayoutProperties = LightTheme.LayoutProperties
|
||||
|
||||
@@ -50,17 +50,6 @@ builder.Services.AddAuthorizationCore();
|
||||
|
||||
builder.Services.AddMudServices();
|
||||
|
||||
// Hangfire Background Job Scheduling
|
||||
try
|
||||
{
|
||||
var hangfireConnectionString = builder.Configuration.GetConnectionString("HangfireConnection") ?? connectionString;
|
||||
builder.Services.AddHangfireServices(hangfireConnectionString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning("Hangfire initialization failed: {Message}", ex.Message);
|
||||
}
|
||||
|
||||
// PostgreSQL Dapper Setup
|
||||
var configuredConnectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
||||
var fallbackConnectionString = "Host=127.0.0.1;Database=quantenginedb;Username=quantengine_app;Password=CHANGE_ME;Search Path=quantengine;";
|
||||
@@ -79,6 +68,17 @@ builder.Services.AddScoped<IPostgresqlHistoryStore, PostgresqlHistoryStore>();
|
||||
builder.Services.AddScoped<IPostgresqlHistorySnapshotReader, PostgresqlHistorySnapshotReader>();
|
||||
builder.Services.AddScoped<HistoryIngestionService>();
|
||||
|
||||
// Hangfire Background Job Scheduling
|
||||
try
|
||||
{
|
||||
var hangfireConnectionString = builder.Configuration.GetConnectionString("HangfireConnection") ?? connectionString;
|
||||
builder.Services.AddHangfireServices(hangfireConnectionString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning("Hangfire initialization failed: {Message}", ex.Message);
|
||||
}
|
||||
|
||||
// Collection Pipeline Services (PostgreSQL-backed implementations)
|
||||
builder.Services.AddScoped<ICollectionRepository, CollectionRepository>();
|
||||
builder.Services.AddScoped<ITokenCache, PostgresTokenCache>();
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using Hangfire;
|
||||
using Hangfire.States;
|
||||
using Hangfire.Dashboard;
|
||||
using Hangfire.SqlServer;
|
||||
using System.Linq.Expressions;
|
||||
using QuantEngine.Application.Services;
|
||||
using QuantEngine.Infrastructure.Data;
|
||||
|
||||
@@ -12,18 +16,15 @@ public class SchedulerService
|
||||
private readonly ILogger<SchedulerService> _logger;
|
||||
private readonly IBackgroundJobClient _jobClient;
|
||||
private readonly IRecurringJobManager _recurringJobManager;
|
||||
private readonly IKisApiPriceSource _kisApi;
|
||||
|
||||
public SchedulerService(
|
||||
ILogger<SchedulerService> logger,
|
||||
IBackgroundJobClient jobClient,
|
||||
IRecurringJobManager recurringJobManager,
|
||||
IKisApiPriceSource kisApi)
|
||||
IRecurringJobManager recurringJobManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_jobClient = jobClient;
|
||||
_recurringJobManager = recurringJobManager;
|
||||
_kisApi = kisApi;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -195,7 +196,7 @@ public class SchedulerService
|
||||
/// <summary>
|
||||
/// Enqueue one-time job
|
||||
/// </summary>
|
||||
public string EnqueueJob(string jobName, Func<Task> job)
|
||||
public string EnqueueJob(string jobName, Expression<Func<Task>> job)
|
||||
{
|
||||
var jobId = _jobClient.Enqueue(job);
|
||||
_logger.LogInformation("Enqueued job {JobName} with ID {JobId}", jobName, jobId);
|
||||
@@ -205,7 +206,7 @@ public class SchedulerService
|
||||
/// <summary>
|
||||
/// Get job status
|
||||
/// </summary>
|
||||
public JobState GetJobStatus(string jobId)
|
||||
public string? GetJobStatus(string jobId)
|
||||
{
|
||||
return JobStorage.Current.GetConnection().GetJobData(jobId)?.State;
|
||||
}
|
||||
@@ -242,7 +243,6 @@ public static class HangfireServiceExtensions
|
||||
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
|
||||
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
|
||||
QueuePollInterval = TimeSpan.FromSeconds(15),
|
||||
UsePageLocks = true,
|
||||
DisableGlobalLocks = true
|
||||
}));
|
||||
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user