From c3dbbead40178ea25ddc820bea3b1d44ced005bb Mon Sep 17 00:00:00 2001 From: Bohdan Krokhmal Date: Tue, 29 Apr 2025 19:12:53 +0200 Subject: [PATCH] sk1 --- sk1/Dockerfile | 8 ++ sk1/README.md | 149 ++++++++++++++++++++++++++++++++ sk1/app/cSyntaxChecker.js | 72 ++++++++++++++++ sk1/app/index.html | 48 +++++++++++ sk1/app/janeParser.js | 47 ++++++++++ sk1/app/javaSyntaxChecker.js | 57 +++++++++++++ sk1/app/pythonSyntaxChecker.js | 40 +++++++++ sk1/app/script.js | 145 +++++++++++++++++++++++++++++++ sk1/app/style.css | 152 +++++++++++++++++++++++++++++++++ sk1/docker-compose.yml | 10 +++ sk1/nginx/nginx.conf | 10 +++ sk1/prepare-app.sh | 6 ++ sk1/remove-app.sh | 5 ++ sk1/restart-app-service.sh | 10 +++ sk1/start-app-service.sh | 10 +++ sk1/stop-app-service.sh | 10 +++ 16 files changed, 779 insertions(+) create mode 100644 sk1/Dockerfile create mode 100644 sk1/README.md create mode 100644 sk1/app/cSyntaxChecker.js create mode 100644 sk1/app/index.html create mode 100644 sk1/app/janeParser.js create mode 100644 sk1/app/javaSyntaxChecker.js create mode 100644 sk1/app/pythonSyntaxChecker.js create mode 100644 sk1/app/script.js create mode 100644 sk1/app/style.css create mode 100644 sk1/docker-compose.yml create mode 100644 sk1/nginx/nginx.conf create mode 100755 sk1/prepare-app.sh create mode 100755 sk1/remove-app.sh create mode 100755 sk1/restart-app-service.sh create mode 100755 sk1/start-app-service.sh create mode 100755 sk1/stop-app-service.sh diff --git a/sk1/Dockerfile b/sk1/Dockerfile new file mode 100644 index 0000000..bd91084 --- /dev/null +++ b/sk1/Dockerfile @@ -0,0 +1,8 @@ +FROM nginx:alpine + +COPY ./app /usr/share/nginx/html + +COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 +EXPOSE 443 diff --git a/sk1/README.md b/sk1/README.md new file mode 100644 index 0000000..478621f --- /dev/null +++ b/sk1/README.md @@ -0,0 +1,149 @@ +# Abstract Machine - Syntax Checker and Translator + +## **Project Description** +This web application is a syntax checker and code translator for multiple programming languages (Jane, C, Python, Java). It allows users to: +- **Check syntax errors** in their code. +- **Get AI-powered suggestions** to fix errors. +- **Translate code** between languages (Jane → C, Python, Java). + +The application is deployed on **Azure** using Docker containers and Nginx as a reverse proxy. + +--- + +## **Technical Implementation** + +### **Cloud & Deployment** +- **Public Cloud:** Microsoft Azure +- **Services Used:** + - **Azure App Service** (for hosting the web app). + - **Docker** (containerization). + - **Nginx** (web server and reverse proxy). + +### **Docker Compose** +- **Docker Containers:** + - `nginx:alpine` (serves the static frontend). +- **Volumes:** + - Persistent storage for app files (`./app:/usr/share/nginx/html`). + - Nginx configuration (`./nginx:/etc/nginx/conf.d`). +- **Auto-Restart Policy:** + - `restart: unless-stopped` (ensures the app restarts on failure). + +### **Application Components** +1. **Frontend:** + - HTML/CSS/JavaScript (static files served by Nginx). +2. **Backend:** + - Syntax checkers (`janeParser.js`, `cSyntaxChecker.js`, `pythonSyntaxChecker.js`, `javaSyntaxChecker.js`). + - AI error helper (`script.js`). + - Code translator (`script.js`). + +### **HTTPS & Security** +- The app is accessible via **HTTPS** (certificate managed by Azure). +- Nginx ensures proper routing and security. + +--- + +## **Files & Configuration** + +### **Key Files** +| File | Description | +|----------------------|----------------------------------------------------------------------| +| `prepare-app.sh` | Starts the Docker container. | +| `remove-app.sh` | Stops and removes the container. | +| `Dockerfile` | Builds the Nginx-based Docker image. | +| `docker-compose.yml` | Defines the container setup (ports, volumes, restart policy). | +| `nginx/nginx.conf` | Configures Nginx routing. | +| `app/` | Contains all frontend files (`index.html`, `style.css`, JS scripts). | + +### **Scripts** +Azure Management Scripts + + * start-app-service.sh +Starts the Azure App Service instance +# Authenticates with Azure and sends start request +# Requires Azure CLI login and proper permissions + + * stop-app-service.sh +Stops the Azure App Service instance +# Authenticates with Azure and sends stop request +# Preserves all application data + + * restart-app-service.sh +Restarts the Azure App Service instance +# Performs full restart of the Azure service +# Useful for applying configuration changes + +Application Lifecycle Scripts + * prepare-app.sh + Full application deployment sequence: + + 1)Starts Docker containers (docker-compose up -d) + + 2)Triggers Azure App Service startup (start-app-service.sh) + + 3)Outputs deployment URL +> The application has started at https://sk1bk-edgvcnfsfxfsegg2.westeurope-01.azurewebsites.net/ + + * remove-app.sh + Complete application teardown sequence: + + 1)Stops Azure App Service (stop-app-service.sh) + + 2)Removes all Docker containers and volumes (docker-compose down -v) + +Implementation Notes + +1)All Azure management scripts: + + - Require authenticated Azure CLI session + + - Use the same subscription/resource group context + + - Include proper API versioning (2022-03-01) + + - Handle empty request bodies with Content-Length: 0 + +2)Composite scripts (prepare-app.sh, remove-app.sh): + + - Combine container and cloud resource management + + - Maintain execution order dependencies + + - Provide user feedback via console output + +3)Security: + + - Access tokens are obtained fresh for each execution + + - No long-term credential storage + + - Requires Contributor-level permissions on target resources + + +--- + +## **How to Use the Application** +1. **Access the Web App:** + - Open: [https://sk1bk-edgvcnfsfxfsegg2.westeurope-01.azurewebsites.net/](https://sk1bk-edgvcnfsfxfsegg2.westeurope-01.azurewebsites.net/) +2. **Check Syntax:** + - Select a language (Jane, C, Python, Java). + - Paste your code into the text area. + - Click **"Start analysis"**. +3. **Get AI Help:** + - After analysis, click **"AI Help"** for error explanations. +4. **Translate Code:** + - If the code is error-free, select a target language and click **"Translate"**. + +--- + +## **Requirements for Running Scripts** +- **`prepare-app.sh` and `remove-app.sh` require:** + - Docker and Docker Compose installed. + - Azure CLI (if redeploying to Azure). + - Internet access (to pull Docker images). + +--- + +## **External Sources** +- **No third-party APIs** were used (all logic is custom). + +--- diff --git a/sk1/app/cSyntaxChecker.js b/sk1/app/cSyntaxChecker.js new file mode 100644 index 0000000..253fae9 --- /dev/null +++ b/sk1/app/cSyntaxChecker.js @@ -0,0 +1,72 @@ +function checkCSyntax(code) { + let errors = []; + let lines = code.split("\n"); + let openBraces = 0; + + if (!code.match(/^\s*#include\s+\s*$/m)) { + errors.push("Error: Missing or incorrect #include directive."); + } + + if (!code.match(/int\s+main\s*\(.*\)\s*\{/)) { + errors.push("Error: Missing main() function."); + } + + if (!code.match(/return\s+0\s*;/)) { + errors.push("Error: Missing 'return 0;' in main()."); + } + + let keywords = ["if", "else", "while", "for", "return", "int", "char", "float", "double", "void"]; + let variableDeclarationRegex = /^\s*(int|char|float|double)\s+[a-zA-Z_][a-zA-Z0-9_]*\s*(=\s*[^;]+)?\s*;/; + let functionCallRegex = /^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*\(.*\)\s*;/; + let operatorRegex = /^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*(\+\+|--|\+=|-=|\*=|\/=|%=)?\s*;/; + let controlFlowRegex = /^\s*(if|while|for)\s*\(.*\)\s*\{/; + + for (let i = 0; i < lines.length; i++) { + let line = lines[i].trim(); + if (!line) continue; + + if (line.includes("{")) openBraces++; + if (line.includes("}")) openBraces--; + + let words = line.split(/\s+/); + for (let word of words) { + if (keywords.includes(word)) continue; + if (word.match(/^(if|else|while|for|return)$/i)) { + errors.push(`Error on line ${i + 1}: Misspelled keyword '${word}'. Did you mean '${word.toLowerCase()}'?`); + } + } + + if (variableDeclarationRegex.test(line)) { + continue; + } + + if (functionCallRegex.test(line)) { + continue; + } + + if (line.match(/[a-zA-Z_][a-zA-Z0-9_]*\s*-+\s*;/)) { + let incorrectOperator = line.match(/[a-zA-Z_][a-zA-Z0-9_]*\s*(-+)\s*;/)[1]; + if (incorrectOperator !== "--") { + errors.push(`Error on line ${i + 1}: Incorrect operator '${line.trim()}'. Did you mean '${line.trim().replace(/-+;/, "--;")}'?`); + } + } + + if (operatorRegex.test(line)) { + continue; + } + + if (controlFlowRegex.test(line)) { + continue; + } + + if (!line.endsWith(";") && !line.includes("{") && !line.includes("}") && !line.startsWith("#")) { + errors.push(`Error on line ${i + 1}: Missing semicolon.`); + } + } + + if (openBraces !== 0) { + errors.push("Error: Mismatched braces `{}`."); + } + + return errors.length ? errors : ["Syntax is correct!"]; +} diff --git a/sk1/app/index.html b/sk1/app/index.html new file mode 100644 index 0000000..efc0389 --- /dev/null +++ b/sk1/app/index.html @@ -0,0 +1,48 @@ + + + + + + Abstract Machine + + + + +

Abstract Machine

+ +
+ + +
+ + + +

+
+
+

+
+
+ + + +
+

+
+
+
+
+
+
+
+
+
diff --git a/sk1/app/janeParser.js b/sk1/app/janeParser.js
new file mode 100644
index 0000000..4b56e3b
--- /dev/null
+++ b/sk1/app/janeParser.js
@@ -0,0 +1,47 @@
+function checkJaneSyntax(code) {
+    let errors = [];
+    let lines = code.split("\n");
+    let variables = new Set();
+
+    let assignmentRegex = /^\s*var\s+([a-zA-Z][a-zA-Z0-9]*)\s*;?\s*$/;
+    let valueAssignmentRegex = /^\s*([a-zA-Z][a-zA-Z0-9]*)\s*:=\s*[0-9a-zA-Z+\-*\/]+\s*;?\s*$/;
+    let ifRegex = /^\s*if\s*\(.+\)\s*then\s*\{.*\}\s*;?\s*$/;
+    let whileRegex = /^\s*while\s*\(.+\)\s*do\s*\{.*\}\s*;?\s*$/;
+
+    for (let i = 0; i < lines.length; i++) {
+        let line = lines[i].trim();
+        if (!line) continue;
+
+        if (assignmentRegex.test(line)) {
+            let varName = line.match(assignmentRegex)[1];
+            if (variables.has(varName)) {
+                errors.push(`Error on line ${i + 1}: variable '${varName}' is already declared.`);
+            } else {
+                variables.add(varName);
+            }
+            if (!line.endsWith(";")) {
+                errors.push(`Error on line ${i + 1}: Missing semicolon at the end of the line.`);
+            }
+        } else if (valueAssignmentRegex.test(line)) {
+            let varName = line.match(valueAssignmentRegex)[1];
+            if (!variables.has(varName)) {
+                errors.push(`Error on line ${i + 1}: variable '${varName}' is not declared.`);
+            }
+            if (!line.endsWith(";")) {
+                errors.push(`Error on line ${i + 1}: Missing semicolon at the end of the line.`);
+            }
+        } else if (ifRegex.test(line)) {
+            if (!line.endsWith(";")) {
+                errors.push(`Error on line ${i + 1}: Missing semicolon at the end of the line.`);
+            }
+        } else if (whileRegex.test(line)) {
+            if (!line.endsWith(";")) {
+                errors.push(`Error on line ${i + 1}: Missing semicolon at the end of the line.`);
+            }
+        } else {
+            errors.push(`Error on line ${i + 1}: invalid syntax.`);
+        }
+    }
+
+    return errors.length ? errors : ["Syntax is correct!"];
+}
diff --git a/sk1/app/javaSyntaxChecker.js b/sk1/app/javaSyntaxChecker.js
new file mode 100644
index 0000000..bf852ca
--- /dev/null
+++ b/sk1/app/javaSyntaxChecker.js
@@ -0,0 +1,57 @@
+function checkJavaSyntax(code) {
+    let errors = [];
+    let lines = code.split("\n");
+    let openBraces = 0;
+
+    if (!code.match(/public\s+class\s+\w+\s*\{/)) {
+        errors.push("Error: Missing or incorrect class definition.");
+    }
+
+    if (!code.match(/public\s+static\s+void\s+main\s*\(String\[\]\s+args\)\s*\{/)) {
+        errors.push("Error: Missing or incorrect main method.");
+    }
+
+    let keywords = ["if", "else", "while", "for", "return", "int", "char", "float", "double", "void", "public", "static", "class"];
+
+    for (let i = 0; i < lines.length; i++) {
+        let line = lines[i].trim();
+        if (!line) continue;
+
+        if (line.includes("{")) openBraces++;
+        if (line.includes("}")) openBraces--;
+
+        let words = line.split(/\s+/);
+        for (let word of words) {
+            if (keywords.includes(word)) continue;
+            if (word.match(/^(if|else|while|for|return|public|static|class)$/i)) {
+                errors.push(`Error on line ${i + 1}: Misspelled keyword '${word}'. Did you mean '${word.toLowerCase()}'?`);
+            }
+        }
+
+        if (line.includes("x-;")) {
+            errors.push(`Error on line ${i + 1}: Incorrect operator 'x-;'. Did you mean 'x--;'?`);
+        }
+
+        if (!line.endsWith(";") && !line.includes("{") && !line.includes("}")) {
+            errors.push(`Error on line ${i + 1}: Missing semicolon.`);
+        }
+
+        if (line.includes("System.out.println") && !line.match(/System\.out\.println\(["'].*["']\)\s*;/)) {
+            errors.push(`Error on line ${i + 1}: Incorrect System.out.println syntax.`);
+        }
+
+        if (line.includes("System.out.pritln")) {
+            errors.push(`Error on line ${i + 1}: Misspelled 'System.out.println'. Did you mean 'System.out.println'?`);
+        }
+
+        if (line.includes('System.out.println("Done)')) {
+            errors.push(`Error on line ${i + 1}: Missing closing quote in System.out.println.`);
+        }
+    }
+
+    if (openBraces !== 0) {
+        errors.push("Error: Mismatched braces `{}`.");
+    }
+
+    return errors.length ? errors : ["Syntax is correct!"];
+}
diff --git a/sk1/app/pythonSyntaxChecker.js b/sk1/app/pythonSyntaxChecker.js
new file mode 100644
index 0000000..5e30567
--- /dev/null
+++ b/sk1/app/pythonSyntaxChecker.js
@@ -0,0 +1,40 @@
+function checkPythonSyntax(code) {
+    let errors = [];
+    let lines = code.split("\n");
+    let keywords = ["if", "else", "while", "for", "def", "return", "print"];
+
+    for (let i = 0; i < lines.length; i++) {
+        let line = lines[i].trim();
+        if (!line) continue;
+
+        let words = line.split(/\s+/);
+        for (let word of words) {
+            if (keywords.includes(word)) continue;
+            if (word.match(/^(if|else|while|for|def|return|print)$/i)) {
+                errors.push(`Error on line ${i + 1}: Misspelled keyword '${word}'. Did you mean '${word.toLowerCase()}'?`);
+            }
+        }
+
+        if (line.startsWith("if ") && !line.includes(":")) {
+            errors.push(`Error on line ${i + 1}: Missing ':' after 'if'.`);
+        }
+
+        if (line.startsWith("while ") && !line.includes(":")) {
+            errors.push(`Error on line ${i + 1}: Missing ':' after 'while'.`);
+        }
+
+        if (line.startsWith("for ") && !line.includes(":")) {
+            errors.push(`Error on line ${i + 1}: Missing ':' after 'for'.`);
+        }
+
+        if (line.startsWith("def ") && !line.includes(":")) {
+            errors.push(`Error on line ${i + 1}: Missing ':' after 'def'.`);
+        }
+
+        if (line.startsWith("print(") && !line.match(/print\(["'].*["']\)/)) {
+            errors.push(`Error on line ${i + 1}: Strings in print() must be enclosed in quotes.`);
+        }
+    }
+
+    return errors.length ? errors : ["Syntax is correct!"];
+}
diff --git a/sk1/app/script.js b/sk1/app/script.js
new file mode 100644
index 0000000..96bc641
--- /dev/null
+++ b/sk1/app/script.js
@@ -0,0 +1,145 @@
+document.addEventListener("DOMContentLoaded", function () {
+    const analyzeBtn = document.getElementById("analyzeBtn");
+    const aiHelpBtn = document.getElementById("aiHelpBtn");
+    const translateBtn = document.getElementById("translateBtn");
+    const output = document.getElementById("output");
+    const aiHelpOutput = document.getElementById("aiHelpOutput");
+    const translateOutput = document.getElementById("translateOutput");
+    const languageSelect = document.getElementById("languageSelect");
+    const translateContainer = document.querySelector(".translate-container");
+
+    let lastErrors = [];
+
+    aiHelpBtn.disabled = true;
+    translateBtn.disabled = true;
+
+    function toggleTranslateVisibility() {
+        if (languageSelect.value === "jane") {
+            translateContainer.style.display = "flex";
+            translateOutput.style.display = "block";
+        } else {
+            translateContainer.style.display = "none";
+            translateOutput.style.display = "none";
+        }
+    }
+
+    toggleTranslateVisibility();
+
+    languageSelect.addEventListener("change", toggleTranslateVisibility);
+
+    analyzeBtn.addEventListener("click", function () {
+        let code = document.getElementById("codeInput").value;
+        let language = languageSelect.value;
+
+        let result = [];
+
+        try {
+            let checker;
+            switch (language) {
+                case "jane":
+                    checker = checkJaneSyntax;
+                    break;
+                case "c":
+                    checker = checkCSyntax;
+                    break;
+                case "python":
+                    checker = checkPythonSyntax;
+                    break;
+                case "java":
+                    checker = checkJavaSyntax;
+                    break;
+                default:
+                    result.push("Unknown language selected");
+            }
+
+            if (checker) {
+                result = checker(code);
+            }
+        } catch (error) {
+            result.push("Error: " + error.message);
+            console.error(error);
+        }
+
+        if (result.length === 0 || result[0] === "Syntax is correct!") {
+            result = ["No errors found!"];
+        }
+
+        output.innerText = result.join("\n");
+        lastErrors = result;
+
+        aiHelpBtn.disabled = false;
+        translateBtn.disabled = false;
+    });
+
+    aiHelpBtn.addEventListener("click", function () {
+        aiHelpOutput.innerText = "";
+
+        if (lastErrors.length === 0 || lastErrors[0] === "No errors found!") {
+            typeText("There are no errors here, keep up the good work! If you encounter any errors in your new job, I will be happy to help you fix them.", aiHelpOutput);
+            return;
+        }
+
+        let helpText = [];
+
+        lastErrors.forEach(error => {
+            if (error.includes("Missing semicolon")) {
+                helpText.push("Error: Missing semicolon.\nFix: Add a semicolon at the end of the line.");
+            } else if (error.includes("Incorrect operator")) {
+                helpText.push("Error: Incorrect operator.\nFix: Use the correct operator, such as 'x++' or 'x--'.");
+            } else if (error.includes("Misspelled keyword")) {
+                helpText.push("Error: Misspelled keyword.\nFix: Check the spelling of the keyword.");
+            } else if (error.includes("Missing ':' after")) {
+                helpText.push("Error: Missing colon.\nFix: Add a colon after the keyword.");
+            } else {
+                helpText.push(`Error: ${error}\nFix: Review the syntax and correct the error.`);
+            }
+        });
+
+        typeText(helpText.join("\n\n"), aiHelpOutput);
+    });
+
+    translateBtn.addEventListener("click", function () {
+        if (lastErrors.length > 0 && lastErrors[0] !== "No errors found!") {
+            translateOutput.innerText = "It's impossible to translate incorrect code, fix the errors first.";
+            return;
+        }
+
+        let code = document.getElementById("codeInput").value;
+        let targetLanguage = document.getElementById("translateLanguageSelect").value;
+        let translatedCode = translateJaneToLanguage(code, targetLanguage);
+        translateOutput.innerText = translatedCode;
+    });
+
+    function typeText(text, element, speed = 30) {
+        let i = 0;
+        element.innerText = "";
+        function typing() {
+            if (i < text.length) {
+                element.innerText += text.charAt(i);
+                i++;
+                setTimeout(typing, speed);
+            }
+        }
+        typing();
+    }
+
+    function translateJaneToLanguage(code, targetLanguage) {
+        switch (targetLanguage) {
+            case "c":
+                return `// Translated to C\n#include \n\nint main() {\n    ${code.replace(/:=/g, "=")}\n    return 0;\n}`;
+            case "python":
+                let pythonCode = code
+                    .replace(/:=/g, "=")
+                    .replace(/;\n/g, "\n")
+                    .replace(/\bthen\b/g, "")
+                    .replace(/\bdo\b/g, "")
+                    .replace(/{/g, ":")
+                    .replace(/}/g, "");
+                return `# Translated to Python\n${pythonCode}`;
+            case "java":
+                return `// Translated to Java\npublic class Main {\n    public static void main(String[] args) {\n        ${code.replace(/:=/g, "=")}\n    }\n}`;
+            default:
+                return "Translation not supported for this language.";
+        }
+    }
+});
diff --git a/sk1/app/style.css b/sk1/app/style.css
new file mode 100644
index 0000000..b96dd04
--- /dev/null
+++ b/sk1/app/style.css
@@ -0,0 +1,152 @@
+body {
+    background-color: #1E1E1E;
+    color: white;
+    font-family: Arial, sans-serif;
+    text-align: center;
+}
+
+h1 {
+    margin-top: 20px;
+    font-size: 24px;
+}
+
+.container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    gap: 10px;
+}
+
+.lang-select-container {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    width: 80%;
+    justify-content: left;
+}
+
+label {
+    font-size: 18px;
+}
+
+select {
+    padding: 5px;
+    font-size: 16px;
+    border-radius: 5px;
+    background-color: #2D2B5A;
+    color: white;
+    border: none;
+    cursor: pointer;
+}
+
+textarea, pre {
+    width: 80%;
+    height: 150px;
+    background-color: #5E5E61;
+    color: white;
+    border: none;
+    border-radius: 10px;
+    padding: 10px;
+    font-size: 16px;
+    resize: none;
+    margin-top: 10px;
+    display: block;
+}
+
+textarea::placeholder {
+    color: rgba(255, 255, 255, 0.5);
+    font-style: italic;
+}
+
+button {
+    background-color: #2D2B5A;
+    color: white;
+    font-size: 16px;
+    padding: 10px 20px;
+    border: none;
+    border-radius: 10px;
+    cursor: pointer;
+    margin-top: 10px;
+}
+
+button:hover {
+    background-color: #3A3769;
+}
+
+pre {
+    width: 80%;
+    height: 150px;
+    background-color: #5E5E61;
+    color: white;
+    border: none;
+    border-radius: 10px;
+    padding: 10px;
+    font-size: 16px;
+    text-align: left;
+    overflow-y: auto;
+    display: flex;
+    align-items: center;
+    justify-content: left;
+    white-space: pre-wrap;
+}
+
+#aiHelpBtn {
+    background-color: #4CAF50;
+    color: white;
+    font-size: 16px;
+    padding: 10px 20px;
+    border: none;
+    border-radius: 10px;
+    cursor: pointer;
+    margin-top: 10px;
+}
+
+#aiHelpBtn:disabled {
+    background-color: #cccccc;
+    cursor: not-allowed;
+}
+
+#aiHelpOutput {
+    width: 80%;
+    background-color: #5E5E61;
+    color: white;
+    border: none;
+    border-radius: 10px;
+    padding: 10px;
+    font-size: 16px;
+    text-align: left;
+    overflow-y: auto;
+    margin-top: 10px;
+}
+
+.translate-container {
+    display: flex;
+    align-items: flex-end;
+    gap: 10px;
+    margin-top: 20px;
+}
+
+#translateBtn {
+    background-color: #2D2B5A;
+    color: white;
+    font-size: 16px;
+    padding: 6px 20px;
+    border: none;
+    border-radius: 10px;
+    cursor: pointer;
+    margin-top: 10px;
+}
+
+#translateOutput {
+    width: 80%;
+    background-color: #5E5E61;
+    color: white;
+    border: none;
+    border-radius: 10px;
+    padding: 10px;
+    font-size: 16px;
+    text-align: left;
+    overflow-y: auto;
+    margin-top: 10px;
+}
diff --git a/sk1/docker-compose.yml b/sk1/docker-compose.yml
new file mode 100644
index 0000000..62cca7b
--- /dev/null
+++ b/sk1/docker-compose.yml
@@ -0,0 +1,10 @@
+services:
+  web:
+    build: .
+    restart: unless-stopped
+    ports:
+      - "80:80"
+      - "443:443"
+    volumes:
+      - ./app:/usr/share/nginx/html
+      - ./nginx:/etc/nginx/conf.d
diff --git a/sk1/nginx/nginx.conf b/sk1/nginx/nginx.conf
new file mode 100644
index 0000000..3c25a98
--- /dev/null
+++ b/sk1/nginx/nginx.conf
@@ -0,0 +1,10 @@
+server {
+    listen 80;
+    server_name _;
+    root /usr/share/nginx/html;
+    index index.html;
+
+    location / {
+        try_files $uri $uri/ /index.html;
+    }
+}
diff --git a/sk1/prepare-app.sh b/sk1/prepare-app.sh
new file mode 100755
index 0000000..121f3e9
--- /dev/null
+++ b/sk1/prepare-app.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+docker-compose up -d web
+
+./start-app-service.sh
+echo "The application has started at https://sk1bk-edgvcnfsfxfsegg2.westeurope-01.azurewebsites.net/"
diff --git a/sk1/remove-app.sh b/sk1/remove-app.sh
new file mode 100755
index 0000000..a0759ed
--- /dev/null
+++ b/sk1/remove-app.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+./stop-app-service.sh
+docker-compose down -v
+
diff --git a/sk1/restart-app-service.sh b/sk1/restart-app-service.sh
new file mode 100755
index 0000000..eb8ea1d
--- /dev/null
+++ b/sk1/restart-app-service.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/bash
+
+AZURE_ACCESS_TOKEN=$(az account get-access-token --query accessToken -o tsv)
+curl -X POST \
+"https://management.azure.com/subscriptions/a2e2b55c-a568-402b-a953-5b7337e49473/resourceGroups/sk1bk/providers/Microsoft.Web/sites/sk1bk/restart?api-version=2022-03-01" \
+-H "Authorization: Bearer $AZURE_ACCESS_TOKEN" \
+-H "Content-Length: 0" \
+-H "Content-Type: application/json" \
+-d ""
+
diff --git a/sk1/start-app-service.sh b/sk1/start-app-service.sh
new file mode 100755
index 0000000..11e305d
--- /dev/null
+++ b/sk1/start-app-service.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/bash
+
+AZURE_ACCESS_TOKEN=$(az account get-access-token --query accessToken -o tsv)
+curl -X POST \
+"https://management.azure.com/subscriptions/a2e2b55c-a568-402b-a953-5b7337e49473/resourceGroups/sk1bk/providers/Microsoft.Web/sites/sk1bk/start?api-version=2022-03-01" \
+-H "Authorization: Bearer $AZURE_ACCESS_TOKEN" \
+-H "Content-Length: 0" \
+-H "Content-Type: application/json" \
+-d ""
+
diff --git a/sk1/stop-app-service.sh b/sk1/stop-app-service.sh
new file mode 100755
index 0000000..af92aec
--- /dev/null
+++ b/sk1/stop-app-service.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/bash
+
+AZURE_ACCESS_TOKEN=$(az account get-access-token --query accessToken -o tsv)
+curl -X POST \
+"https://management.azure.com/subscriptions/a2e2b55c-a568-402b-a953-5b7337e49473/resourceGroups/sk1bk/providers/Microsoft.Web/sites/sk1bk/stop?api-version=2022-03-01" \
+-H "Authorization: Bearer $AZURE_ACCESS_TOKEN" \
+-H "Content-Length: 0" \
+-H "Content-Type: application/json" \
+-d ""
+