mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-11-10 21:21:13 +08:00
feat: Add support for importing environment variables into the runtime environment. (#13727)
This commit is contained in:
1 parent
a47e41a8b7
commit
50a54d0613
15 files changed
+168
-21
No files matched your search
@@ -838,21 +838,49 @@ func restartRuntime(runtime *model.Runtime) (err error) {
|
||||
}
|
||||
|
||||
func getDockerComposeEnvironments(yml []byte) ([]request.Environment, error) {
|
||||
var (
|
||||
composeProject docker.ComposeProject
|
||||
err error
|
||||
)
|
||||
err = yaml.Unmarshal(yml, &composeProject)
|
||||
if err != nil {
|
||||
var project struct {
|
||||
Services yaml.Node `yaml:"services"`
|
||||
}
|
||||
if err := yaml.Unmarshal(yml, &project); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services := &project.Services
|
||||
if services.Kind == yaml.AliasNode {
|
||||
services = services.Alias
|
||||
}
|
||||
if services.Kind != 0 && services.Kind != yaml.MappingNode {
|
||||
return nil, fmt.Errorf("unsupported services format")
|
||||
}
|
||||
var res []request.Environment
|
||||
for _, service := range composeProject.Services {
|
||||
for key, value := range service.Environment.Variables {
|
||||
res = append(res, request.Environment{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
// Keep the file order for both services and environment entries.
|
||||
for i := 1; i < len(services.Content); i += 2 {
|
||||
var service struct {
|
||||
Environment yaml.Node `yaml:"environment"`
|
||||
}
|
||||
if err := services.Content[i].Decode(&service); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
environment := &service.Environment
|
||||
if environment.Kind == yaml.AliasNode {
|
||||
environment = environment.Alias
|
||||
}
|
||||
switch environment.Kind {
|
||||
case yaml.MappingNode:
|
||||
for j := 0; j < len(environment.Content); j += 2 {
|
||||
res = append(res, request.Environment{Key: environment.Content[j].Value, Value: environment.Content[j+1].Value})
|
||||
}
|
||||
case yaml.SequenceNode:
|
||||
for _, item := range environment.Content {
|
||||
var entry string
|
||||
if err := item.Decode(&entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, value, _ := strings.Cut(entry, "=")
|
||||
res = append(res, request.Environment{Key: key, Value: value})
|
||||
}
|
||||
case 0:
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported environment format")
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
|
||||
@@ -4318,6 +4318,11 @@ const message = {
|
||||
'The following fields will change: {0}. The rule will be applied and verified immediately. Continue?',
|
||||
},
|
||||
runtime: {
|
||||
importEnv: 'Import Environment Variables',
|
||||
envImportError: 'Line {0}: {1}',
|
||||
envInvalidAssignment: 'Expected a valid NAME=value assignment',
|
||||
envUnclosedQuote: 'Unclosed quote',
|
||||
envUnexpectedText: 'Unexpected text after the closing quote',
|
||||
runtime: 'Runtime',
|
||||
default: 'Default',
|
||||
workDir: 'Working directory',
|
||||
|
||||
@@ -4371,6 +4371,11 @@ const message = {
|
||||
'Se modificarán los siguientes campos: {0}. La regla se aplicará y verificará inmediatamente. ¿Continuar?',
|
||||
},
|
||||
runtime: {
|
||||
importEnv: 'Importar variables de entorno',
|
||||
envImportError: 'Línea {0}: {1}',
|
||||
envInvalidAssignment: 'Se esperaba una asignación NOMBRE=valor válida',
|
||||
envUnclosedQuote: 'Comillas sin cerrar',
|
||||
envUnexpectedText: 'Texto inesperado después de las comillas de cierre',
|
||||
runtime: 'Runtime',
|
||||
default: 'Predeterminado',
|
||||
workDir: 'Directorio de trabajo',
|
||||
|
||||
@@ -4275,6 +4275,11 @@ const message = {
|
||||
editRuleConfirm: 'فیلدهای زیر تغییر میکنند: {0}. قانون بلافاصله اعمال و بررسی میشود. ادامه میدهید؟',
|
||||
},
|
||||
runtime: {
|
||||
importEnv: 'وارد کردن متغیرهای محیطی',
|
||||
envImportError: 'خط {0}: {1}',
|
||||
envInvalidAssignment: 'یک عبارت معتبر NAME=value لازم است',
|
||||
envUnclosedQuote: 'علامت نقل قول بسته نشده است',
|
||||
envUnexpectedText: 'متن اضافی پس از علامت نقل قول پایانی',
|
||||
runtime: 'محیط اجرا',
|
||||
default: 'پیشفرض',
|
||||
workDir: 'دایرکتوری کاری',
|
||||
|
||||
@@ -4303,6 +4303,11 @@ const message = {
|
||||
editRuleConfirm: '次の項目を変更します:{0}。ルールは直ちに適用され、再検証されます。続行しますか?',
|
||||
},
|
||||
runtime: {
|
||||
importEnv: '環境変数をインポート',
|
||||
envImportError: '{0} 行目:{1}',
|
||||
envInvalidAssignment: '有効な NAME=value 形式で入力してください',
|
||||
envUnclosedQuote: '引用符が閉じられていません',
|
||||
envUnexpectedText: '閉じ引用符の後に余分な文字があります',
|
||||
runtime: 'ランタイム',
|
||||
workDir: '作業ディレクトリ',
|
||||
localHelper:
|
||||
|
||||
@@ -4223,6 +4223,11 @@ const message = {
|
||||
editRuleConfirm: '다음 필드가 변경됩니다: {0}. 규칙이 즉시 적용되고 다시 검증됩니다. 계속하시겠습니까?',
|
||||
},
|
||||
runtime: {
|
||||
importEnv: '환경 변수 가져오기',
|
||||
envImportError: '{0}행: {1}',
|
||||
envInvalidAssignment: '유효한 NAME=value 형식이 필요합니다',
|
||||
envUnclosedQuote: '따옴표가 닫히지 않았습니다',
|
||||
envUnexpectedText: '닫는 따옴표 뒤에 불필요한 텍스트가 있습니다',
|
||||
runtime: '실행 환경',
|
||||
workDir: '작업 디렉토리',
|
||||
localHelper: '로컬 환경 설치 및 오프라인 환경 사용 관련 문제는 다음을 참조하세요 ',
|
||||
|
||||
@@ -4186,6 +4186,11 @@ const message = {
|
||||
editRuleConfirm: 'ຈະປ່ຽນຟິວຕໍ່ໄປນີ້: {0}. ກົດຈະຖືກນຳໃຊ້ແລະກວດສອບທັນທີ. ສືບຕໍ່ບໍ?',
|
||||
},
|
||||
runtime: {
|
||||
importEnv: 'ນຳເຂົ້າຕົວແປສະພາບແວດລ້ອມ',
|
||||
envImportError: 'ແຖວ {0}: {1}',
|
||||
envInvalidAssignment: 'ຕ້ອງເປັນຮູບແບບ NAME=value ທີ່ຖືກຕ້ອງ',
|
||||
envUnclosedQuote: 'ເຄື່ອງໝາຍວົງຢືມຍັງບໍ່ປິດ',
|
||||
envUnexpectedText: 'ມີຂໍ້ຄວາມເກີນຫຼັງເຄື່ອງໝາຍວົງຢືມປິດ',
|
||||
runtime: 'ຣັນທາມ (Runtime)',
|
||||
default: 'ຄ່າເລີ່ມຕົ້ນ',
|
||||
workDir: 'ໂຟນເດີທີ່ເຮັດວຽກ',
|
||||
|
||||
@@ -4383,6 +4383,11 @@ const message = {
|
||||
editRuleConfirm: 'Medan berikut akan diubah: {0}. Peraturan akan digunakan dan disahkan serta-merta. Teruskan?',
|
||||
},
|
||||
runtime: {
|
||||
importEnv: 'Import Pemboleh Ubah Persekitaran',
|
||||
envImportError: 'Baris {0}: {1}',
|
||||
envInvalidAssignment: 'Penetapan NAME=value yang sah diperlukan',
|
||||
envUnclosedQuote: 'Tanda petikan tidak ditutup',
|
||||
envUnexpectedText: 'Teks tidak dijangka selepas tanda petikan penutup',
|
||||
runtime: 'Runtime',
|
||||
workDir: 'Direktori kerja',
|
||||
localHelper:
|
||||
|
||||
@@ -4409,6 +4409,11 @@ const message = {
|
||||
'Os seguintes campos serão alterados: {0}. A regra será aplicada e verificada imediatamente. Continuar?',
|
||||
},
|
||||
runtime: {
|
||||
importEnv: 'Importar variáveis de ambiente',
|
||||
envImportError: 'Linha {0}: {1}',
|
||||
envInvalidAssignment: 'Era esperada uma atribuição NOME=valor válida',
|
||||
envUnclosedQuote: 'Aspas não fechadas',
|
||||
envUnexpectedText: 'Texto inesperado após as aspas de fechamento',
|
||||
runtime: 'Runtime',
|
||||
workDir: 'Diretório de trabalho',
|
||||
localHelper: 'Para problemas de instalação em ambiente local e uso em ambiente offline, consulte ',
|
||||
|
||||
@@ -4373,6 +4373,11 @@ const message = {
|
||||
editRuleConfirm: 'Будут изменены поля: {0}. Правило будет немедленно применено и проверено. Продолжить?',
|
||||
},
|
||||
runtime: {
|
||||
importEnv: 'Импорт переменных окружения',
|
||||
envImportError: 'Строка {0}: {1}',
|
||||
envInvalidAssignment: 'Ожидается корректное присваивание NAME=value',
|
||||
envUnclosedQuote: 'Незакрытая кавычка',
|
||||
envUnexpectedText: 'Лишний текст после закрывающей кавычки',
|
||||
runtime: 'Среда выполнения',
|
||||
workDir: 'Рабочая директория',
|
||||
localHelper: 'По вопросам установки в локальной среде и использования в автономном режиме обратитесь к ',
|
||||
|
||||
@@ -4387,6 +4387,11 @@ const message = {
|
||||
editRuleConfirm: 'Şu alanlar değiştirilecek: {0}. Kural hemen uygulanıp doğrulanacak. Devam edilsin mi?',
|
||||
},
|
||||
runtime: {
|
||||
importEnv: 'Ortam Değişkenlerini İçe Aktar',
|
||||
envImportError: 'Satır {0}: {1}',
|
||||
envInvalidAssignment: 'Geçerli bir NAME=value ataması bekleniyor',
|
||||
envUnclosedQuote: 'Kapatılmamış tırnak',
|
||||
envUnexpectedText: 'Kapanış tırnağından sonra beklenmeyen metin',
|
||||
runtime: 'Çalışma Zamanı',
|
||||
default: 'Varsayılan',
|
||||
workDir: 'Çalışma dizini',
|
||||
|
||||
@@ -4025,6 +4025,11 @@ const message = {
|
||||
editRuleConfirm: '將修改以下欄位:{0}。提交後會立即套用並回讀驗證,是否繼續?',
|
||||
},
|
||||
runtime: {
|
||||
importEnv: '匯入環境變數',
|
||||
envImportError: '第 {0} 行:{1}',
|
||||
envInvalidAssignment: '應為有效的變數名稱=value',
|
||||
envUnclosedQuote: '引號未閉合',
|
||||
envUnexpectedText: '閉合引號後有多餘內容',
|
||||
runtime: '執行環境',
|
||||
default: '預設',
|
||||
workDir: '工作目錄',
|
||||
|
||||
@@ -4074,6 +4074,11 @@ const message = {
|
||||
editRuleConfirm: '将修改以下字段:{0}。提交后会立即应用并回读验证,是否继续?',
|
||||
},
|
||||
runtime: {
|
||||
importEnv: '导入环境变量',
|
||||
envImportError: '第 {0} 行:{1}',
|
||||
envInvalidAssignment: '应为有效的变量名=value',
|
||||
envUnclosedQuote: '引号未闭合',
|
||||
envUnexpectedText: '闭合引号后存在多余内容',
|
||||
runtime: '运行环境',
|
||||
default: '默认',
|
||||
workDir: '工作目录',
|
||||
|
||||
@@ -40,7 +40,7 @@ type LocalTab = 'ollama' | 'vllm' | 'tensorrt' | 'downloader';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { isFxplay } = useGlobalStore();
|
||||
const { isFxplay, isProductPro } = useGlobalStore();
|
||||
|
||||
const tabLabels: Record<LocalTab, string> = {
|
||||
ollama: 'Ollama',
|
||||
@@ -66,7 +66,10 @@ const currentTab = computed<LocalTab>(() => {
|
||||
if (tab === 'vllm' || tab === 'downloader' || (tab === 'tensorrt' && isFxplay.value)) {
|
||||
return tab;
|
||||
}
|
||||
return 'ollama';
|
||||
if (tab === 'tensorrt') {
|
||||
return 'ollama';
|
||||
}
|
||||
return isProductPro.value ? 'vllm' : 'ollama';
|
||||
});
|
||||
|
||||
const currentComponent = computed(() => {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="mt-1">=</div>
|
||||
</el-col>
|
||||
<el-col :span="7">
|
||||
<el-form-item :prop="`environments.${index}.value`" :rules="rules.value">
|
||||
<el-form-item :prop="`environments.${index}.value`">
|
||||
<el-input v-model="env.value" :placeholder="$t('runtime.envValue')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -23,19 +23,59 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4">
|
||||
<el-button v-permission @click="addEnv">{{ $t('commons.button.add') }}</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<el-button v-permission @click="addEnv">{{ $t('commons.button.add') }}</el-button>
|
||||
<el-button v-permission icon="Upload" @click="openImport">{{ $t('runtime.importEnv') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="importVisible"
|
||||
:title="$t('runtime.importEnv')"
|
||||
width="min(680px, 90vw)"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@closed="importText = ''"
|
||||
>
|
||||
<el-input
|
||||
v-model="importText"
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
:aria-label="$t('runtime.environment')"
|
||||
placeholder="KEY=value"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<el-alert
|
||||
v-if="parsed.error"
|
||||
class="mt-3"
|
||||
type="error"
|
||||
:closable="false"
|
||||
:title="$t('runtime.envImportError', [parsed.error.line, $t('runtime.' + parsed.error.reason)])"
|
||||
/>
|
||||
<el-table v-else-if="parsed.entries.length" :data="parsed.entries" max-height="260" class="mt-3">
|
||||
<el-table-column prop="key" :label="$t('runtime.envKey')" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="value" :label="$t('runtime.envValue')" min-width="180" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="importVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button
|
||||
v-permission
|
||||
type="primary"
|
||||
:disabled="!!parsed.error || !parsed.entries.length"
|
||||
@click="importEnv"
|
||||
>
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { FormRules } from 'element-plus';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { Runtime } from '@/api/interface/runtime';
|
||||
import { mergeEnvironments, parseEnvironment } from '@/utils/runtime-environment';
|
||||
|
||||
const props = defineProps({
|
||||
environments: {
|
||||
@@ -50,6 +90,22 @@ const props = defineProps({
|
||||
|
||||
const { showHelper } = props;
|
||||
|
||||
const importVisible = ref(false);
|
||||
const importText = ref('');
|
||||
const parsed = computed(() => parseEnvironment(importText.value));
|
||||
|
||||
const openImport = () => {
|
||||
importText.value = '';
|
||||
importVisible.value = true;
|
||||
};
|
||||
|
||||
const importEnv = () => {
|
||||
if (parsed.value.error || !parsed.value.entries.length) return;
|
||||
mergeEnvironments(props.environments, parsed.value.entries);
|
||||
importVisible.value = false;
|
||||
importText.value = '';
|
||||
};
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
value: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user