12 Commits

Author SHA1 Message Date
ssa
eca5eb3d3c Update .gitea/workflows/demo.yaml
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 2m36s
2026-02-19 12:20:22 +03:00
ssa
1a23706379 Update .gitea/workflows/demo.yaml
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-02-19 12:08:07 +03:00
ssa
48a16f3c83 Update .gitea/workflows/demo.yaml
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-02-19 12:07:01 +03:00
ssa
795f47cd4f Update .gitea/workflows/demo.yaml
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-02-19 11:23:50 +03:00
ssa
b496f27e86 Update .gitea/workflows/demo.yaml
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-02-19 11:21:27 +03:00
ssa
a03e0a03fd Update .gitea/workflows/demo.yaml
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 4s
2026-02-19 11:15:18 +03:00
ssa
7438147428 Update .gitea/workflows/demo.yaml
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 3s
2026-02-19 10:21:03 +03:00
ssa
47fdd36e92 Update .gitea/workflows/demo.yaml
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 42s
2026-02-19 09:36:35 +03:00
sokol
a0cd68b42b add gitea wf
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 52s
2026-02-19 08:21:28 +03:00
sokol
0c97c4421c fix: JSON validation for unquoted @placeholders@ 2026-02-19 00:14:56 +03:00
sokol
529b506612 fix: handle unquoted @placeholders@ in JSON validation 2026-02-19 00:02:43 +03:00
sokol
93e3a66252 config: change external port to 11088 2026-02-18 23:40:21 +03:00
6 changed files with 191 additions and 6 deletions

View File

@@ -0,0 +1,24 @@
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: [ubuntu-latest]
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- run: whoami
- run: hostname
- run: node -v
#- name: Check out repository code
#uses: actions/checkout@v6
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."

View File

@@ -87,8 +87,8 @@ docker-compose logs -f
After deployment, access the app at:
- **Local:** http://localhost
- **Server:** http://your-server-ip
- **Local:** http://localhost:11088
- **Server:** http://your-server-ip:11088
---

View File

@@ -10,7 +10,7 @@ services:
GIT_BRANCH: main
container_name: configucci-app
ports:
- "80:80"
- "11088:80"
restart: unless-stopped
networks:
- configucci-network

View File

@@ -119,6 +119,56 @@ test.describe('Environment Management', () => {
expect(hostPlaceholderCount).toBe(2);
});
test('should validate template with unquoted placeholders', async ({ page }) => {
await page.goto('/');
await page.click('button:has-text("Create new")');
await page.waitForTimeout(500);
// Add a parameter
await page.click('button:has-text("✚")');
await page.waitForTimeout(300);
const nameInput = page.locator('input[placeholder="name"]');
const valueInput = page.locator('input[placeholder="value"]');
const addButton = page.locator('button:has-text("✓")');
await nameInput.fill('port');
await valueInput.fill('8080');
await addButton.click();
await page.waitForTimeout(500);
// Go to Content Template and edit with unquoted placeholder
await page.click('a:has-text("Content Template")');
await page.waitForTimeout(300);
await page.click('button:has-text("Edit")');
await page.waitForTimeout(300);
// Fill template with unquoted @port@ placeholder
const textarea = page.locator('textarea');
await textarea.fill('{\n "Host": "@host@",\n "Port": @port@,\n "Url": "http://@host@:@port@/api"\n}');
await page.waitForTimeout(300);
// Check that Save button is enabled (validation passed)
const saveButton = page.locator('button:has-text("Save")');
await expect(saveButton).toBeEnabled();
// Check that there's no JSON error
const errorAlert = page.locator('.alert-danger');
await expect(errorAlert).not.toBeVisible();
// Save the template
await saveButton.click();
await page.waitForTimeout(500);
// Verify it was saved - should be in view mode with Edit button visible
const editButton = page.locator('button:has-text("Edit")');
await expect(editButton).toBeVisible();
// Verify the template content is displayed correctly
const codeContent = page.locator('code');
await expect(codeContent).toBeVisible();
const content = await codeContent.textContent();
expect(content).toContain('@port@');
});
test('should download config file with correct filename', async ({ page }) => {
await page.goto('/');
await page.click('button:has-text("Create new")');

View File

@@ -35,9 +35,10 @@ export function ConfigTemplate(props: ConfigTemplateProps) {
}
function handleSave() {
// Validate JSON before saving
// Validate JSON before saving (with placeholder support)
try {
JSON.parse(draftContent);
const sanitizedValue = draftContent.replace(/@[^@]+@/g, '1');
JSON.parse(sanitizedValue);
setJsonError(null);
props.onSaved(draftContent);
setMode('view');
@@ -52,7 +53,9 @@ export function ConfigTemplate(props: ConfigTemplateProps) {
try {
if (value.trim()) {
// Replace @placeholders@ with valid JSON values for validation
const sanitizedValue = value.replace(/"@?(\w+)@?"/g, '"__PLACEHOLDER__"');
// Strategy: Replace ALL @...@ patterns with "1" (valid for both string and numeric contexts)
const sanitizedValue = value.replace(/@[^@]+@/g, '1');
JSON.parse(sanitizedValue);
setJsonError(null);
} else {

View File

@@ -0,0 +1,108 @@
import { describe, it, expect } from 'vitest';
describe('JSON Validation with @placeholders@', () => {
/**
* Helper function that mimics the validation logic in ConfigTemplate.tsx
*/
function validateJsonWithPlaceholders(value: string): { valid: boolean; error?: string } {
try {
if (!value.trim()) {
return { valid: true };
}
// This is the current implementation from ConfigTemplate.tsx
// Replace ALL @...@ patterns with "1" (valid for both string and numeric contexts)
const sanitizedValue = value.replace(/@[^@]+@/g, '1');
JSON.parse(sanitizedValue);
return { valid: true };
} catch (e) {
return { valid: false, error: (e as Error).message };
}
}
it('should validate quoted placeholders', () => {
const json = `{
"Host": "@host@",
"Port": "@port@"
}`;
const result = validateJsonWithPlaceholders(json);
expect(result.valid).toBe(true);
});
it('should validate unquoted placeholders', () => {
const json = `{
"Host": "@host@",
"Port": @port@
}`;
const result = validateJsonWithPlaceholders(json);
expect(result.valid).toBe(true);
});
it('should validate mixed quoted and unquoted placeholders', () => {
const json = `{
"Host": "@host@",
"Port": @port@,
"ApiPath": "@host@:@port@/v1/data",
"MessageBroker": {
"hosts": @MessageBrokerHosts@
}
}`;
const result = validateJsonWithPlaceholders(json);
expect(result.valid).toBe(true);
});
it('should validate placeholders inside strings (URLs)', () => {
const json = `{
"ApiUrl": "http://@host@:@port@/api"
}`;
const result = validateJsonWithPlaceholders(json);
expect(result.valid).toBe(true);
});
it('should validate complex real-world template', () => {
const json = `{
"Host": "@host@",
"Port": @port@,
"ApiPath": "@host@:@port@/v1/data",
"MessageBroker": {
"hosts": @MessageBrokerHosts@
},
"basePath": "./@env_name@/in",
"NoParam": "@no_param@"
}`;
const result = validateJsonWithPlaceholders(json);
expect(result.valid).toBe(true);
});
it('should reject invalid JSON structure', () => {
const json = `{
"Host": "@host@",
"Port": @port@
"Missing": "comma"
}`;
const result = validateJsonWithPlaceholders(json);
expect(result.valid).toBe(false);
expect(result.error).toContain('Expected');
});
it('should handle empty value', () => {
const result = validateJsonWithPlaceholders('');
expect(result.valid).toBe(true);
});
it('should handle whitespace only', () => {
const result = validateJsonWithPlaceholders(' \n\t ');
expect(result.valid).toBe(true);
});
});